0%

在现实世界中,许多对象并不是独立存在的,其中一个对象的行为发生改变可能会导致一个或者多个其他对象的行为也发生改变。例如,某种商品的物价上涨时会导致部分商家高兴,而消费者伤心。

在软件世界也是这样,例如,事件模型中的事件源与事件处理者。所有这些,如果用观察者模式来实现就非常方便。

观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。

观察者模式是一种对象行为型模式,其主要优点如下:

  • 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系
  • 目标与观察者之间建立了一套触发机制

它的主要缺点如下:

  • 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用
  • 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。

实现观察者模式时要注意具体目标对象和具体观察者对象之间不能直接调用,否则将使两者之间紧密耦合起来,这违反了面向对象的设计原则。

模式的结构#

观察者模式的主要角色如下:

  • 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法
  • 具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
  • 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
  • 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。

观察者模式的结构图如图所示:

模式的实现#

观察者模式的实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
class Program
{
static void Main(string[] args)
{
Subject subject=new ConcreteSubject();
IObserver obs1=new ConcreteObserver1();
IObserver obs2=new ConcreteObserver2();
subject.Add(obs1);
subject.Add(obs2);
subject.NotifyObserver();
Console.Read();
}
}


public abstract class Subject
{
protected List<IObserver> observers=new List<IObserver>();

public void Add(IObserver observer)
{
observers.Add(observer);
}

public void Remove(IObserver observer)
{
observers.Remove(observer);
}
public abstract void NotifyObserver();
}


public class ConcreteSubject : Subject
{
public override void NotifyObserver()
{
Console.WriteLine("具体目标发生改变...");
Console.WriteLine("--------------");

foreach (var obs in observers)
{
obs.Response();
}
}
}


public interface IObserver
{
void Response();
}


public class ConcreteObserver1 : IObserver
{
public void Response()
{
Console.WriteLine("具体观察者1作出反应!");
}
}


public class ConcreteObserver2 : IObserver
{
public void Response()
{
Console.WriteLine("具体观察者2作出反应!");
}
}

程序运行结果如下:

1
2
3
4
具体目标发生改变...
--------------
具体观察者1作出反应!
具体观察者2作出反应!

通过前面的分析与应用实例可知观察者模式适合以下几种情形:

  • 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
  • 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,可将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。

在.net环境下,其运行时库为开发者提供了IObservable和 IObserver接口,用于实现观察者模式软件设计。另外,ObservableCollection 类表示一个动态数据集合,它可在添加、删除项目或刷新整个列表时提供通知。

  • 提供者或主题,是将通知发送给观察者的对象。 提供程序是实现IObservable 接口的类或结构。 提供者必须实现单个方法IObservable .Subscribe,该方法由希望从提供者接收通知的观察者调用
  • 观察者,即从提供程序接收通知的对象。 观察者是实现 IObserver 接口的类或结构。 观察者必须实现以下三个方法,这三个方法均由提供程序调用
    IObserver.OnNext,它向观察者提供新信息或当前信息。
    IObserver.OnError,它通知观察者已发生错误。
    IObserver.OnCompleted,它指示提供程序已完成发送通知。
  • 允许提供程序跟踪观察者的一种机制。 通常情况下,提供程序使用容器对象(如 List 对象)来保存对已订阅通知的 IObserver 实现的引用。 将存储容器用于此目的使提供程序能够处理零到无限数量的观察者。 未定义观察者接收通知的顺序;提供程序可以随意使用任何方法来确定顺序
  • IDisposable 实现,它使提供程序在能够通知完成时删除观察者。 观察者从 Subscribe 方法接收对 IDisposable 实现的引用,因此它们还可以调用 IDisposable.Dispose 方法,以便在提供程序已完成发送通知之前取消订阅
  • 包含提供程序发送到其观察者的数据的对象。 此对象的类型对应 IObservable 和 IObserver 接口的泛型类型参数。 尽管此对象可与 IObservable 实现相同,但通常情况下,它是一个单独的类型

注:在 Java 中,通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。

下面的示例演示观察者设计模式,实现定位系统实时通知当前经纬度坐标,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
class Program
{
static void Main(string[] args)
{

LocationTracker provider = new LocationTracker();
LocationReporter reporter1 = new LocationReporter("FixedGPS");
reporter1.Subscribe(provider);
LocationReporter reporter2 = new LocationReporter("MobileGPS");
reporter2.Subscribe(provider);

provider.TrackLocation(new Location(47.6456, -122.1312));
reporter1.Unsubscribe();
provider.TrackLocation(new Location(47.6677, -122.1199));
provider.TrackLocation(null);
provider.EndTransmission();

Console.Read();
}
}




public struct Location
{
double lat, lon;

public Location(double latitude, double longitude)
{
this.lat = latitude;
this.lon = longitude;
}




public double Latitude
{ get { return this.lat; } }



public double Longitude
{ get { return this.lon; } }
}




public class LocationReporter : IObserver<Location>
{
private IDisposable unsubscriber;
private string instName;

public LocationReporter(string name)
{
this.instName = name;
}

public string Name
{ get { return this.instName; } }





public virtual void Subscribe(IObservable<Location> provider)
{
if (provider != null)
unsubscriber = provider.Subscribe(this);
}

public virtual void OnCompleted()
{
Console.WriteLine("位置跟踪器已将数据传输到 {0}", this.Name);
this.Unsubscribe();
}

public virtual void OnError(Exception e)
{
Console.WriteLine("{0}: 无法确定位置", this.Name);
}

public virtual void OnNext(Location value)
{
Console.WriteLine("{2}: 当前位置是 {0}, {1}", value.Latitude, value.Longitude, this.Name);
}




public virtual void Unsubscribe()
{
unsubscriber.Dispose();
}
}




public class LocationTracker : IObservable<Location>
{
public LocationTracker()
{
observers = new List<IObserver<Location>>();
}

private List<IObserver<Location>> observers;






public IDisposable Subscribe(IObserver<Location> observer)
{
if (!observers.Contains(observer))
observers.Add(observer);
return new Unsubscriber(observers, observer);
}




private class Unsubscriber : IDisposable
{
private List<IObserver<Location>> _observers;
private IObserver<Location> _observer;

public Unsubscriber(List<IObserver<Location>> observers, IObserver<Location> observer)
{
this._observers = observers;
this._observer = observer;
}

public void Dispose()
{
if (_observer != null && _observers.Contains(_observer))
_observers.Remove(_observer);
}
}

public void TrackLocation(Nullable<Location> loc)
{
foreach (var observer in observers)
{
if (!loc.HasValue)
observer.OnError(new LocationUnknownException());
else
observer.OnNext(loc.Value);
}
}

public void EndTransmission()
{
foreach (var observer in observers.ToArray())
{
if (observers.Contains(observer))
observer.OnCompleted();
}
observers.Clear();
}
}




public class LocationUnknownException : Exception
{
internal LocationUnknownException()
{ }
}

程序运行结果如下:

1
2
3
4
5
FixedGPS:当前位置是47.6456,-122.1312
MobileGPS:当前位置是47.6456,-122.1312
MobileGPS:当前位置是47.6677,-122.1199
MobileGPS:无法确定位置位置
跟踪器已将数据传输到MobileGPS

参考资料:
观察者设计模式——MSDN
ObservableCollection 类——MSDN
IObservable 接口——MSDN
IObserver 接口——MSDN

部署 Kubernetes Dashboard 2.0.0 并通过域名访问 - 简书

Excerpt

前提 假设已安装好 Kubernetes 集群以及 NGINX Ingress Controller, 信息如下: cluster-info ingress-nginx 部署…


部署 Kubernetes Dashboard 2.0.0 并通过域名访问

2021.05.08 18:24:23字数 113阅读 1,095

前提

假设已安装好 Kubernetes 集群以及 NGINX Ingress Controller, 信息如下:

  • cluster-info
1
[user@vm-centos-7 ~]$ kubectl cluster-info Kubernetes master is running at https://192.168.2.120:6443 KubeDNS is running at https://192.168.2.120:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
  • ingress-nginx
1
[user@vm-centos-7 ~]$ kubectl get all -n ingress-nginx NAME READY STATUS RESTARTS AGE pod/ingress-nginx-admission-create-c9wfb 0/1 Completed 0 20d pod/ingress-nginx-admission-patch-k9pvn 0/1 Completed 1 20d pod/ingress-nginx-controller-64db99fb6-9tcnv 1/1 Running 1 20d NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/ingress-nginx-controller NodePort 10.101.247.5 <none> 8080:30069/TCP,8443:32471/TCP,6379:32072/TCP,3306:32412/TCP 20d service/ingress-nginx-controller-admission ClusterIP 10.98.210.145 <none> 443/TCP 20d NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/ingress-nginx-controller 1/1 1 1 20d NAME DESIRED CURRENT READY AGE replicaset.apps/ingress-nginx-controller-64db99fb6 1 1 1 20d NAME COMPLETIONS DURATION AGE job.batch/ingress-nginx-admission-create 1/1 3s 20d job.batch/ingress-nginx-admission-patch 1/1 4s 20d

部署 Deployment/Service

1
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0/aio/deploy/recommended.yaml

部署 Ingress

1
kubectl apply -f ingress.yaml
  • ingress.yaml 内容如下:
1
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: kubernetes-dashboard namespace: kubernetes-dashboard annotations: kubernetes.io/ingress.class: "nginx" nginx.ingress.kubernetes.io/backend-protocol: HTTPS nginx.ingress.kubernetes.io/configuration-snippet: |- proxy_ssl_server_name on; proxy_ssl_name $host; spec: rules: - host: dashboard.k8s.local http: paths: - path: / backend: serviceName: kubernetes-dashboard servicePort: 443

创建示例帐号

创建 Service Account

1
kubectl apply -f service-account.yaml
  • service-account.yaml 内容如下:
1
apiVersion: v1 kind: ServiceAccount metadata: name: admin-user namespace: kubernetes-dashboard

创建 Cluster Role Binding

1
kubectl apply -f cluster-role-binding.yaml
  • cluster-role-binding.yaml 内容如下:
1
apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: admin-user roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cluster-admin subjects: - kind: ServiceAccount name: admin-user namespace: kubernetes-dashboard

获取 Bearer Token

1
kubectl -n kubernetes-dashboard get secret $(kubectl -n kubernetes-dashboard get sa/admin-user -o jsonpath="{.secrets[0].name}") -o go-template="{{.data.token | base64decode}}"

结果如下:

1
[user@vm-centos-7 ~]$ kubectl -n kubernetes-dashboard get secret $(kubectl -n kubernetes-dashboard get sa/admin-user -o jsonpath="{.secrets[0].name}") -o go-template="{{.data.token | base64decode}}" eyJhbGciOiJSUzI1NiIsImtpZCI6IkVXeDJFaU84RFkwc1BiU1c2VEpIZDZ3aUVsUDEzaENwX0d2NG9qZWtLODQifQ.eyJpc3MiOiJrdWJlcmas5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyasZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pasdfbi11c2VyLXRva2VuLWZ3NGJtIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjasdb3VudC9zZXJ2aWNlLWFjY291bsnQubmFtZSI6ImFkbWluLXVzZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiIyZWQ0NTlmZS1kNzEzLTQ1ZDgtYjc4Ni1hZjZkMTNjNGMxZjUiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZXJuZXRlcy1kYXNoYm9hcmQ6YWRtaW4tdXNlciJ9.saFr6kSsvopHH-OGUEaJKvi8KcZbh_gLL9S49siTQa5r8zm2n-MQY9E2yjGdg-ZHZk6e75nKEsjP6NDd864BcD11_NFZaFcG5RT0MvaHJoRvqFTRVrq_yJ9L5FqpYeFoRlaImOaGrsIN7JIoTyqrr6meRhjWC2RAATAGD4VGout1JZ5Aj7faSezwVoWrsaevxe1qIWgx0q7LotTiP3Tjh1Aijw2pMCbuJEDv-TpWsrL_ThByIJCQvLW77Xinj-aebdhlDSUdFcROkaLx43jZ0qI5uO66IGT9s1tLuQ2V1YFuIqK7tJ_fczxuwoB4Dj4qoxWoacxtp43fmWGtCFSySg

登录

打开 URL:http://dashboard.k8s.local, 选择 Token 并使用上一步得到的 Bearer Token 登录:

k8s-dashboard-login.png

结果如下:

k8s-dashboard.png

最后编辑于

:2021.05.08 21:10:26

更多精彩内容,就在简书APP

“小礼物走一走,来简书关注我”

还没有人赞赏,支持一下

  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解…

    沈念sama阅读 146,525评论 1赞 310

  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都…

  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些…

  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不…

  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我…

    茶点故事阅读 49,624评论 1赞 262

  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一…

  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决…

  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我…

  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经…

  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日…

    茶点故事阅读 29,597评论 2赞 224

  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。…

    茶点故事阅读 30,958评论 1赞 236

  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带…

  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境…

    茶点故事阅读 31,857评论 3赞 214

  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日…

  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响…

  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还…

  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚…

    茶点故事阅读 34,098评论 2赞 241

推荐阅读更多精彩内容

  • 一、组件版本和配置策略 1、组件版本 Kubernetes 1.10.4Docker 18.03.1-ceEtcd…

    Horne阅读 3,495评论 1赞 50

  • Ingress 管理群集中服务的外部访问的API对象,通常是HTTP。Ingress可以提供负载平衡,SSL 终止…

  • 下载Dashboard所需要用到的yaml文件 kubectl apply -f https://raw.gith

  • Dashboard是Kubernetes集群的基于Web的通用UI。 它允许用户管理在群集中运行的应用程序并对其进…

  • 安装Helm Helm由客户端命helm令行工具和服务端tiller组成,Helm的安装十分简单。 下载helm命…

    红黑咔咔阅读 2,788评论 1赞 0

(一)建表规约

1.【强制】表达是与否概念的字段,必须使用is_xxx的方式命名,数据类型是unsignedtinyint(1表示是,0表示否)
。说明:任何字段如果为非负数,必须是unsigned。正例:表达逻辑删除的字段名is_deleted,1表示删除,0表示未删除。
2.【强制】表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只出现数字。
数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。
说明:MySQL在Windows下不区分大小写,但在Linux下默认是区分大小写。
因此,数据库名、表名、字段名,都不允许出现任何大写字母,避免节外生枝。
正例:aliyun_admin,rdc_config,level3_name
反例:AliyunAdmin,rdcConfig,level_3_name
3.【强制】表名不使用复数名词。说明:表名应该仅仅表示表里面的实体内容,不应该表示实体数量,对应于DO类名也是单数形式,符合表达习惯。
4.【强制】禁用保留字,如desc、range、match、delayed等,请参考MySQL官方保留字。
5.【强制】主键索引名为pk_字段名;唯一索引名为uk_字段名;普通索引名则为idx_字段名。
说明:pk_ 即primary key;uk_ 即uniquekey;idx_ 即index的简称。
6.【强制】小数类型为decimal,禁止使用float和double。
说明:float和double在存储的时候,存在精度损失的问题,很可能在值的比较时,得到不正确的结果。
如果存储的数据范围超过decimal的范围,建议将数据拆成整数和小数分开存储。
7.【强制】如果存储的字符串长度几乎相等,使用char定长字符串类型。
8.【强制】varchar是可变长字符串,不预先分配存储空间,长度不要超过5000,如果存储长度大于此值,定义字段类型为text,独立出来一张表,用主键来对应,避免影响其它字段索引效率。
9.【强制】表必备三字段:id, gmt_create, gmt_modified。
说明:其中id必为主键,类型为unsigned bigint、单表时自增、步长为1。
gmt_create, gmt_modified的类型均为date_time类型,前者现在时表示主动创建,后者过去分词表示被动更新。
10.【推荐】表的命名最好是加上“业务名称_表的作用”。正例:alipay_task/ force_project/ trade_config
11.【推荐】库名与应用名称尽量一致。
12.【推荐】如果修改字段含义或对字段表示的状态追加时,需要及时更新字段注释。
13.【推荐】字段允许适当冗余,以提高查询性能,但必须考虑数据一致。
冗余字段应遵循:
1)不是频繁修改的字段。
2)不是varchar超长字段,更不能是text字段。
正例:商品类目名称使用频率高,字段长度短,名称基本一成不变,可在相关联的表中冗余存储类目名称,避免关联查询。
14.【推荐】单表行数超过500万行或者单表容量超过2GB,才推荐进行分库分表。
说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。
15.【参考】合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。
正例:如下表,其中无符号值可以避免误存负数,且扩大了表示范围。
对象年龄区间类型字节表示范围
人150岁之内unsigned tinyint1无符号值:0到255
龟数百岁unsigned smallint2无符号值:0到65535
恐龙化石数千万年unsigned int4无符号值:0到约42.9亿
太阳约50亿年unsigned bigint8无符号值:0到约10的19次方

(二)索引规约

1.【强制】业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。
说明:不要以为唯一索引影响了insert速度,这个速度损耗可以忽略,但提高查找速度是明显的;
另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。
2.【强制】超过三个表禁止join。需要join的字段,数据类型必须绝对一致;
多表关联查询时,保证被关联的字段需要有索引。说明:即使双表join也要注意表索引、SQL性能。
3.【强制】在varchar字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度即可。
说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为20的索引,区分度会高达90%以上,可以使用count(distinctleft(列名, 索引长度))/count(*)的区分度来确定。
4.【强制】页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。
说明:索引文件具有B-Tree的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索引。
5.【推荐】如果有orderby的场景,请注意利用索引的有序性。
orderby最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现file_sort的情况,影响查询性能。
正例:wherea=? andb=? orderbyc;索引:a_b_c
反例:索引中有范围查找,那么索引有序性无法利用,如:WHEREa>10 ORDERBYb;索引a_b无法排序。
6.【推荐】利用覆盖索引来进行查询操作,避免回表。
说明:如果一本书需要知道第11章是什么标题,会翻开第11章对应的那一页吗?目录浏览一下就好,这个目录就是起到覆盖索引的作用。
正例:能够建立索引的种类:主键索引、唯一索引、普通索引,而覆盖索引是一种查询的一种效果,用explain的结果,extra列会出现:usingindex。
7.【推荐】利用延迟关联或者子查询优化超多分页场景。
说明:MySQL并不是跳过offset行,而是取offset+N行,然后返回放弃前offset行,返回N行,
那当offset特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行SQL改写。
正例:先快速定位需要获取的id段,然后再关联:SELECT a.* FROM 表1 a, (select id from 表1 where 条件LIMIT 100000,20 ) b where a.id=b.id
8.【推荐】SQL性能优化的目标:至少要达到range级别,要求是ref级别,如果可以是consts最好。
说明:
1)consts单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。
2)ref指的是使用普通的索引(normalindex)。
3)range对索引进行范围检索。
反例:explain表的结果,type=index,索引物理文件全扫描,速度非常慢,这个index级别比较range还低,与全表扫描是小巫见大巫。
9.【推荐】建组合索引的时候,区分度最高的在最左边。正例:如果wherea=? andb=? ,a列的几乎接近于唯一值,那么只需要单建idx_a索引即可。
说明:存在非等号和等号混合判断条件时,在建索引时,请把等号条件的列前置。
如:wherea>? andb=? 那么即使a的区分度更高,也必须把b放在索引的最前列。
10.【推荐】防止因字段类型不同造成的隐式转换,导致索引失效。
11.【参考】创建索引时避免有如下极端误解:
1)宁滥勿缺。认为一个查询就需要建一个索引。
2)宁缺勿滥。认为索引会消耗空间、严重拖慢更新和新增速度。
3)抵制惟一索引。认为业务的惟一性一律需要在应用层通过“先查后插”方式解决。

(三)SQL语句

1.【强制】不要使用count(列名)或count(常量)来替代count(),count()是SQL92定义的标准统计行数的语法,跟数据库无关,跟NULL和非NULL无关。
说明:count(*)会统计值为NULL的行,而count(列名)不会统计此列为NULL值的行。
2.【强制】count(distinctcol)计算该列除NULL之外的不重复行数,注意count(distinctcol1, col2)如果其中一列全为NULL,那么即使另一列有不同的值,也返回为0。
3.【强制】当某一列的值全是NULL时,count(col)的返回结果为0,但sum(col)的返回结果为NULL,因此使用sum()时需注意NPE问题。
正例:可以使用如下方式来避免sum的NPE问题:SELECTIF(ISNULL(SUM(g)),0,SUM(g))FROMtable;
4.【强制】使用ISNULL()来判断是否为NULL值。
说明:NULL与任何值的直接比较都为NULL。
1)NULL<>NULL的返回结果是NULL,而不是false。
2)NULL=NULL的返回结果是NULL,而不是true。
3)NULL<>1的返回结果是NULL,而不是true。
5.【强制】在代码中写分页查询逻辑时,若count为0应直接返回,避免执行后面的分页语句。
6.【强制】不得使用外键与级联,一切外键概念必须在应用层解决。
说明:以学生和成绩的关系为例,学生表中的student_id是主键,那么成绩表中的student_id则为外键。
如果更新学生表中的student_id,同时触发成绩表中的student_id更新,即为级联更新。
外键与级联更新适用于单机低并发,不适合分布式、高并发集群;
级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。
7.【强制】禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。
8.【强制】数据订正时,删除和修改记录时,要先select,避免出现误删除,确认无误才能执行更新语句。
9.【推荐】in操作能避免则避免,若实在避免不了,需要仔细评估in后边的集合元素数量,控制在1000个之内。
10.【参考】如果有全球化需要,所有的字符存储与表示,均以utf-8编码,注意字符统计函数的区别。
说明:
SELECT LENGTH(“轻松工作”);返回为12
SELECT CHARACTER_LENGTH(“轻松工作”);返回为4
如果需要存储表情,那么选择utfmb4来进行存储,注意它与utf-8编码的区别。
11.【参考】TRUNCATETABLE比DELETE速度快,且使用的系统和事务日志资源少,
但TRUNCATE无事务且不触发trigger,有可能造成事故,故不建议在开发代码中使用此语句。
说明:
TRUNCATETABLE在功能上与不带WHERE子句的DELETE语句相同。

(四)ORM映射

1.【强制】在表查询中,一律不要使用* 作为查询的字段列表,需要哪些字段必须明确写明。
说明:
1)增加查询分析器解析成本。
2)增减字段容易与resultMap配置不一致。
2.【强制】POJO类的布尔属性不能加is,而数据库字段必须加is_,要求在resultMap中进行字段与属性之间的映射。
说明:参见定义POJO类以及数据库字段定义规定,在中增加映射,是必须的。
在MyBatis Generator生成的代码中,需要进行对应的修改。
3.【强制】不要用resultClass当返回参数,即使所有类属性名与数据库字段一一对应,也需要定义;
反过来,每一个表也必然有一个与之对应。说明:配置映射关系,使字段与DO类解耦,方便维护。
4.【强制】sql.xml配置参数使用:#{},#param# 不要使用${} 此种方式容易出现SQL注入。
5.【强制】iBATIS自带的queryForList(StringstatementName,intstart,intsize)不推荐使用。
说明:其实现方式是在数据库取到statementName对应的SQL语句的所有记录,再通过subList取start,size的子集合。
正例:Map

阿里巴巴提示:禁止用于商业用途,违者必究