-
Notifications
You must be signed in to change notification settings - Fork 13
/
rss.xml
2471 lines (2431 loc) · 510 KB
/
rss.xml
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>克鲁斯卡尔的博客</title>
<link>http://novoland.github.io</link>
<atom:link href="http://novoland.github.io/rss.xml" rel="self" type="application/rss+xml" />
<description>一个程序员</description>
<language>zh-CN</language>
<pubDate>Mon, 17 Aug 2015 21:16:05 +0800</pubDate>
<lastBuildDate>Mon, 17 Aug 2015 21:16:05 +0800</lastBuildDate>
<item>
<title>消息中心开发过程中踩的几个(常识)坑</title>
<link>http://novoland.github.io/%E5%B7%A5%E4%BD%9C/2015/08/17/%E6%B6%88%E6%81%AF%E4%B8%AD%E5%BF%83%E5%BC%80%E5%8F%91%E8%BF%87%E7%A8%8B%E4%B8%AD%E8%B8%A9%E7%9A%84%E5%87%A0%E4%B8%AA(%E5%B8%B8%E8%AF%86)%E5%9D%91.html</link>
<pubDate>17 Aug 2015</pubDate>
<author>克鲁斯卡尔</author>
<guid>http://novoland.github.io/%E5%B7%A5%E4%BD%9C/2015/08/17/消息中心开发过程中踩的几个(常识)坑</guid>
<description><div style="color: #2c3f51; line-height: 1.6;">
<div style="line-height: 1.6;"></div>
<div style="line-height: 1.6;">
</div><div style="line-height: 1.6;">
<p style="margin: 0 0 1.1em; line-height: 1.6;"></p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">碰到的坑主要在数据库并发访问的场景下踩的,原因也比较简单,属于常识坑,这里记录一下,吃一堑长一智。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">简单介绍下业务场景,消息中心负责向用户发消息(站内信),比如谁回复了你,谁赞了你,帖子被删被加精,社区又有新活动,上层模块如影评、社区调用消息中心公布的 thrift RPC 接口向用户发消息。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">消息分两层:聚合消息(session)和子消息(message),二者是父子关系,message 按照不同的规则合并到 session,比如一个用户收到的帖子回复消息,按各自所属帖子分别聚合到不同 session,所有系统通知聚合成一个 session 等等。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">Session和message均有两个文本字段:title和content,为了统一管理各种消息的格式,消息中心预先为每种类型配置了各自的velocity模板,业务方调用时传递参数,消息中心负责模板渲染,生成最终的文本。为了避免MySQL表过大,title和content是保存在Tair中的。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">所以当收到一个发布消息的请求时,处理步骤如下:</p>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;">找到message对应的session,如果不存在则创建一个;</li>
<li style="line-height: 1.6;">修改session的发送者、未读消息数、消息总数等字段,渲染得到其title、content,更新之;</li>
<li style="line-height: 1.6;">渲染得到message的title、content,插入之。</li>
</ol></div><div style="line-height: 1.6;">
<h2 style="font-family: inherit; font-weight: bold; line-height: 1.1; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 2.15em; margin: 1.2em 0 .6em 0; text-align: start;">1. if (not exist) INSERT else UPDATE</h2>
<p style="margin: 0 0 1.1em; line-height: 1.6;">第一步的实现一开始是这样的:</p>
</div><div style="line-height: 1.6;">
<pre style="word-break: break-word; font-family: 'Source Code Pro',monospace; white-space: pre-wrap; display: block; background-color: rgba(102,128,153,0.05); color: #333; word-wrap: break-word; font-size: .9em; background: #f6f6f6; line-height: 1.6; margin: 0 0 1.1em; padding: 2px; border: 0; border-radius: 5px; text-align: start;" xml:space="preserve"><code style="font-family: 'Source Code Pro',monospace; font-size: inherit; background-color: transparent; white-space: pre-wrap; border-radius: 0; color: #f8f8f2; display: block; background: #23241f; padding: 18px 28px;">session = findSession(msg);
<span style="line-height: 1.6; color: #f92672;">if</span>(session == <span style="line-height: 1.6; color: #f92672;">null</span>){
session = <span style="line-height: 1.6; color: #f92672;">new</span> Session();
<span style="line-height: 1.6; color: #75715e;">// 更新session字段</span>
add(session);
}<span style="line-height: 1.6; color: #f92672;">else</span>{
<span style="line-height: 1.6; color: #75715e;">// 更新session字段</span>
update(session);
}</code></pre>
<p style="margin: 0 0 1.1em; line-height: 1.6;">这样的实现有两个问题:</p>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;">add(session)时可能已经有其他事务insert成功了,此时再add会失败;</li>
<li style="line-height: 1.6;">高并发时,insert同一条数据比较容易产生死锁,这个问题足够再写篇笔记了,不赘述。</li>
</ol>
<p style="margin: 0 0 1.1em; line-height: 1.6;">对于问题1,数据库并不会出现脏数据,只是会在代码层面抛出异常,导致业务方发布消息失败。通常发消息的动作会是一个异步调用,调用方通常可以接受等待,但不能忍受异常失败,更不愿意捕获异常进行重试,因为相对的发消息只是一个支线流程,复杂度不宜太高。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">MySQL 提供了对标准insert语句的扩展 <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">INSERT INTO ... ON DUPLICATE KEY UPDATE ...</code>,如果 insert 触发了索引重复异常,则转为执行 update 动作,非常适合这里 “如果不存在则insert,存在则update” 的场景。重申,这是一个API的设计问题。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">对于问题2,<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">ON DUPLICATE KEY UPDATE</code>依然无法避免死锁(见<a href="http://bugs.mysql.com/bug.php?id=52020" style="background: transparent; color: #1980e6; text-decoration: none;" target="_blank">MySQL bug 52020</a>,死锁现场见<a href="https://www.evernote.com/OutboundRedirect.action?dest=https%3A%2F%2Fgist.github.com%2Fnovoland%2Ff14df279b0edf34d061f" style="background: transparent; color: #1980e6; text-decoration: none;" target="_blank">该条gist</a>),但概率会低一些(无责任嘴炮中…)</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">经验教训:</strong></p>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;">用 <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">INSERT INTO ... ON DUPLICATE KEY UPDATE ...</code> 代替代码中 <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">if null INSERT else UPDATE</code> 的逻辑。</li>
</ol></div><div style="line-height: 1.6;">
<h2 style="font-family: inherit; font-weight: bold; line-height: 1.1; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 2.15em; margin: 1.2em 0 .6em 0; text-align: start;">2. 自增字段的第二类丢失更新</h2>
<p style="margin: 0 0 1.1em; line-height: 1.6;">session 中维护了未读消息数(unreadCount)、消息总数(totalMsgCount),来了一条新消息必须自增这两个字段。最早的版本是在代码里先读这俩字段,+1再用<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">update(session)</code>写回DB,update是一个全字段的更新动作,但这显然有所谓的“第二类丢失更新”的问题。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">第二类丢失更新问题指的是两个并发的update动作,后提交的(假设B)将先提交的值(假设A)覆盖掉。如果A是一个普通的update倒也没什么问题,但如果A的逻辑是在代码里做自增/自减,然后再更新,那么A的自增/自减就会被覆盖掉:</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;"><img alt="Alt text" class="en-media" longdesc="./1433165317854.png" name="6145a052-5018-425a-82ab-cfdddceb54ca" src="/assets/img/ee085d1bc8f7e095587ae9061fcfa357.png" style="border: 0; vertical-align: middle; max-width: 100%;" title=""/></p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">解决办法是在DB层面用<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">UPDATE</code>语句做自增自减,利用DB的锁机制保证更新动作的互斥。但这还不够,代码里经常有这样的逻辑:先读一个对象,修改某些字段,再用<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">update(obj)</code>类似的语句更新到DB。如果<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">update(obj)</code>是全量更新,那么仍然存在问题:</p>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;">A 将字段i自增1;</li>
<li style="line-height: 1.6;">B 读该对象,修改非i的字段;</li>
<li style="line-height: 1.6;">A 提交;</li>
<li style="line-height: 1.6;">B update(obj) 并提交事务,A 的自增被覆盖了。</li>
</ol>
<p style="margin: 0 0 1.1em; line-height: 1.6;">所以最后的解决方案是:</p>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;">对每个需要自增自减的字段在DAO层提供<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">incr(incBy)</code>方法,通过<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">UPDATE</code>语句自增自减;</li>
<li style="line-height: 1.6;">将自增自减字段从各种<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">UPDATE</code>语句中移除,通过1提供的方法单独维护。</li>
</ol></div><div style="line-height: 1.6;">
<h2 style="font-family: inherit; font-weight: bold; line-height: 1.1; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 2.15em; margin: 1.2em 0 .6em 0; text-align: start;">3. 锁超时</h2>
<p style="margin: 0 0 1.1em; line-height: 1.6;">消息中心上线后,PM提出要将原有社区的消息全部迁移到消息中心,当时用了一个数量为24的线程池并发做数据迁移,结果测试时后台JDBC不停地报<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">INSERT INTO ... ON DUPLICATE KEY UPDATE</code> socket超时,而这句SQL是整个事务调用的第一条语句。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">一开始怀疑是网络问题,于是把JDBC连接字符串中的socketTimeout参数调高到10s,然而并没有什么卵用。考虑到只有在数据迁移这样的并发场景下才会出现问题,很自然地想到会不会是锁超时。回顾发布消息的流程,第一步是更新消息所属Session,如果测试数据中有一批连续的消息发送给同一个人,且对应的聚合消息为同一条,那么极端情况下会出现24个线程争用同一把锁的情况。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">但即使如此,如果每个事务的时间都比较短,对最后一个获取锁的事务而言10s也应该够了,除非事务里做了什么耗时的动作。按这个思路用 spring 提供的 <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">StopWatch</code> 分别对数据库操作、模板渲染&amp;保存到Tair两个阶段计时,发现前者平均30ms,后者则高达300ms,问题应该就出在这里。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">修复方法很简单:</p>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;">降低线程数量,24–&gt;12;</li>
<li style="line-height: 1.6;">将模板渲染 &amp; Tair存取的动作提到事务外面,降低事务耗时。</li>
</ol>
<p style="margin: 0 0 1.1em; line-height: 1.6;">但这样一来我们就无法利用事务的原子性了,假如模板渲染或Tair失败,事务并不会回滚,用户会看到title或content为空的消息。考虑到真实业务中并不会出现同时给一个人发送大量消息的场景,相对并发程度而言,线上的数据完整性更重要,因此只在数据迁移时用了上面的方案。假如哪天既要保证并发度,又要保证数据完整性,可以使用上述方案,并在第二步失败时手动对数据订正,将脏数据还原或删除;用户在一个极短的时间窗口内是可能看到脏数据的,但这并没有太大影响。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">最后还有一个问题,明明是锁的问题,为啥报 socket 超时呢?这是因为 InnoDB 锁超时时间由参数 <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">innodb_lock_wait_timeout</code>决定,默认值为50s,测试数据库并没有修改,因此 JDBC 只会发现 socket 读超时,而无法感知到锁超时。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">经验&amp;教训:</strong></p>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;">耗时的动作不在事务里做,尽量减少锁的持有时间;</li>
<li style="line-height: 1.6;">根据具体业务的特点和需求,在事务的ACID与并发性之间做tradeoff,必要情况下可以允许短暂的脏数据,事后再进行数据订正;</li>
<li style="line-height: 1.6;">活用 spring / apache.commons 提供的 <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">StopWatch</code> 分析任务耗时。</li>
</ol></div><div style="line-height: 1.6;"></div></div></description>
</item>
<item>
<title>异常</title>
<link>http://novoland.github.io/%E8%AE%BE%E8%AE%A1/2015/08/17/%E5%BC%82%E5%B8%B8.html</link>
<pubDate>17 Aug 2015</pubDate>
<author>克鲁斯卡尔</author>
<guid>http://novoland.github.io/%E8%AE%BE%E8%AE%A1/2015/08/17/异常</guid>
<description><div style="color: #2c3f51; font-family: 'Helvetica Neue', Arial, 'Hiragino Sans GB', STHeiti, 'Microsoft YaHei', 'WenQuanYi Micro Hei', SimSun, Song, sans-serif; line-height: 1.6;">
<div style="line-height: 1.6;"></div>
<div style="line-height: 1.6;">
</div><div style="line-height: 1.6;">
<p style="margin: 0 0 1.1em; line-height: 1.6;"></p></div><div style="line-height: 1.6;">
<h2 style="font-family: inherit; font-weight: bold; line-height: 1.1; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 2.15em; margin: 1.2em 0 .6em 0; text-align: start;">1. 异常分类</h2>
<p style="margin: 0 0 1.1em; line-height: 1.6;">JDK提供的异常基础类关系如下:</p>
</div><div style="line-height: 1.6;">
<pre style="word-break: break-word; font-family: 'Source Code Pro',monospace; white-space: pre-wrap; display: block; background-color: rgba(102,128,153,0.05); color: #333; word-wrap: break-word; font-size: .9em; background: #f6f6f6; line-height: 1.6; margin: 0 0 1.1em; padding: 2px; border: 0; border-radius: 5px; text-align: start;" xml:space="preserve"><code style="font-family: 'Source Code Pro',monospace; font-size: inherit; background-color: transparent; white-space: pre-wrap; border-radius: 0; color: #f8f8f2; display: block; background: #23241f; padding: 18px 28px;"><div style="line-height: 1.6;"><div style="line-height: 1.6;"><div style="line-height: 1.6;"><div style="line-height: 1.6;"><div style="line-height: 1.6;"> +---------+ </div><div style="line-height: 1.6;"> |Throwable| </div><div style="line-height: 1.6;"> +-+------++ </div><div style="line-height: 1.6;"> ^ ^ </div><div style="line-height: 1.6;"> | | </div><div style="line-height: 1.6;"> +--------++ +-+---+</div><div style="line-height: 1.6;"> |Exception| |Error|</div><div style="line-height: 1.6;"> +----+----+ +-----+</div><div style="line-height: 1.6;"> ^ </div><div style="line-height: 1.6;"> | </div><div style="line-height: 1.6;">+--------+-------+ </div><div style="line-height: 1.6;">|RuntimeException| </div><div style="line-height: 1.6;">+----------------+ </div></div></div></div></div></code></pre>
<p style="margin: 0 0 1.1em; line-height: 1.6;">异常分两种:</p>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;">继承 <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">Exception</code> 的异常类称为<strong style="font-weight: bold; line-height: 1.6;">checked exception</strong>,一个方法抛出 checked exception,就必须在方法签名中加上 <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">throws</code> 声明,否则无法通过编译,这也是称为 “checked” 的原因。调用方如果选择继续向上抛出,则也应在自己的方法加上相应声明。</li>
<li style="line-height: 1.6;">继承<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">RuntimeException</code>的异常类称为<strong style="font-weight: bold; line-height: 1.6;">unchecked exception</strong>,编译器不会检查。</li>
</ol>
</div><div style="line-height: 1.6;">
<h2 style="font-family: inherit; font-weight: bold; line-height: 1.1; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 2.15em; margin: 1.2em 0 .6em 0; text-align: start;">2. 什么时候抛出异常</h2>
<p style="margin: 0 0 1.1em; line-height: 1.6;">《程序员修炼之道-从小工到专家》第四章介绍了一种被称为“Design By Contract(按合约设计)”的设计思路,它认为,程序中的每个函数在提供某项服务,在开始真正逻辑之前,函数对程序当前的状态有某种<em style="line-height: 1.6;">期望</em>,函数对服务成功完成后程序的状态有某种<em style="line-height: 1.6;">承诺</em>。这些<em style="line-height: 1.6;">期望</em>和<em style="line-height: 1.6;">承诺</em>可以这样描述:</p>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">Precondition</strong>:为了调用函数,必须为真的条件,函数的需求。在 Precondition 无法被满足时,函数不应开始提供服务。Precondition 通常指的是<strong style="font-weight: bold; line-height: 1.6;">参数的合法性</strong>,调用者有责任保证传递的参数的正确性;</li>
<li style="line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">Postcondition</strong>:函数保证会做的事情,方法完成时程序的状态,一个use case的happy ending;</li>
<li style="line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">Class Invariant</strong>(暂不讨论):类确保从调用者的视角来看,该条件始终为真。在函数执行过程中,不变项不一定会保持,但在函数退出时,不变项必须为真。</li>
</ol>
<p style="margin: 0 0 1.1em; line-height: 1.6;">举个例子,一个方法向指定用户发送push,它接受两个参数,用户id和message,具体实现是:1)查询用户设置,如果用户关闭了push 则不发送;2)查询用户当天收到的push数,超过阈值则不发送;3)调用第三方API,根据id找到设备token;4)调用第三方API,对指定设备发送push。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">对这个方法,id、message不为空这类参数格式约束条件属于 Precondition。方法成功执行后,要么由于用户的设置、收到push太多而选择不push,要么成功向用户发送push,这属于方法的 Postcondition。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">函数和调用者之间的<em style="line-height: 1.6;">合约</em>可以解读为:</p>
<blockquote style="padding: 15px 20px; margin: 0 0 1.1em; border-left: 5px solid rgba(102,128,153,0.075); border-left-width: 10px; background-color: rgba(102,128,153,0.05); border-top-right-radius: 5px; border-bottom-right-radius: 5px;">
<p style="margin: 0 0 1.1em; font-size: 1em; font-weight: 300; margin-bottom: 0; line-height: 1.6;">如果调用者满足了函数的所有Precondition,那么函数在完成时,所有Postcondition和Class Invariant为真。</p>
</blockquote>
<p style="margin: 0 0 1.1em; line-height: 1.6;">所以,什么时候抛异常?</p>
<blockquote style="padding: 15px 20px; margin: 0 0 1.1em; border-left: 5px solid rgba(102,128,153,0.075); border-left-width: 10px; background-color: rgba(102,128,153,0.05); border-top-right-radius: 5px; border-bottom-right-radius: 5px;">
<p style="margin: 0 0 1.1em; font-size: 1em; font-weight: 300; margin-bottom: 0; line-height: 1.6;">当函数在开始真正业务逻辑之前发现Precondition不满足、业务逻辑进行过程中由于各种原因(业务规则冲突、第三方库调用失败、代码错误等)导致无法满足Postcondition时,抛异常。</p>
</blockquote>
<p style="margin: 0 0 1.1em; line-height: 1.6;">以上述push方法为例,如果参数id或message为null,违反了Precondition,方法可以立即抛出<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">IllegalArgumentException</code>而不进入业务逻辑。另一方面,导致方法无法满足Postcondition的原因有很多,比如用户不存在、数据库连接失败、第三方API调用失败、找不到用户的设备token等等,这些情况也应该抛出异常。虽然用户关闭push、接收push过多也会导致push发送失败,但这属于正常的业务逻辑范畴,不算异常情况。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">这里有几个常见问题:</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">1.<strong style="font-weight: bold; line-height: 1.6;">为什么不用异常码?</strong> <br/>
很多人喜欢返回bool表示函数是否成功,或在失败的时候返回null、特殊的异常码。这种方案的第一个问题是,调用方无法知道到底是什么原因导致失败的(返回bool/null);第二个问题是,调用方可以选择忽略异常码,但异常如果不处理则会沿调用栈上浮,到达最上层的统一异常处理器,或导致当前线程退出,从而实现fail-fast,系统不至于处在一个不稳定的、非正确的状态。此外,异常还可以将错误处理代码隔离到<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">catch</code>块内,保持正常业务代码整洁。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">2.<strong style="font-weight: bold; line-height: 1.6;">参数校验谁做?</strong> <br/>
调用方有责任保证参数的合法性,但函数本身对调用方应该是防御性的,因此最完备的方案是进行两次校验,一次在调用方,一次在函数内。很多情况下这样显得很繁琐,所以如果函数是在一个可信、可控的环境中被调用的,比如调用方和函数都是你写的,那么函数内的参数校验可以省略;但假如你在写一个公共库,那么函数内的参数校验就是必须的。常见的参数校验库有<a href="https://www.evernote.com/OutboundRedirect.action?dest=https%3A%2F%2Fcode.google.com%2Fp%2Fguava-libraries%2Fwiki%2FPreconditionsExplained" style="background: transparent; color: #1980e6; text-decoration: none;" target="_blank">Guava 的 Preconditions</a>、<a href="http://jinnianshilongnian.iteye.com/blog/1990081" style="background: transparent; color: #1980e6; text-decoration: none;" target="_blank">Bean Validation 1.1 的实现 Hibernate Validator</a>。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">3.<strong style="font-weight: bold; line-height: 1.6;">异常的情况有很多种,用一堆异常类还是一个异常类+各种ErrorCode?</strong> <br/>
我的答案是用一堆异常类,原因:</p>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;"><code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">CashNotEnoughException</code> 比 <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">ServiceException</code> + <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">ErrorType.CASH_NOT_ENOUGH</code> 更显式;</li>
<li style="line-height: 1.6;">不同的异常类可以传递不同的上下文和具体异常信息,如<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">CashNotEnoughException</code>可以捎带用户当前的cash,后者无法做到这一点。</li>
</ol>
</div><div style="line-height: 1.6;">
<h2 style="font-family: inherit; font-weight: bold; line-height: 1.1; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 2.15em; margin: 1.2em 0 .6em 0; text-align: start;">3. 抛出什么类型的异常</h2>
<p style="margin: 0 0 1.1em; line-height: 1.6;">OK,现在我们要抛异常了,但究竟抛 <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">checked</code> 还是 <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">unchecked</code> 异常呢?</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">关于这两类异常的争论从来没有停止过,比如<a href="http://www.iteye.com/topic/2038" style="background: transparent; color: #1980e6; text-decoration: none;" target="_blank">这里</a>、<a href="http://tutorials.jenkov.com/java-exception-handling/checked-or-unchecked-exceptions.html" style="background: transparent; color: #1980e6; text-decoration: none;" target="_blank">这里</a>。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">checked的优点是:</p>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;">将异常显式化,强制让caller考虑如何处理异常,不让caller有忘记处理异常的机会</li>
<li style="line-height: 1.6;">可以将这种强制性沿调用栈向上传导。用unchecked,一个caller很难知道调用栈的哪个地方会抛什么异常。</li>
</ol>
<p style="margin: 0 0 1.1em; line-height: 1.6;">缺点是污染方法签名(包括抛出异常的函数本身、propagate异常的调用方),增加后期维护成本。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">主流观点是,<strong style="font-weight: bold; line-height: 1.6;">可恢复的、业务类的异常</strong>用checked exception,<strong style="font-weight: bold; line-height: 1.6;">不可恢复的异常</strong>用unchecked exception,如数据库连接失败、http调用超时。但实践中发现该规则存在的最大问题是,callee如何得知caller是否有能力从某种异常情况下恢复?调用方千变万化,可能A可以恢复B不行,函数本身无法了解这些情况。因此我的选择是,<strong style="font-weight: bold; line-height: 1.6;">抛弃checked exception,全部使用unchecked exception,并标注在javadoc中</strong>,让caller自己决定哪些是自己感兴趣可以处理的异常,这是一种更温和的方式。况且,调用一个方法之前了解其可能抛出的异常,这也是一个合格的调用方应该做的。某些语言如C#、Python取消了checked exception这种设计,个人觉得也是有一定道理的。</p>
</div><div style="line-height: 1.6;">
<h2 style="font-family: inherit; font-weight: bold; line-height: 1.1; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 2.15em; margin: 1.2em 0 .6em 0; text-align: start;">4. 异常和日志</h2>
<p style="margin: 0 0 1.1em; line-height: 1.6;">调用方对方法抛出的异常,要么处理,要么向上冒泡,为了让异常有迹可循,通常要记录在日志中。我的原则是,在<strong style="font-weight: bold; line-height: 1.6;">异常被处理的地方</strong>打日志,这个地方可能是“真正”的catch并恢复,也可能是一个最外层的统一的异常拦截器,catch所有未被捕获的异常做统一处理。</p>
</div><div style="line-height: 1.6;">
<h2 style="font-family: inherit; font-weight: bold; line-height: 1.1; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 2.15em; margin: 1.2em 0 .6em 0; text-align: start;">参考文档</h2>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;">《Effective Java》Item 57- Item 65</li>
<li style="line-height: 1.6;">《程序员修炼之道-从小工到专家》第四章</li>
<li style="line-height: 1.6;"><a href="http://www.iteye.com/topic/2038" style="background: transparent; color: #1980e6; text-decoration: none;" target="_blank">为什么 Java 中要使用 Checked Exceptions</a></li>
<li style="line-height: 1.6;"><a href="http://tutorials.jenkov.com/java-exception-handling/checked-or-unchecked-exceptions.html" style="background: transparent; color: #1980e6; text-decoration: none;" target="_blank">Checked or Unchecked Exceptions?</a></li>
<li style="line-height: 1.6;"><a href="http://docs.oracle.com/javase/tutorial/essential/exceptions/advantages.html" style="background: transparent; color: #1980e6; text-decoration: none;" target="_blank">Advantages of Exceptions</a></li>
</ol></div><div style="line-height: 1.6;"></div></div></description>
</item>
<item>
<title>Restful API 的设计规范</title>
<link>http://novoland.github.io/%E8%AE%BE%E8%AE%A1/2015/08/17/Restful%20API%20%E7%9A%84%E8%AE%BE%E8%AE%A1%E8%A7%84%E8%8C%83.html</link>
<pubDate>17 Aug 2015</pubDate>
<author>克鲁斯卡尔</author>
<guid>http://novoland.github.io/%E8%AE%BE%E8%AE%A1/2015/08/17/Restful API 的设计规范</guid>
<description><div style="color: #2c3f51; line-height: 1.6;">
<div style="line-height: 1.6;"></div>
<div style="line-height: 1.6;">
</div><div style="line-height: 1.6;">
<p style="margin: 0 0 1.1em; line-height: 1.6;"></p>
<div style="line-height: 1.6;"><div style="line-height: 1.6;"><div style="line-height: 1.6;">
<ul style="margin-top: 0; margin-bottom: 15px; list-style-type: none; line-height: 1.6;">
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">Restful API 的设计规范</a><ul style="margin-top: 0; margin-bottom: 15px; list-style-type: none; line-height: 1.6;">
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">1. URI</a><ul style="margin-top: 0; margin-bottom: 15px; list-style-type: none; line-height: 1.6;">
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">URI规范</a></li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">资源集合 vs 单个资源</a></li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">避免层级过深的URI</a></li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">对Composite资源的访问</a></li>
</ul>
</li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">2. Request</a><ul style="margin-top: 0; margin-bottom: 15px; list-style-type: none; line-height: 1.6;">
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">HTTP方法</a></li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">安全性和幂等性</a></li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">复杂查询</a></li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">Bookmarker</a></li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">Format</a></li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">Content Negotiation</a></li>
</ul>
</li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">6. Response</a><ul style="margin-top: 0; margin-bottom: 15px; list-style-type: none; line-height: 1.6;">
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">分页response</a></li>
</ul>
</li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">7. 错误处理</a></li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">8. 服务型资源</a></li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">9. 异步任务</a></li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">10. API的演进</a><ul style="margin-top: 0; margin-bottom: 15px; list-style-type: none; line-height: 1.6;">
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">版本</a></li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">URI失效</a></li>
</ul>
</li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">11. 安全</a></li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">参考文档</a></li>
</ul>
</li>
</ul>
</div>
</div>
</div>
<p style="margin: 0 0 1.1em; line-height: 1.6;">本文总结了 RESTful API 设计相关的一些原则,只覆盖了常见的场景。有些规则只是针对自己项目而言,并非其他做法都是错误的。</p>
</div><div style="line-height: 1.6;">
<h2 style="font-family: inherit; font-weight: bold; line-height: 1.1; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 2.15em; margin: 1.2em 0 .6em 0; text-align: start;">1. URI</h2>
<p style="margin: 0 0 1.1em; line-height: 1.6;">URI 表示资源,资源一般对应服务器端领域模型中的实体类。</p>
</div><div style="line-height: 1.6;">
<h3 style="font-family: inherit; font-weight: bold; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 1.7em; margin: 1.2em 0 .6em 0; text-align: start; line-height: 1.6;">URI规范</h3>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;">不用大写;</li>
<li style="line-height: 1.6;">用中杠<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">-</code>不用下杠<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">_</code>;</li>
<li style="line-height: 1.6;">参数列表要encode;</li>
<li style="line-height: 1.6;">URI中的名词表示资源集合,使用复数形式。</li>
</ol>
</div><div style="line-height: 1.6;">
<h3 style="font-family: inherit; font-weight: bold; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 1.7em; margin: 1.2em 0 .6em 0; text-align: start; line-height: 1.6;">资源集合 vs 单个资源</h3>
<p style="margin: 0 0 1.1em; line-height: 1.6;">URI表示资源的两种方式:资源集合、单个资源。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">资源集合:</p>
</div><div style="line-height: 1.6;">
<pre style="word-break: break-word; font-family: 'Source Code Pro',monospace; white-space: pre-wrap; display: block; background-color: rgba(102,128,153,0.05); color: #333; word-wrap: break-word; font-size: .9em; background: #f6f6f6; line-height: 1.6; margin: 0 0 1.1em; padding: 2px; border: 0; border-radius: 5px; text-align: start;" xml:space="preserve"><code style="font-family: 'Source Code Pro',monospace; font-size: inherit; background-color: transparent; white-space: pre-wrap; border-radius: 0; color: #f8f8f2; display: block; background: #23241f; padding: 18px 28px;">/zoos <span style="line-height: 1.6; color: #75715e;">//所有动物园</span>
/zoos/<span style="line-height: 1.6; color: #ae81ff;">1</span>/animals <span style="line-height: 1.6; color: #75715e;">//id为1的动物园中的所有动物</span></code></pre>
<p style="margin: 0 0 1.1em; line-height: 1.6;">单个资源:</p>
</div><div style="line-height: 1.6;">
<pre style="word-break: break-word; font-family: 'Source Code Pro',monospace; white-space: pre-wrap; display: block; background-color: rgba(102,128,153,0.05); color: #333; word-wrap: break-word; font-size: .9em; background: #f6f6f6; line-height: 1.6; margin: 0 0 1.1em; padding: 2px; border: 0; border-radius: 5px; text-align: start;" xml:space="preserve"><code style="font-family: 'Source Code Pro',monospace; font-size: inherit; background-color: transparent; white-space: pre-wrap; border-radius: 0; color: #f8f8f2; display: block; background: #23241f; padding: 18px 28px;">/zoos/<span style="line-height: 1.6; color: #ae81ff;">1</span> <span style="line-height: 1.6; color: #75715e;">//id为1的动物园</span>
/zoos/<span style="line-height: 1.6; color: #ae81ff;">1</span>;<span style="line-height: 1.6; color: #ae81ff;">2</span>;<span style="line-height: 1.6; color: #ae81ff;">3</span> <span style="line-height: 1.6; color: #75715e;">//id为1,2,3的动物园</span></code></pre>
</div><div style="line-height: 1.6;">
<h3 style="font-family: inherit; font-weight: bold; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 1.7em; margin: 1.2em 0 .6em 0; text-align: start; line-height: 1.6;">避免层级过深的URI</h3>
<p style="margin: 0 0 1.1em; line-height: 1.6;"><code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">/</code>在url中表达层级,用于<strong style="font-weight: bold; line-height: 1.6;">按实体关联关系进行对象导航</strong>,一般根据id导航。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">过深的导航容易导致url膨胀,不易维护,如 <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">GET /zoos/1/areas/3/animals/4</code>,尽量使用查询参数代替路径中的实体导航,如<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">GET /animals?zoo=1&amp;area=3</code>;</p>
</div><div style="line-height: 1.6;">
<h3 style="font-family: inherit; font-weight: bold; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 1.7em; margin: 1.2em 0 .6em 0; text-align: start; line-height: 1.6;">对Composite资源的访问</h3>
<p style="margin: 0 0 1.1em; line-height: 1.6;">服务器端的组合实体必须在uri中通过父实体的id导航访问。</p>
<blockquote style="padding: 15px 20px; margin: 0 0 1.1em; border-left: 5px solid rgba(102,128,153,0.075); border-left-width: 10px; background-color: rgba(102,128,153,0.05); border-top-right-radius: 5px; border-bottom-right-radius: 5px;">
<p style="margin: 0 0 1.1em; font-size: 1em; font-weight: 300; margin-bottom: 0; line-height: 1.6;">组合实体不是first-class的实体,它的生命周期完全依赖父实体,无法独立存在,在实现上通常是对数据库表中某些列的抽象,不直接对应表,也无id。一个常见的例子是 User — Address,Address是对User表中zipCode/country/city三个字段的简单抽象,无法独立于User存在。必须通过User索引到Address:<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">GET /user/1/addresses</code></p>
</blockquote>
</div><div style="line-height: 1.6;">
<h2 style="font-family: inherit; font-weight: bold; line-height: 1.1; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 2.15em; margin: 1.2em 0 .6em 0; text-align: start;">2. Request</h2>
</div><div style="line-height: 1.6;">
<h3 style="font-family: inherit; font-weight: bold; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 1.7em; margin: 1.2em 0 .6em 0; text-align: start; line-height: 1.6;">HTTP方法</h3>
<p style="margin: 0 0 1.1em; line-height: 1.6;">通过标准HTTP方法对资源CRUD:</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">GET:查询</p>
</div><div style="line-height: 1.6;">
<pre style="word-break: break-word; font-family: 'Source Code Pro',monospace; white-space: pre-wrap; display: block; background-color: rgba(102,128,153,0.05); color: #333; word-wrap: break-word; font-size: .9em; background: #f6f6f6; line-height: 1.6; margin: 0 0 1.1em; padding: 2px; border: 0; border-radius: 5px; text-align: start;" xml:space="preserve"><code style="font-family: 'Source Code Pro',monospace; font-size: inherit; background-color: transparent; white-space: pre-wrap; border-radius: 0; color: #f8f8f2; display: block; background: #23241f; padding: 18px 28px;"><span style="line-height: 1.6; color: #f92672;">GET</span> /zoos
<span style="line-height: 1.6; color: #f92672;">GET</span> /zoos/<span style="line-height: 1.6; color: #ae81ff;">1</span>
<span style="line-height: 1.6; color: #f92672;">GET</span> /zoos/<span style="line-height: 1.6; color: #ae81ff;">1</span>/employees</code></pre>
<p style="margin: 0 0 1.1em; line-height: 1.6;">POST:创建单个资源。<strong style="font-weight: bold; line-height: 1.6;">POST一般向“资源集合”型uri发起</strong></p>
</div><div style="line-height: 1.6;">
<pre style="word-break: break-word; font-family: 'Source Code Pro',monospace; white-space: pre-wrap; display: block; background-color: rgba(102,128,153,0.05); color: #333; word-wrap: break-word; font-size: .9em; background: #f6f6f6; line-height: 1.6; margin: 0 0 1.1em; padding: 2px; border: 0; border-radius: 5px; text-align: start;" xml:space="preserve"><code style="font-family: 'Source Code Pro',monospace; font-size: inherit; background-color: transparent; white-space: pre-wrap; border-radius: 0; color: #f8f8f2; display: block; background: #23241f; padding: 18px 28px;"><span style="line-height: 1.6; color: #f92672;">POST</span> /animals <span style="line-height: 1.6; color: #75715e;">//新增动物</span>
<span style="line-height: 1.6; color: #f92672;">POST</span> /zoos/1/employees <span style="line-height: 1.6; color: #75715e;">//为id为1的动物园雇佣员工</span></code></pre>
<p style="margin: 0 0 1.1em; line-height: 1.6;">PUT:更新单个资源(全量),客户端提供完整的更新后的资源。与之对应的是 PATCH,PATCH 负责部分更新,客户端提供要更新的那些字段。<strong style="font-weight: bold; line-height: 1.6;">PUT/PATCH一般向“单个资源”型uri发起</strong></p>
</div><div style="line-height: 1.6;">
<pre style="word-break: break-word; font-family: 'Source Code Pro',monospace; white-space: pre-wrap; display: block; background-color: rgba(102,128,153,0.05); color: #333; word-wrap: break-word; font-size: .9em; background: #f6f6f6; line-height: 1.6; margin: 0 0 1.1em; padding: 2px; border: 0; border-radius: 5px; text-align: start;" xml:space="preserve"><code style="font-family: 'Source Code Pro',monospace; font-size: inherit; background-color: transparent; white-space: pre-wrap; border-radius: 0; color: #f8f8f2; display: block; background: #23241f; padding: 18px 28px;">PUT <span style="line-height: 1.6; color: #ae81ff;">/animals/</span><span style="line-height: 1.6; color: #ae81ff;">1</span>
PUT <span style="line-height: 1.6; color: #ae81ff;">/zoos/</span><span style="line-height: 1.6; color: #ae81ff;">1</span></code></pre>
<p style="margin: 0 0 1.1em; line-height: 1.6;">DELETE:删除</p>
</div><div style="line-height: 1.6;">
<pre style="word-break: break-word; font-family: 'Source Code Pro',monospace; white-space: pre-wrap; display: block; background-color: rgba(102,128,153,0.05); color: #333; word-wrap: break-word; font-size: .9em; background: #f6f6f6; line-height: 1.6; margin: 0 0 1.1em; padding: 2px; border: 0; border-radius: 5px; text-align: start;" xml:space="preserve"><code style="font-family: 'Source Code Pro',monospace; font-size: inherit; background-color: transparent; white-space: pre-wrap; border-radius: 0; color: #f8f8f2; display: block; background: #23241f; padding: 18px 28px;"><span style="line-height: 1.6; color: #f92672;">DELETE</span> <span style="line-height: 1.6; color: #ae81ff;">/zoos/</span><span style="line-height: 1.6; color: #ae81ff;">1</span><span style="line-height: 1.6; color: #ae81ff;">/employees/</span><span style="line-height: 1.6; color: #ae81ff;">2</span>
<span style="line-height: 1.6; color: #f92672;">DELETE</span> <span style="line-height: 1.6; color: #ae81ff;">/zoos/</span><span style="line-height: 1.6; color: #ae81ff;">1</span><span style="line-height: 1.6; color: #ae81ff;">/employees/</span><span style="line-height: 1.6; color: #ae81ff;">2</span>;<span style="line-height: 1.6; color: #ae81ff;">4</span>;<span style="line-height: 1.6; color: #ae81ff;">5</span>
<span style="line-height: 1.6; color: #f92672;">DELETE</span> <span style="line-height: 1.6; color: #ae81ff;">/zoos/</span><span style="line-height: 1.6; color: #ae81ff;">1</span><span style="line-height: 1.6; color: #ae81ff;">/animals /</span><span style="line-height: 1.6; color: #ae81ff;">/删除id为1的动物园内的所有动物</span></code></pre>
<p style="margin: 0 0 1.1em; line-height: 1.6;">HEAD / OPTION 用的不多,就不多解释了。</p>
</div><div style="line-height: 1.6;">
<h3 style="font-family: inherit; font-weight: bold; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 1.7em; margin: 1.2em 0 .6em 0; text-align: start; line-height: 1.6;">安全性和幂等性</h3>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">安全性</strong>:不会改变资源状态,可以理解为只读的;</li>
<li style="line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">幂等性</strong>:执行1次和执行N次,对资源状态改变的效果是等价的。</li>
</ol>
<table style="border-collapse: collapse; border-spacing: 0; margin-bottom: 20px; line-height: 1.6;">
<thead style="line-height: 1.6;">
<tr style="line-height: 1.6;">
<th align="center" style="font-weight: bold; vertical-align: bottom; padding: .5em; border-top: 0; border: 1px solid #ddd; line-height: 1.6;">.</th>
<th align="center" style="font-weight: bold; vertical-align: bottom; padding: .5em; border-top: 0; border: 1px solid #ddd; line-height: 1.6;">安全性</th>
<th align="center" style="font-weight: bold; vertical-align: bottom; padding: .5em; border-top: 0; border: 1px solid #ddd; line-height: 1.6;">幂等性</th>
</tr>
</thead>
<tbody style="line-height: 1.6;"><tr style="line-height: 1.6;">
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">GET</td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">√</td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">√</td>
</tr>
<tr style="line-height: 1.6;">
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">POST</td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">×</td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">×</td>
</tr>
<tr style="line-height: 1.6;">
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">PUT</td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">×</td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">√</td>
</tr>
<tr style="line-height: 1.6;">
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">DELETE</td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">×</td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">√</td>
</tr>
</tbody></table>
<p style="margin: 0 0 1.1em; line-height: 1.6;">安全性和幂等性均不保证反复请求能拿到相同的response。以 DELETE 为例,第一次DELETE返回200表示删除成功,第二次返回404提示资源不存在,这是允许的。</p>
</div><div style="line-height: 1.6;">
<h3 style="font-family: inherit; font-weight: bold; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 1.7em; margin: 1.2em 0 .6em 0; text-align: start; line-height: 1.6;">复杂查询</h3>
<p style="margin: 0 0 1.1em; line-height: 1.6;">查询可以捎带以下参数:</p>
<table style="border-collapse: collapse; border-spacing: 0; margin-bottom: 20px; line-height: 1.6;">
<thead style="line-height: 1.6;">
<tr style="line-height: 1.6;">
<th align="center" style="font-weight: bold; vertical-align: bottom; padding: .5em; border-top: 0; border: 1px solid #ddd; line-height: 1.6;">.</th>
<th align="left" style="font-weight: bold; vertical-align: bottom; padding: .5em; border-top: 0; border: 1px solid #ddd; line-height: 1.6;">示例</th>
<th align="left" style="font-weight: bold; vertical-align: bottom; padding: .5em; border-top: 0; border: 1px solid #ddd; line-height: 1.6;">备注</th>
</tr>
</thead>
<tbody style="line-height: 1.6;"><tr style="line-height: 1.6;">
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">过滤条件</td>
<td align="left" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"><code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">?type=1&amp;age=16</code></td>
<td align="left" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">允许一定的uri冗余,如<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">/zoos/1</code>与<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">/zoos?id=1</code></td>
</tr>
<tr style="line-height: 1.6;">
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">排序</td>
<td align="left" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"><code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">?sort=age,desc</code></td>
<td align="left" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"></td>
</tr>
<tr style="line-height: 1.6;">
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">投影</td>
<td align="left" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"><code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">?whitelist=id,name,email</code></td>
<td align="left" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"></td>
</tr>
<tr style="line-height: 1.6;">
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">分页</td>
<td align="left" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"><code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">?limit=10&amp;offset=3</code></td>
<td align="left" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"></td>
</tr>
</tbody></table>
</div><div style="line-height: 1.6;">
<h3 style="font-family: inherit; font-weight: bold; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 1.7em; margin: 1.2em 0 .6em 0; text-align: start; line-height: 1.6;">Bookmarker</h3>
<p style="margin: 0 0 1.1em; line-height: 1.6;">经常使用的、复杂的查询标签化,降低维护成本。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">如:</p>
</div><div style="line-height: 1.6;">
<pre style="word-break: break-word; font-family: 'Source Code Pro',monospace; white-space: pre-wrap; display: block; background-color: rgba(102,128,153,0.05); color: #333; word-wrap: break-word; font-size: .9em; background: #f6f6f6; line-height: 1.6; margin: 0 0 1.1em; padding: 2px; border: 0; border-radius: 5px; text-align: start;" xml:space="preserve"><code style="font-family: 'Source Code Pro',monospace; font-size: inherit; background-color: transparent; white-space: pre-wrap; border-radius: 0; color: #f8f8f2; display: block; background: #23241f; padding: 18px 28px;">GET /trades?<span style="line-height: 1.6; color: #f8f8f2;">status=</span>closed&amp;<span style="line-height: 1.6; color: #f8f8f2;">sort=</span>created,desc</code></pre>
<p style="margin: 0 0 1.1em; line-height: 1.6;">快捷方式:</p>
</div><div style="line-height: 1.6;">
<pre style="word-break: break-word; font-family: 'Source Code Pro',monospace; white-space: pre-wrap; display: block; background-color: rgba(102,128,153,0.05); color: #333; word-wrap: break-word; font-size: .9em; background: #f6f6f6; line-height: 1.6; margin: 0 0 1.1em; padding: 2px; border: 0; border-radius: 5px; text-align: start;" xml:space="preserve"><code style="font-family: 'Source Code Pro',monospace; font-size: inherit; background-color: transparent; white-space: pre-wrap; border-radius: 0; color: #f8f8f2; display: block; background: #23241f; padding: 18px 28px;"><span style="line-height: 1.6; color: #f92672;">GET</span> /trades<span style="line-height: 1.6;">#recently-closed</span>
或者
<span style="line-height: 1.6; color: #f92672;">GET</span> /trades/recently-closed</code></pre>
</div><div style="line-height: 1.6;">
<h3 style="font-family: inherit; font-weight: bold; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 1.7em; margin: 1.2em 0 .6em 0; text-align: start; line-height: 1.6;">Format</h3>
<p style="margin: 0 0 1.1em; line-height: 1.6;">只用以下常见的3种body format:</p>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;"><p style="margin: 0 0 1.1em; line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">Content-Type: application/json</strong></p>
<pre style="word-break: break-word; font-family: 'Source Code Pro',monospace; white-space: pre-wrap; display: block; background-color: rgba(102,128,153,0.05); color: #333; word-wrap: break-word; font-size: .9em; background: #f6f6f6; line-height: 1.6; margin: 0 0 1.1em; padding: 2px; border: 0; border-radius: 5px; text-align: start;" xml:space="preserve"><code style="font-family: 'Source Code Pro',monospace; font-size: inherit; background-color: transparent; white-space: pre-wrap; border-radius: 0; color: #f8f8f2; display: block; background: #23241f; padding: 18px 28px;">POST /v1/animal HTTP/1.1
Host: api.example.org
Accept: application/json
Content-Type: application/json
Content-Length: 24
{
"name": "Gir",
"animalType": "12"
}</code></pre></li>
<li style="line-height: 1.6;"><p style="margin: 0 0 1.1em; line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">Content-Type: application/x-www-form-urlencoded</strong> (浏览器POST表单用的格式)</p>
<pre style="word-break: break-word; font-family: 'Source Code Pro',monospace; white-space: pre-wrap; display: block; background-color: rgba(102,128,153,0.05); color: #333; word-wrap: break-word; font-size: .9em; background: #f6f6f6; line-height: 1.6; margin: 0 0 1.1em; padding: 2px; border: 0; border-radius: 5px; text-align: start;" xml:space="preserve"><code style="font-family: 'Source Code Pro',monospace; font-size: inherit; background-color: transparent; white-space: pre-wrap; border-radius: 0; color: #f8f8f2; display: block; background: #23241f; padding: 18px 28px;">POST /login HTTP/1.1
Host: example.com
Content-Length: 31
Accept: text/html
Content-Type: application/x-www-form-urlencoded
username=root&amp;password=Zion0101</code></pre></li>
<li style="line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">Content-Type: multipart/form-data; boundary=—-RANDOM_jDMUxq4Ot5</strong> (表单有文件上传时的格式)</li>
</ol>
</div><div style="line-height: 1.6;">
<h3 style="font-family: inherit; font-weight: bold; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 1.7em; margin: 1.2em 0 .6em 0; text-align: start; line-height: 1.6;">Content Negotiation</h3>
<p style="margin: 0 0 1.1em; line-height: 1.6;">资源可以有多种表示方式,如json、xml、pdf、excel等等,客户端可以指定自己期望的格式,通常有两种方式:</p>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;"><p style="margin: 0 0 1.1em; line-height: 1.6;">http header <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">Accept</code>:</p>
<pre style="word-break: break-word; font-family: 'Source Code Pro',monospace; white-space: pre-wrap; display: block; background-color: rgba(102,128,153,0.05); color: #333; word-wrap: break-word; font-size: .9em; background: #f6f6f6; line-height: 1.6; margin: 0 0 1.1em; padding: 2px; border: 0; border-radius: 5px; text-align: start;" xml:space="preserve"><code style="font-family: 'Source Code Pro',monospace; font-size: inherit; background-color: transparent; white-space: pre-wrap; border-radius: 0; color: #f8f8f2; display: block; background: #23241f; padding: 18px 28px;"><span style="line-height: 1.6; color: #66d9ef;">Accept</span><span style="line-height: 1.6; color: #f92672;">:application/xml</span>;q=<span style="line-height: 1.6; color: #ae81ff;">0</span>.<span style="line-height: 1.6; color: #ae81ff;">6</span>,application/atom+xml;q=<span style="line-height: 1.6; color: #ae81ff;">1.0</span></code></pre>
<p style="margin: 0 0 1.1em; line-height: 1.6;">q为各项格式的偏好程度</p></li>
<li style="line-height: 1.6;">url中加文件后缀:<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">/zoo/1.json</code></li>
</ol>
</div><div style="line-height: 1.6;">
<h2 style="font-family: inherit; font-weight: bold; line-height: 1.1; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 2.15em; margin: 1.2em 0 .6em 0; text-align: start;">6. Response</h2>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;"><p style="margin: 0 0 1.1em; line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">不要包装</strong>: <br/>
response 的 body 直接就是数据,不要做多余的包装。错误示例:</p>
<pre style="word-break: break-word; font-family: 'Source Code Pro',monospace; white-space: pre-wrap; display: block; background-color: rgba(102,128,153,0.05); color: #333; word-wrap: break-word; font-size: .9em; background: #f6f6f6; line-height: 1.6; margin: 0 0 1.1em; padding: 2px; border: 0; border-radius: 5px; text-align: start;" xml:space="preserve"><code style="font-family: 'Source Code Pro',monospace; font-size: inherit; background-color: transparent; white-space: pre-wrap; border-radius: 0; color: #f8f8f2; display: block; background: #23241f; padding: 18px 28px;">{
<span style="line-height: 1.6; color: #e6db74;">"success"</span>:<span style="line-height: 1.6; color: #ae81ff;">true</span>,
<span style="line-height: 1.6; color: #e6db74;">"data"</span>:{<span style="line-height: 1.6; color: #e6db74;">"id"</span>:<span style="line-height: 1.6; color: #ae81ff;">1</span>,<span style="line-height: 1.6; color: #e6db74;">"name"</span>:<span style="line-height: 1.6; color: #e6db74;">"xiaotuan"</span>},
}</code></pre></li>
<li style="line-height: 1.6;"><p style="margin: 0 0 1.1em; line-height: 1.6;">各HTTP方法成功处理后的数据格式:</p>
<table style="border-collapse: collapse; border-spacing: 0; margin-bottom: 20px; line-height: 1.6;">
<thead style="line-height: 1.6;">
<tr style="line-height: 1.6;">
<th align="left" style="font-weight: bold; vertical-align: bottom; padding: .5em; border-top: 0; border: 1px solid #ddd; line-height: 1.6;">·</th>
<th align="center" style="font-weight: bold; vertical-align: bottom; padding: .5em; border-top: 0; border: 1px solid #ddd; line-height: 1.6;">response 格式</th>
</tr>
</thead>
<tbody style="line-height: 1.6;"><tr style="line-height: 1.6;">
<td align="left" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">GET</td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">单个对象、集合</td>
</tr>
<tr style="line-height: 1.6;">
<td align="left" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">POST</td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">新增成功的对象</td>
</tr>
<tr style="line-height: 1.6;">
<td align="left" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">PUT/PATCH</td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">更新成功的对象</td>
</tr>
<tr style="line-height: 1.6;">
<td align="left" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">DELETE</td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">空</td>
</tr>
</tbody></table>
</li>
<li style="line-height: 1.6;"><p style="margin: 0 0 1.1em; line-height: 1.6;">json格式的约定:</p>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;">时间用长整形(毫秒数),客户端自己按需解析(<a href="http://mementjs.com" style="background: transparent; color: #1980e6; text-decoration: none;" target="_blank">moment.js</a>)</li>
<li style="line-height: 1.6;">不传<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">null</code>字段</li></ol></li>
</ol>
</div><div style="line-height: 1.6;">
<h3 style="font-family: inherit; font-weight: bold; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 1.7em; margin: 1.2em 0 .6em 0; text-align: start; line-height: 1.6;">分页response</h3>
</div><div style="line-height: 1.6;">
<pre style="word-break: break-word; font-family: 'Source Code Pro',monospace; white-space: pre-wrap; display: block; background-color: rgba(102,128,153,0.05); color: #333; word-wrap: break-word; font-size: .9em; background: #f6f6f6; line-height: 1.6; margin: 0 0 1.1em; padding: 2px; border: 0; border-radius: 5px; text-align: start;" xml:space="preserve"><code style="font-family: 'Source Code Pro',monospace; font-size: inherit; background-color: transparent; white-space: pre-wrap; border-radius: 0; color: #f8f8f2; display: block; background: #23241f; padding: 18px 28px;">{
<span style="line-height: 1.6; color: #e6db74;">"paging"</span>:{<span style="line-height: 1.6; color: #e6db74;">"limit"</span>:<span style="line-height: 1.6; color: #ae81ff;">10</span>,<span style="line-height: 1.6; color: #e6db74;">"offset"</span>:<span style="line-height: 1.6; color: #ae81ff;">0</span>,<span style="line-height: 1.6; color: #e6db74;">"total"</span>:<span style="line-height: 1.6; color: #ae81ff;">729</span>},
<span style="line-height: 1.6; color: #e6db74;">"data"</span>:[{},{},{}...]
}</code></pre>
</div><div style="line-height: 1.6;">
<h2 style="font-family: inherit; font-weight: bold; line-height: 1.1; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 2.15em; margin: 1.2em 0 .6em 0; text-align: start;">7. 错误处理</h2>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;">不要发生了错误但给2xx响应,客户端可能会缓存成功的http请求;</li>
<li style="line-height: 1.6;">正确设置http状态码,不要自定义;</li>
<li style="line-height: 1.6;">Response body 提供 1) 错误的代码(日志/问题追查);2) 错误的描述文本(展示给用户)。</li>
</ol>
<p style="margin: 0 0 1.1em; line-height: 1.6;">对第三点的实现稍微多说一点:</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">Java 服务器端一般用异常表示 RESTful API 的错误。API 可能抛出两类异常:业务异常和非业务异常。<strong style="font-weight: bold; line-height: 1.6;">业务异常</strong>由自己的业务代码抛出,表示一个用例的前置条件不满足、业务规则冲突等,比如参数校验不通过、权限校验失败。<strong style="font-weight: bold; line-height: 1.6;">非业务类异常</strong>表示不在预期内的问题,通常由类库、框架抛出,或由于自己的代码逻辑错误导致,比如数据库连接失败、空指针异常、除0错误等等。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">业务类异常必须提供2种信息:</p>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;">如果抛出该类异常,HTTP 响应状态码应该设成什么;</li>
<li style="line-height: 1.6;">异常的文本描述;</li>
</ol>
<p style="margin: 0 0 1.1em; line-height: 1.6;">在Controller层使用统一的异常拦截器:</p>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;">设置 HTTP 响应状态码:对业务类异常,用它指定的 HTTP code;对非业务类异常,统一500;</li>
<li style="line-height: 1.6;">Response Body 的错误码:异常类名</li>
<li style="line-height: 1.6;">Response Body 的错误描述:对业务类异常,用它指定的错误文本;对非业务类异常,线上可以统一文案如“服务器端错误,请稍后再试”,开发或测试环境中用异常的 stacktrace,服务器端提供该行为的开关。</li>
</ol>
<p style="margin: 0 0 1.1em; line-height: 1.6;">常用的http状态码及使用场景:</p>
<table style="border-collapse: collapse; border-spacing: 0; margin-bottom: 20px; line-height: 1.6;">
<thead style="line-height: 1.6;">
<tr style="line-height: 1.6;">
<th align="left" style="font-weight: bold; vertical-align: bottom; padding: .5em; border-top: 0; border: 1px solid #ddd; line-height: 1.6;">状态码</th>
<th style="font-weight: bold; vertical-align: bottom; padding: .5em; border-top: 0; border: 1px solid #ddd; line-height: 1.6;">使用场景</th>
</tr>
</thead>
<tbody style="line-height: 1.6;"><tr style="line-height: 1.6;">
<td align="left" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">400 bad request</td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">常用在参数校验</td>
</tr>
<tr style="line-height: 1.6;">
<td align="left" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">401 unauthorized</td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">未经验证的用户,常见于未登录。如果经过验证后依然没权限,应该 403(即 authentication 和 authorization 的区别)。</td>
</tr>
<tr style="line-height: 1.6;">
<td align="left" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">403 forbidden</td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">无权限</td>
</tr>
<tr style="line-height: 1.6;">
<td align="left" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">404 not found</td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">资源不存在</td>
</tr>
<tr style="line-height: 1.6;">
<td align="left" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">500 internal server error</td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">非业务类异常</td>
</tr>
<tr style="line-height: 1.6;">
<td align="left" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">503 service unavaliable</td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">由容器抛出,自己的代码不要抛这个异常</td>
</tr>
</tbody></table>
</div><div style="line-height: 1.6;">
<h2 style="font-family: inherit; font-weight: bold; line-height: 1.1; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 2.15em; margin: 1.2em 0 .6em 0; text-align: start;">8. 服务型资源</h2>
<p style="margin: 0 0 1.1em; line-height: 1.6;">除了资源简单的CRUD,服务器端经常还会提供其他服务,这些服务无法直接用上面提到的URI映射。如:</p>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;">按关键字搜索;</li>
<li style="line-height: 1.6;">计算地球上两点间的距离;</li>
<li style="line-height: 1.6;">批量向用户推送消息</li>
</ol>
<p style="margin: 0 0 1.1em; line-height: 1.6;">可以把这些服务看成资源,计算的结果是资源的presentation,按服务属性选择合适的HTTP方法。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">例:</p></div><div style="line-height: 1.6;">
<pre style="word-break: break-word; font-family: 'Source Code Pro',monospace; white-space: pre-wrap; display: block; background-color: rgba(102,128,153,0.05); color: #333; word-wrap: break-word; font-size: .9em; background: #f6f6f6; line-height: 1.6; margin: 0 0 1.1em; padding: 2px; border: 0; border-radius: 5px; text-align: start;" xml:space="preserve"><code style="font-family: 'Source Code Pro',monospace; font-size: inherit; background-color: transparent; white-space: pre-wrap; border-radius: 0; color: #f8f8f2; display: block; background: #23241f; padding: 18px 28px;">GET /search?q=filter?category=file 搜索
GET /distance-calc?lats=<span style="line-height: 1.6; color: #ae81ff;">47.480</span>&amp;lngs=-<span style="line-height: 1.6; color: #ae81ff;">122.389</span>&amp;late=<span style="line-height: 1.6; color: #ae81ff;">37.108</span>&amp;lnge=-<span style="line-height: 1.6; color: #ae81ff;">122.448</span>
POST /batch-publish-msg
[{<span style="line-height: 1.6; color: #e6db74;">"from"</span>:<span style="line-height: 1.6; color: #ae81ff;">0</span>,<span style="line-height: 1.6; color: #e6db74;">"to"</span>:<span style="line-height: 1.6; color: #ae81ff;">1</span>,<span style="line-height: 1.6; color: #e6db74;">"text"</span>:<span style="line-height: 1.6; color: #e6db74;">"abc"</span>},{},{}<span style="line-height: 1.6; color: #f92672;">...</span>]</code></pre></div><div style="line-height: 1.6;">
<h2 style="font-family: inherit; font-weight: bold; line-height: 1.1; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 2.15em; margin: 1.2em 0 .6em 0; text-align: start;">9. 异步任务</h2>
<p style="margin: 0 0 1.1em; line-height: 1.6;">对耗时的异步任务,服务器端接受客户端传递的参数后,应返回创建成功的任务资源,其中包含了任务的执行状态。客户端可以轮训该任务获得最新的执行进度。</p>
</div><div style="line-height: 1.6;">
<pre style="word-break: break-word; font-family: 'Source Code Pro',monospace; white-space: pre-wrap; display: block; background-color: rgba(102,128,153,0.05); color: #333; word-wrap: break-word; font-size: .9em; background: #f6f6f6; line-height: 1.6; margin: 0 0 1.1em; padding: 2px; border: 0; border-radius: 5px; text-align: start;" xml:space="preserve"><code style="font-family: 'Source Code Pro',monospace; font-size: inherit; background-color: transparent; white-space: pre-wrap; border-radius: 0; color: #f8f8f2; display: block; background: #23241f; padding: 18px 28px;">提交任务:
POST /batch-publish-msg
[{<span style="line-height: 1.6; color: #e6db74;">"from"</span>:<span style="line-height: 1.6; color: #ae81ff;">0</span>,<span style="line-height: 1.6; color: #e6db74;">"to"</span>:<span style="line-height: 1.6; color: #ae81ff;">1</span>,<span style="line-height: 1.6; color: #e6db74;">"text"</span>:<span style="line-height: 1.6; color: #e6db74;">"abc"</span>},{},{}<span style="line-height: 1.6; color: #f92672;">...</span>]
返回:
{<span style="line-height: 1.6; color: #e6db74;">"taskId"</span>:<span style="line-height: 1.6; color: #ae81ff;">3</span>,<span style="line-height: 1.6; color: #e6db74;">"createBy"</span>:<span style="line-height: 1.6; color: #e6db74;">"Anonymous"</span>,<span style="line-height: 1.6; color: #e6db74;">"status"</span>:<span style="line-height: 1.6; color: #e6db74;">"running"</span>}
GET /task/<span style="line-height: 1.6; color: #ae81ff;">3</span>
{<span style="line-height: 1.6; color: #e6db74;">"taskId"</span>:<span style="line-height: 1.6; color: #ae81ff;">3</span>,<span style="line-height: 1.6; color: #e6db74;">"createBy"</span>:<span style="line-height: 1.6; color: #e6db74;">"Anonymous"</span>,<span style="line-height: 1.6; color: #e6db74;">"status"</span>:<span style="line-height: 1.6; color: #e6db74;">"success"</span>}</code></pre>
<p style="margin: 0 0 1.1em; line-height: 1.6;">如果任务的执行状态包括较多信息,可以把“执行状态”抽象成<strong style="font-weight: bold; line-height: 1.6;">组合资源</strong>,客户端查询该状态资源了解任务的执行情况。</p>
</div><div style="line-height: 1.6;">
<pre style="word-break: break-word; font-family: 'Source Code Pro',monospace; white-space: pre-wrap; display: block; background-color: rgba(102,128,153,0.05); color: #333; word-wrap: break-word; font-size: .9em; background: #f6f6f6; line-height: 1.6; margin: 0 0 1.1em; padding: 2px; border: 0; border-radius: 5px; text-align: start;" xml:space="preserve"><code style="font-family: 'Source Code Pro',monospace; font-size: inherit; background-color: transparent; white-space: pre-wrap; border-radius: 0; color: #f8f8f2; display: block; background: #23241f; padding: 18px 28px;">提交任务:
POST /batch-publish-msg
[{<span style="line-height: 1.6; color: #e6db74;">"from"</span>:<span style="line-height: 1.6; color: #ae81ff;">0</span>,<span style="line-height: 1.6; color: #e6db74;">"to"</span>:<span style="line-height: 1.6; color: #ae81ff;">1</span>,<span style="line-height: 1.6; color: #e6db74;">"text"</span>:<span style="line-height: 1.6; color: #e6db74;">"abc"</span>},{},{}<span style="line-height: 1.6; color: #f92672;">...</span>]
返回:
{<span style="line-height: 1.6; color: #e6db74;">"taskId"</span>:<span style="line-height: 1.6; color: #ae81ff;">3</span>,<span style="line-height: 1.6; color: #e6db74;">"createBy"</span>:<span style="line-height: 1.6; color: #e6db74;">"Anonymous"</span>}
GET /task/<span style="line-height: 1.6; color: #ae81ff;">3</span>/status
{<span style="line-height: 1.6; color: #e6db74;">"progress"</span>:<span style="line-height: 1.6; color: #e6db74;">"50%"</span>,<span style="line-height: 1.6; color: #e6db74;">"total"</span>:<span style="line-height: 1.6; color: #ae81ff;">18</span>,<span style="line-height: 1.6; color: #e6db74;">"success"</span>:<span style="line-height: 1.6; color: #ae81ff;">8</span>,<span style="line-height: 1.6; color: #e6db74;">"fail"</span>:<span style="line-height: 1.6; color: #ae81ff;">1</span>}</code></pre>
</div><div style="line-height: 1.6;">
<h2 style="font-family: inherit; font-weight: bold; line-height: 1.1; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 2.15em; margin: 1.2em 0 .6em 0; text-align: start;">10. API的演进</h2>
</div><div style="line-height: 1.6;">
<h3 style="font-family: inherit; font-weight: bold; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 1.7em; margin: 1.2em 0 .6em 0; text-align: start; line-height: 1.6;">版本</h3>
<p style="margin: 0 0 1.1em; line-height: 1.6;">常见的三种方式:</p>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;">在uri中放版本信息:<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">GET /v1/users/1</code></li>
<li style="line-height: 1.6;">Accept Header:<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">Accept: application/json+v1</code></li>
<li style="line-height: 1.6;">自定义 Header:<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">X-Api-Version: 1</code></li>
</ol>
<p style="margin: 0 0 1.1em; line-height: 1.6;">用第一种,虽然没有那么优雅,但最明显最方便。</p>
</div><div style="line-height: 1.6;">
<h3 style="font-family: inherit; font-weight: bold; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 1.7em; margin: 1.2em 0 .6em 0; text-align: start; line-height: 1.6;">URI失效</h3>
<p style="margin: 0 0 1.1em; line-height: 1.6;">随着系统发展,总有一些API失效或者迁移,对失效的API,返回404 not found 或 410 gone;对迁移的API,返回 301 重定向。</p>
</div><div style="line-height: 1.6;">
<h2 style="font-family: inherit; font-weight: bold; line-height: 1.1; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 2.15em; margin: 1.2em 0 .6em 0; text-align: start;">11. 安全</h2>
<p style="margin: 0 0 1.1em; line-height: 1.6;">这个不熟,接触到的时候再说。</p>
</div><div style="line-height: 1.6;">
<h2 style="font-family: inherit; font-weight: bold; line-height: 1.1; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 2.15em; margin: 1.2em 0 .6em 0; text-align: start;">参考文档</h2>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;">&lt; RESTful Web Services Cookbook &gt;</li>
<li style="line-height: 1.6;"><a href="https://www.evernote.com/OutboundRedirect.action?dest=https%3A%2F%2Fgithub.com%2Ftlhunter%2Fconsumer-centric-api-design" style="background: transparent; color: #1980e6; text-decoration: none;" target="_blank">Consumer-Centric API Design</a></li>
<li style="line-height: 1.6;"><a href="https://www.evernote.com/OutboundRedirect.action?dest=https%3A%2F%2Fwww.zybuluo.com%2Fyanbo-ai%2Fnote%2F17890" style="background: transparent; color: #1980e6; text-decoration: none;" target="_blank">RESTful Best Practices</a></li>
</ol></div><div style="line-height: 1.6;"></div></div></description>
</item>
<item>
<title>InnoDB 锁</title>
<link>http://novoland.github.io/%E6%95%B0%E6%8D%AE%E5%BA%93/2015/08/17/InnoDB%20%E9%94%81.html</link>
<pubDate>17 Aug 2015</pubDate>
<author>克鲁斯卡尔</author>
<guid>http://novoland.github.io/%E6%95%B0%E6%8D%AE%E5%BA%93/2015/08/17/InnoDB 锁</guid>
<description><div style="color: #2c3f51; font-family: 'Helvetica Neue', Arial, 'Hiragino Sans GB', STHeiti, 'Microsoft YaHei', 'WenQuanYi Micro Hei', SimSun, Song, sans-serif; line-height: 1.6;">
<div style="line-height: 1.6;"></div>
<div style="line-height: 1.6;">
</div><div style="line-height: 1.6;">
<p style="margin: 0 0 1.1em; line-height: 1.6;"></p>
<div style="line-height: 1.6;"><div style="line-height: 1.6;"><div style="line-height: 1.6;">
<ul style="margin-top: 0; margin-bottom: 15px; list-style-type: none; line-height: 1.6;">
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">InnoDB 锁</a><ul style="margin-top: 0; margin-bottom: 15px; list-style-type: none; line-height: 1.6;">
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">1. 事务并发问题</a></li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">2. 事务隔离级别</a></li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">3. MVCC</a></li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">4. 锁</a><ul style="margin-top: 0; margin-bottom: 15px; list-style-type: none; line-height: 1.6;">
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">二阶段加锁协议 (Two-Phase Locking)</a></li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">锁的细分</a></li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">表锁</a><ul style="margin-top: 0; margin-bottom: 15px; list-style-type: none; line-height: 1.6;">
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">意向表锁</a></li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">AUTO_INC表锁</a></li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">表锁的兼容性</a></li>
</ul>
</li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">行锁</a><ul style="margin-top: 0; margin-bottom: 15px; list-style-type: none; line-height: 1.6;">
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">行锁的分类</a></li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">where的执行原理及加锁对象</a></li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">加锁规则</a></li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">行锁的兼容性</a></li>
</ul>
</li>
</ul>
</li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">5. 查看锁</a><ul style="margin-top: 0; margin-bottom: 15px; list-style-type: none; line-height: 1.6;">
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">InnoDB Lock Monitor</a></li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">INFORMATION_SCHEMA 内的表</a></li>
</ul>
</li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">6. 两个简单的死锁示例</a><ul style="margin-top: 0; margin-bottom: 15px; list-style-type: none; line-height: 1.6;">
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">不走索引的DELETE引发的死锁</a></li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">并发INSERT引发的死锁</a></li>
</ul>
</li>
<li style="line-height: 1.6;"><a style="background: transparent; color: #1980e6; text-decoration: none;">7. 参考文档</a></li>
</ul>
</li>
</ul>
</div>
</div>
</div>
</div><div style="line-height: 1.6;">
<h2 style="font-family: inherit; font-weight: bold; line-height: 1.1; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 2.15em; margin: 1.2em 0 .6em 0; text-align: start;">1. 事务并发问题</h2>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;"><p style="margin: 0 0 1.1em; line-height: 1.6;">脏读(<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">Dirty Read</code>): <br/>
A 看到 B 进行中更新的数据,并以此为根据继续执行相关的操作;B 回滚,导致 A 操作的是脏数据。</p></li>
<li style="line-height: 1.6;"><p style="margin: 0 0 1.1em; line-height: 1.6;">不可重复读(<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">Non-repeatable Read</code>): <br/>
A 先查询一次数据,然后 B 更新之并提交,A 再次查询,得到和上一次不同的查询结果。</p></li>
<li style="line-height: 1.6;"><p style="margin: 0 0 1.1em; line-height: 1.6;">幻读(<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">Phantom Read</code>): <br/>
A 查询一批数据,B 插入或删除了某些记录并提交,A 再次查询,发现结果集中出现了上次没有的记录,或者上次有的记录消失了。</p></li>
<li style="line-height: 1.6;"><p style="margin: 0 0 1.1em; line-height: 1.6;">第二类丢失更新 (<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">覆盖丢失</code>): <br/>
A 和 B 更新同一条记录并提交,后提交的数据将覆盖先提交的,通常这是没问题的,但是在某些情况下,如在程序中自增自减、程序中的读-改-全量更新,就会出现并发问题。<em style="line-height: 1.6;">这类问题更像是应用层面的,不属于DB范畴。</em></p></li>
</ol>
</div><div style="line-height: 1.6;">
<h2 style="font-family: inherit; font-weight: bold; line-height: 1.1; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 2.15em; margin: 1.2em 0 .6em 0; text-align: start;">2. 事务隔离级别</h2>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;"><p style="margin: 0 0 1.1em; line-height: 1.6;">read uncommited <br/>
最弱,事务的任何动作对其他事务都是立即可见的。存在脏读、不可重复读、幻读问题(除了回滚丢失,其他的并发问题都有)。</p></li>
<li style="line-height: 1.6;"><p style="margin: 0 0 1.1em; line-height: 1.6;">read commited <br/>
只能读到其他事务已提交的数据,中间状态的数据则看不到,解决了<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">脏读</code>问题。</p></li>
<li style="line-height: 1.6;"><p style="margin: 0 0 1.1em; line-height: 1.6;">repeatable read <strong style="font-weight: bold; line-height: 1.6;">(InnoDB的默认隔离级别)</strong> <br/>
根据标准的SQL规范,该级别解决了<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">不可重复读</code>的问题,保证在一个事务内,对同一条记录的重复读都是一致的。</p>
<blockquote style="padding: 15px 20px; margin: 0 0 1.1em; border-left: 5px solid rgba(102,128,153,0.075); border-left-width: 10px; background-color: rgba(102,128,153,0.05); border-top-right-radius: 5px; border-bottom-right-radius: 5px;">
<p style="margin: 0 0 1.1em; font-size: 1em; font-weight: 300; margin-bottom: 0; line-height: 1.6;">InnoDB 的 Repeatable Read 通过 <em style="line-height: 1.6;">MVCC</em> 和 <em style="line-height: 1.6;">间隙锁</em> 机制额外解决了<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">幻读</code>问题。</p>
</blockquote></li>
<li style="line-height: 1.6;"><p style="margin: 0 0 1.1em; line-height: 1.6;">serial <br/>
最高,所有读写都是串行的。</p></li>
</ol>
<p style="margin: 0 0 1.1em; line-height: 1.6;">InnoDB 对事务隔离级别的实现依赖两个手段:锁、MVCC(多版本控制协议)。MVCC可以认为是对锁机制的优化,让普通select避免加锁,同时还能有事务隔离级别的语义保证。</p>
</div><div style="line-height: 1.6;">
<h2 style="font-family: inherit; font-weight: bold; line-height: 1.1; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 2.15em; margin: 1.2em 0 .6em 0; text-align: start;">3. MVCC</h2>
<p style="margin: 0 0 1.1em; line-height: 1.6;">MVCC,Multi-Version Concurrency Control,为一条记录维护多个不同的snapshot,并记录各snapshot对应的版本号(事务ID),每个事务可以读到的snapshot是受限的,从而隔离其他事务的并发动作。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">MVCC并发控制中,读操作分为两类:快照读 (snapshot read)与当前读 (current read)。前者读取的是记录的snapshot(有可能是历史版本),不用加锁;后者读取的是记录的最新版本,且会加上锁,保证其他事务不会并发修改这条记录。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">快照读:</strong></p>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;">普通的select均为快照读,不用加锁;</li>
</ol>
<p style="margin: 0 0 1.1em; line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">当前读:</strong></p>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;"><code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">select... lock in shared mode</code>: 读锁</li>
<li style="line-height: 1.6;"><code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">select... for update</code>: 写锁</li>
<li style="line-height: 1.6;">DML(insert/delete/update):写锁</li>
</ol>
<p style="margin: 0 0 1.1em; line-height: 1.6;">MVCC 只工作在RC &amp; RR两个隔离级别下,Read Uncommited 直接读数据;Serializable 所有读都是当前读。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">在RR级别下,快照读只能读取本事务开始之前的snapshot,反复读同一条记录,不会看到其他事务对它的更新动作;反复执行同一条查询,不会看到其他事务插入的新记录,也不会丢失其他事务删除的记录(删除并非立刻物理删除)。可以看到,RR级别下,普通的select没有<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">不可重复读</code>和<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">幻读</code>的问题。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">在RC级别下,快照读读取的是记录最新的snapshot,可以看到其他事务已提交的内容。</p>
</div><div style="line-height: 1.6;">
<h2 style="font-family: inherit; font-weight: bold; line-height: 1.1; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 2.15em; margin: 1.2em 0 .6em 0; text-align: start;">4. 锁</h2>
</div><div style="line-height: 1.6;">
<h3 style="font-family: inherit; font-weight: bold; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 1.7em; margin: 1.2em 0 .6em 0; text-align: start; line-height: 1.6;">二阶段加锁协议 (Two-Phase Locking)</h3>
<p style="margin: 0 0 1.1em; line-height: 1.6;">事务中只加锁不释放,事务结束一起释放。</p>
</div><div style="line-height: 1.6;">
<h3 style="font-family: inherit; font-weight: bold; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 1.7em; margin: 1.2em 0 .6em 0; text-align: start; line-height: 1.6;">锁的细分</h3>
<p style="margin: 0 0 1.1em; line-height: 1.6;">锁可以从两个维度上进行细分。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">根据粒度大小(InnoDB 称为 lock type)锁可以细分为<strong style="font-weight: bold; line-height: 1.6;">表锁</strong> 和 <strong style="font-weight: bold; line-height: 1.6;">行锁</strong>。表锁对整个表加锁,影响表内的所有记录,行锁只影响一条记录,粒度更细,并发程度高。行锁根据场景的不同又可以进一步细分(稍后详细介绍)。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;"><a href="http://osxr.org/mysql/source/storage/innobase/include/lock0lock.h#0833" style="background: transparent; color: #1980e6; text-decoration: none;" target="_blank">lock0lock.h</a>:</p>
</div><div style="line-height: 1.6;">
<pre style="word-break: break-word; font-family: 'Source Code Pro',monospace; white-space: pre-wrap; display: block; background-color: rgba(102,128,153,0.05); color: #333; word-wrap: break-word; font-size: .9em; background: #f6f6f6; line-height: 1.6; margin: 0 0 1.1em; padding: 2px; border: 0; border-radius: 5px; text-align: start;" xml:space="preserve"><code style="font-family: 'Source Code Pro',monospace; font-size: inherit; background-color: transparent; white-space: pre-wrap; border-radius: 0; color: #f8f8f2; display: block; background: #23241f; padding: 18px 28px;"><div style="line-height: 1.6;"><div style="line-height: 1.6;"><span style="line-height: 1.6;">#<span style="line-height: 1.6; color: #f92672;">define</span> LOCK_TABLE 16 /* table lock */</span></div><div style="line-height: 1.6;"><span style="line-height: 1.6;">#<span style="line-height: 1.6; color: #f92672;">define</span> LOCK_REC 32 /* record lock */</span></div><br/><div style="line-height: 1.6;"><span style="line-height: 1.6; color: #75715e;">/* Precise modes */</span></div><br/><div style="line-height: 1.6;"><span style="line-height: 1.6; color: #75715e;">/* ... ordinary next-key lock in contrast to LOCK_GAP or LOCK_REC_NOT_GAP*/</span></div><div style="line-height: 1.6;"><span style="line-height: 1.6;">#<span style="line-height: 1.6; color: #f92672;">define</span> LOCK_ORDINARY 0 </span></div><br/><div style="line-height: 1.6;"><span style="line-height: 1.6; color: #75715e;">/* ... the lock holds only on the gap before the record; for instance, an x-lock on the gap does not give permission to modify the record on which the bit is set ... */</span></div><div style="line-height: 1.6;"><span style="line-height: 1.6;">#<span style="line-height: 1.6; color: #f92672;">define</span> LOCK_GAP 512 </span></div><br/><div style="line-height: 1.6;"><span style="line-height: 1.6; color: #75715e;">/* ... the lock is only on the index record and does NOT block inserts to the gap before the index record; this is used in the case when we retrieve a record with a unique key, and is also used in locking plain SELECTs (not part of UPDATE or DELETE) when the user has set the READ COMMITTED isolation level */</span></div><div style="line-height: 1.6;"><span style="line-height: 1.6;">#<span style="line-height: 1.6; color: #f92672;">define</span> LOCK_REC_NOT_GAP 1024 </span></div><br/><div style="line-height: 1.6;"><span style="line-height: 1.6; color: #75715e;">/* this bit is set when we place a waiting gap type record lock request in order to let an insert of an index record to wait until there are no conflicting locks by other transactions on the gap; note that this flag remains set when the waiting lock is granted, or if the lock is inherited to a neighboring record */</span></div><div style="line-height: 1.6;"><span style="line-height: 1.6;">#<span style="line-height: 1.6; color: #f92672;">define</span> LOCK_INSERT_INTENTION 2048 </span></div></div></code></pre>
<p style="margin: 0 0 1.1em; line-height: 1.6;">锁的 mode 分类如下所示:</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;"><a href="http://osxr.org/mysql/source/storage/innobase/include/lock0types.h" style="background: transparent; color: #1980e6; text-decoration: none;" target="_blank">lock0types.h</a>:</p>
</div><div style="line-height: 1.6;">
<pre style="word-break: break-word; font-family: 'Source Code Pro',monospace; white-space: pre-wrap; display: block; background-color: rgba(102,128,153,0.05); color: #333; word-wrap: break-word; font-size: .9em; background: #f6f6f6; line-height: 1.6; margin: 0 0 1.1em; padding: 2px; border: 0; border-radius: 5px; text-align: start;" xml:space="preserve"><code style="font-family: 'Source Code Pro',monospace; font-size: inherit; background-color: transparent; white-space: pre-wrap; border-radius: 0; color: #f8f8f2; display: block; background: #23241f; padding: 18px 28px;"><div style="line-height: 1.6;"><div style="line-height: 1.6;"><span style="line-height: 1.6; color: #75715e;">/* Basic lock modes */</span></div><div style="line-height: 1.6;"><span style="line-height: 1.6; color: #f92672;">enum</span> lock_mode {</div><div style="line-height: 1.6;"> LOCK_IS = <span style="line-height: 1.6; color: #ae81ff;">0</span>, <span style="line-height: 1.6; color: #75715e;">/* intention shared */</span></div><div style="line-height: 1.6;"> LOCK_IX, <span style="line-height: 1.6; color: #75715e;">/* intention exclusive */</span></div><div style="line-height: 1.6;"> LOCK_S, <span style="line-height: 1.6; color: #75715e;">/* shared */</span></div><div style="line-height: 1.6;"> LOCK_X, <span style="line-height: 1.6; color: #75715e;">/* exclusive */</span></div><div style="line-height: 1.6;"> LOCK_AUTO_INC, <span style="line-height: 1.6; color: #75715e;">/* locks the auto-inc counter of a table in an exclusive mode*/</span></div><div style="line-height: 1.6;"> ...</div><div style="line-height: 1.6;">};</div></div></code></pre>
<p style="margin: 0 0 1.1em; line-height: 1.6;">将锁分为读锁和写锁主要是为了提高读的并发,它们的兼容性矩阵:</p>
</div><div style="line-height: 1.6;">
<pre style="word-break: break-word; font-family: 'Source Code Pro',monospace; white-space: pre-wrap; display: block; background-color: rgba(102,128,153,0.05); color: #333; word-wrap: break-word; font-size: .9em; background: #f6f6f6; line-height: 1.6; margin: 0 0 1.1em; padding: 2px; border: 0; border-radius: 5px; text-align: start;" xml:space="preserve"><code style="font-family: 'Source Code Pro',monospace; font-size: inherit; background-color: transparent; white-space: pre-wrap; border-radius: 0; color: #f8f8f2; display: block; background: #23241f; padding: 18px 28px;"><div style="line-height: 1.6;"><div style="line-height: 1.6;"> S X</div><div style="line-height: 1.6;">S + – </div><div style="line-height: 1.6;">X - -</div></div></code></pre>
<p style="margin: 0 0 1.1em; line-height: 1.6;">IX(写意向)、IS(读意向)只会应用在表锁上,方便表锁和行锁之间的冲突检测。<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">LOCK_AUTO_INC</code>是一种特殊的表锁。</p>
</div><div style="line-height: 1.6;">
<h3 style="font-family: inherit; font-weight: bold; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 1.7em; margin: 1.2em 0 .6em 0; text-align: start; line-height: 1.6;">表锁</h3>
<p style="margin: 0 0 1.1em; line-height: 1.6;"><code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">LOCK TABLES t1 READ, t2 WRITE</code> 对表加 S 或 X 锁、<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">ALTER TABLE</code>需要加 X 锁。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">表锁的实现有两个层面,MySQL Server 和 InnoDB 存储引擎,innodb_table_locks 参数为1(默认值)表明当 autocommit 关闭时启用 InnoDB 表锁。此时调用 <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">LOCK TABLES</code>, MySQL Server 和 InnoDB 都会加表锁,不同的是,前者加的锁只有显式调用 <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">UNLOCK TABLES</code> 才会释放,InnoDB 层面的表锁则会在事务提交时自动释放。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;"><code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">LOCK TABLES</code> 搭配 InnoDB 表锁的正确使用姿势:</p>
</div><div style="line-height: 1.6;">
<pre style="word-break: break-word; font-family: 'Source Code Pro',monospace; white-space: pre-wrap; display: block; background-color: rgba(102,128,153,0.05); color: #333; word-wrap: break-word; font-size: .9em; background: #f6f6f6; line-height: 1.6; margin: 0 0 1.1em; padding: 2px; border: 0; border-radius: 5px; text-align: start;" xml:space="preserve"><code style="font-family: 'Source Code Pro',monospace; font-size: inherit; background-color: transparent; white-space: pre-wrap; border-radius: 0; color: #f8f8f2; display: block; background: #23241f; padding: 18px 28px;"><div style="line-height: 1.6;"><div style="line-height: 1.6;">SET autocommit=<span style="line-height: 1.6; color: #ae81ff;">0</span>;</div><div style="line-height: 1.6;">LOCK TABLES t1 WRITE, t2 READ, <span style="line-height: 1.6; color: #f92672;">...</span>;</div><div style="line-height: 1.6;"><span style="line-height: 1.6; color: #f92672;">...</span> do something with tables t1 and t2 here <span style="line-height: 1.6; color: #f92672;">...</span></div><div style="line-height: 1.6;">COMMIT;</div><div style="line-height: 1.6;">UNLOCK TABLES;</div></div></code></pre>
<p style="margin: 0 0 1.1em; line-height: 1.6;">Manual:<a href="https://www.evernote.com/OutboundRedirect.action?dest=https%3A%2F%2Fdev.mysql.com%2Fdoc%2Frefman%2F5.6%2Fen%2Flock-tables-and-transactions.html" style="background: transparent; color: #1980e6; text-decoration: none;" target="_blank">Interaction of Table Locking and Transactions</a></p>
</div><div style="line-height: 1.6;">
<h4 style="font-family: inherit; font-weight: bold; color: inherit; margin-top: 10.5px; margin-bottom: 10.5px; font-size: 1.25em; margin: 1.2em 0 .6em 0; text-align: start; line-height: 1.6;">意向表锁</h4>
<p style="margin: 0 0 1.1em; line-height: 1.6;">表锁锁定了整张表,因此表锁和行锁之间也会冲突,为了方便检测表锁和行锁的冲突引入了<strong style="font-weight: bold; line-height: 1.6;">意向表锁</strong>。</p>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;">意向锁分为意向读锁(IS)和意向写锁(IX)。</li>
<li style="line-height: 1.6;">意向锁是表级锁,但表示事务试图读或写某一行记录,而不是整个表。所以意向锁之间不会产生冲突,真正的冲突在加行锁时检查。</li>
<li style="line-height: 1.6;">在给一行记录加锁前,首先要给该表加意向锁。也就是要同时加表意向锁和行锁。</li>
</ol>
</div><div style="line-height: 1.6;">
<h4 style="font-family: inherit; font-weight: bold; color: inherit; margin-top: 10.5px; margin-bottom: 10.5px; font-size: 1.25em; margin: 1.2em 0 .6em 0; text-align: start; line-height: 1.6;">AUTO_INC表锁</h4>
<p style="margin: 0 0 1.1em; line-height: 1.6;">为一个AUTO_INCREMENT列生成自增值前,必须先为该表加 AUTO_INC 表锁。AUTO_INC 表锁有些特别的地方:</p>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;">每个表最多只能有一个自增锁</li>
<li style="line-height: 1.6;">为了提高并发插入的性能,<strong style="font-weight: bold; line-height: 1.6;">自增锁不遵循二阶段锁协议</strong>,加锁释放锁不跟事务而跟语句走,insert开始时获取,结束时释放</li>
<li style="line-height: 1.6;">自增值只要分配了就会+1,不管事务是否提交了都不会撤销,所以可能出现空洞。</li>
</ol>
<p style="margin: 0 0 1.1em; line-height: 1.6;">从5.1.22开始,MySQL 提供了一种可选的轻量级锁(mutex)机制代替AUTO_INC表锁,参数 innodb_autoinc_lock_mode 控制分配自增值时的并发策略。介绍该参数之前先引入几个insert相关的概念:</p>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">Simple inserts</strong>:通过分析insert语句可以确定插入数量的insert语句,如<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">INSERT, INSERT … VALUES(1,2),VALUES(3,4)</code></li>
<li style="line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">Bulk inserts</strong>:通过分析insert语句无法知道插入数量的insert语句,<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">INSERT … SELECT, REPLACE … SELECT, LOAD DATA</code></li>
<li style="line-height: 1.6;"><p style="margin: 0 0 1.1em; line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">Mixed-mode inserts</strong>:不确定是否需要分配auto_increment id,一般是下面两种情况</p>
<pre style="word-break: break-word; font-family: 'Source Code Pro',monospace; white-space: pre-wrap; display: block; background-color: rgba(102,128,153,0.05); color: #333; word-wrap: break-word; font-size: .9em; background: #f6f6f6; line-height: 1.6; margin: 0 0 1.1em; padding: 2px; border: 0; border-radius: 5px; text-align: start;" xml:space="preserve"><code style="font-family: 'Source Code Pro',monospace; font-size: inherit; background-color: transparent; white-space: pre-wrap; border-radius: 0; color: #f8f8f2; display: block; background: #23241f; padding: 18px 28px;"><div style="line-height: 1.6;"><div style="line-height: 1.6;"><span style="line-height: 1.6;"><span style="line-height: 1.6; color: #f92672;">INSERT</span> <span style="line-height: 1.6; color: #f92672;">INTO</span> t1 (c1,c2) <span style="line-height: 1.6; color: #f92672;">VALUES</span> (<span style="line-height: 1.6; color: #ae81ff;">1</span>,<span style="line-height: 1.6; color: #e6db74;">'a'</span>), (<span style="line-height: 1.6; color: #ae81ff;">NULL</span>,<span style="line-height: 1.6; color: #e6db74;">'b'</span>), (<span style="line-height: 1.6; color: #ae81ff;">5</span>,<span style="line-height: 1.6; color: #e6db74;">'c'</span>), (<span style="line-height: 1.6; color: #ae81ff;">NULL</span>,<span style="line-height: 1.6; color: #e6db74;">'d'</span>)</span></div><div style="line-height: 1.6;"><span style="line-height: 1.6; color: #75715e;">-- 有些指定了id,有些没</span></div><div style="line-height: 1.6;"><span style="line-height: 1.6; color: #f92672;">INSERT</span> … <span style="line-height: 1.6; color: #f92672;">ON</span> DUPLICATE <span style="line-height: 1.6; color: #f92672;">KEY</span> <span style="line-height: 1.6; color: #f92672;">UPDATE</span></div></div></code></pre></li>
</ol>
<p style="margin: 0 0 1.1em; line-height: 1.6;">参数innodb_autoinc_lock_mode可以取下列值:</p>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;"><p style="margin: 0 0 1.1em; line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">innodb_autoinc_lock_mode=0 (traditional lock mode)</strong> <br/>
使用传统的 AUTO_INC 表锁,并发性比较差;</p></li>
<li style="line-height: 1.6;"><p style="margin: 0 0 1.1em; line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">innodb_autoinc_lock_mode=1 (consecutive/连续 lock mode)默认值</strong> <br/>
折中方式,bulk 不能确定插入数用表锁,simple、mix用mutex,只锁住预分配自增ID的过程,不锁整张表。Mixed-mode inserts 会直接分析语句,获得最坏情况下需要插入的数量,一次性分配足够的auto_increment id,缺点是会分配过多的id,导致“浪费”和空洞。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">这种模式既平衡了并发性,又能保证<strong style="font-weight: bold; line-height: 1.6;">同一条insert语句分配的自增id是连续的</strong>。</p></li>
<li style="line-height: 1.6;"><p style="margin: 0 0 1.1em; line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">innodb_autoinc_lock_mode=2 (interleaved/交叉 lock mode)</strong> <br/>
全部都用mutex,并发性能最高,id一个一个分配,不会预分配。缺点是不能保证同一条insert语句内的id是连续的,但是在replication中,当binlog_format为statement-based时(基于语句的复制)存在问题,因为是来一个分配一个,同一条insert语句内获得的自增id可能不连续,主从数据集会出现数据不一致。</p></li>
</ol>
</div><div style="line-height: 1.6;">
<h4 style="font-family: inherit; font-weight: bold; color: inherit; margin-top: 10.5px; margin-bottom: 10.5px; font-size: 1.25em; margin: 1.2em 0 .6em 0; text-align: start; line-height: 1.6;">表锁的兼容性</h4>
<p style="margin: 0 0 1.1em; line-height: 1.6;">以上表锁的兼容性矩阵如下:(+兼容,-不兼容)</p>
<table style="border-collapse: collapse; border-spacing: 0; margin-bottom: 20px; line-height: 1.6;">
<thead style="line-height: 1.6;">
<tr style="line-height: 1.6;">
<th align="right" style="font-weight: bold; vertical-align: bottom; padding: .5em; border-top: 0; border: 1px solid #ddd; line-height: 1.6;">.</th>
<th style="font-weight: bold; vertical-align: bottom; padding: .5em; border-top: 0; border: 1px solid #ddd; line-height: 1.6;">IS</th>
<th style="font-weight: bold; vertical-align: bottom; padding: .5em; border-top: 0; border: 1px solid #ddd; line-height: 1.6;">IX</th>
<th style="font-weight: bold; vertical-align: bottom; padding: .5em; border-top: 0; border: 1px solid #ddd; line-height: 1.6;">S</th>
<th style="font-weight: bold; vertical-align: bottom; padding: .5em; border-top: 0; border: 1px solid #ddd; line-height: 1.6;">X</th>
<th style="font-weight: bold; vertical-align: bottom; padding: .5em; border-top: 0; border: 1px solid #ddd; line-height: 1.6;">AI</th>
</tr>
</thead>
<tbody style="line-height: 1.6;"><tr style="line-height: 1.6;">
<td align="right" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">IS</td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">+</td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">+</td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">+</td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"></td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">+</td>
</tr>
<tr style="line-height: 1.6;">
<td align="right" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">IX</td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">+</td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">+</td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"></td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"></td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">+</td>
</tr>
<tr style="line-height: 1.6;">
<td align="right" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">S</td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">+</td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"></td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">+</td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"></td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"></td>
</tr>
<tr style="line-height: 1.6;">
<td align="right" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">X</td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"></td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"></td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"></td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"></td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"></td>
</tr>
<tr style="line-height: 1.6;">
<td align="right" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">AI</td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">+</td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">+</td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"></td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"></td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"></td>
</tr>
</tbody></table>
<p style="margin: 0 0 1.1em; line-height: 1.6;">意向表锁只会阻塞X/S表锁,不会阻塞意向表锁和AUTO_INC表锁;</p>
</div><div style="line-height: 1.6;">
<h3 style="font-family: inherit; font-weight: bold; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 1.7em; margin: 1.2em 0 .6em 0; text-align: start; line-height: 1.6;">行锁</h3>
</div><div style="line-height: 1.6;">
<h4 style="font-family: inherit; font-weight: bold; color: inherit; margin-top: 10.5px; margin-bottom: 10.5px; font-size: 1.25em; margin: 1.2em 0 .6em 0; text-align: start; line-height: 1.6;">行锁的分类</h4>
<p style="margin: 0 0 1.1em; line-height: 1.6;">行锁从mode上分为X、S,type上进一步细分为以下类型:</p>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">LOCK_GAP</strong>:GAP锁,锁两个记录之间的GAP,防止记录插入;</li>
<li style="line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">LOCK_ORDINARY</strong>:官方文档中称为 “Next-Key Lock” ,锁一条记录及其<em style="line-height: 1.6;">之前</em>的间隙,这是RR级别用的最多的锁,从名字也能看出来;</li>
<li style="line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">LOCK_REC_NOT_GAP</strong>:只锁记录;</li>
<li style="line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">LOCK_INSERT_INTENSION</strong>:插入意向GAP锁,插入记录时使用,是LOCK_GAP的一种特例。</li>
</ol>
<p style="margin: 0 0 1.1em; line-height: 1.6;">RC级别只有记录锁,没有 Next-Key Lock 和 GAP锁,因此存在幻读现象。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">行锁是加在记录上的锁,InnoDB中的记录是以B+树索引的方式组织在一起的,InnoDB的行锁实际是 index record lock,即对B+索引叶子节点的锁。索引可能有多个,因此操作一行数据时,有可能会加多个行锁在不同的B+树上。</p>
</div><div style="line-height: 1.6;">
<h4 style="font-family: inherit; font-weight: bold; color: inherit; margin-top: 10.5px; margin-bottom: 10.5px; font-size: 1.25em; margin: 1.2em 0 .6em 0; text-align: start; line-height: 1.6;">where的执行原理及加锁对象</h4>
<p style="margin: 0 0 1.1em; line-height: 1.6;">分析不同SQL语句的加锁情况前,有必要先介绍下 SQL 中 where 条件是怎么解析和执行的。假设 where 走某个Secondary索引A,where 中所有的条件可以分为三类:</p>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;"><p style="margin: 0 0 1.1em; line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">index key</strong>:用于确定索引<strong style="font-weight: bold; line-height: 1.6;">扫描</strong>的起始位置和结束位置,因为是范围,所以分 index first key 和 index last key。查询时,先通过 index first key 条件在B+树上做一次<strong style="font-weight: bold; line-height: 1.6;">搜索</strong>确定扫描开始位置(从B+树的根节点一层层往下找),从该处开始沿着叶子节点组成的链表扫描,碰到的每个节点都要与 index last key 比对,判断是否超出扫描范围。</p>
<blockquote style="padding: 15px 20px; margin: 0 0 1.1em; border-left: 5px solid rgba(102,128,153,0.075); border-left-width: 10px; background-color: rgba(102,128,153,0.05); border-top-right-radius: 5px; border-bottom-right-radius: 5px;">
<p style="margin: 0 0 1.1em; font-size: 1em; font-weight: 300; margin-bottom: 0; line-height: 1.6;">注意<strong style="font-weight: bold; line-height: 1.6;">扫描</strong> / <strong style="font-weight: bold; line-height: 1.6;">搜索</strong>的区别:搜索是从根节点到叶子节点的定位过程,扫描针对的是索引叶子节点组成的链表。</p>
</blockquote></li>
<li style="line-height: 1.6;"><p style="margin: 0 0 1.1em; line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">index filter</strong>:是索引A的字段,但无法应用在搜索过程中,只能在扫描索引时对结果集进行过滤,不需额外查询聚集索引;</p></li>
<li style="line-height: 1.6;"><p style="margin: 0 0 1.1em; line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">table filter</strong>:不是索引A的字段,innodb 对根据前面两个条件扫描得到的结果集,去聚集索引上读了完整数据后返回给MySQL Server,后者再用table filter过滤。</p></li>
</ol>
<p style="margin: 0 0 1.1em; line-height: 1.6;">对于除 insert 以外的<strong style="font-weight: bold; line-height: 1.6;">当前读</strong>,如 <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">SELECT…[FOR UPDATE | LOCK IN SHARE MODE]</code>、<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">UPDATE</code>、<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">DELETE</code>,加锁的对象是<strong style="font-weight: bold; line-height: 1.6;">索引扫描后,将要返回给MySQL Server进一步过滤的那些记录</strong>。</p>
<blockquote style="padding: 15px 20px; margin: 0 0 1.1em; border-left: 5px solid rgba(102,128,153,0.075); border-left-width: 10px; background-color: rgba(102,128,153,0.05); border-top-right-radius: 5px; border-bottom-right-radius: 5px;">
<p style="margin: 0 0 1.1em; font-size: 1em; font-weight: 300; margin-bottom: 0; line-height: 1.6;">在MySQL 5.6之前,并不区分Index Filter与Table Filter,统统将Index First Key与Index Last Key范围内的索引记录,回表读取完整记录,然后返回给MySQL Server层进行过滤。而在MySQL 5.6之后,Index Filter与Table Filter分离,Index Filter下降到InnoDB的索引层面进行过滤,减少了回表与返回MySQL Server层的记录交互开销,提高了SQL的执行效率。</p>
</blockquote>
</div><div style="line-height: 1.6;">
<h4 style="font-family: inherit; font-weight: bold; color: inherit; margin-top: 10.5px; margin-bottom: 10.5px; font-size: 1.25em; margin: 1.2em 0 .6em 0; text-align: start; line-height: 1.6;">加锁规则</h4>
<p style="margin: 0 0 1.1em; line-height: 1.6;">在RR级别下,有如下加锁规则:</p>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;"><p style="margin: 0 0 1.1em; line-height: 1.6;">用于搜索和扫描的(where条件走的)索引,加 Next-Key Lock。对于扫描得到的最后一个记录,还要对它和下一条记录之间的空隙加 GAP Lock。<strong style="font-weight: bold; line-height: 1.6;">记录间的间隙(包括第一个记录之前、最后一个记录之后的间隙)都加了锁,新的记录无法插入到这些位置,保证了不会出现幻读</strong>;</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">如果索引是唯一索引或主索引,且SQL是个等值查询,由于有唯一性的保证,可不用锁间隙,加Record Lock 而非 Next-Key Lock。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">如果没有查询到记录,定位到的GAP依然会被锁上,这样才不会出现幻读。</p></li>
<li style="line-height: 1.6;"><p style="margin: 0 0 1.1em; line-height: 1.6;">where索引扫描的结果集,在其他索引上对应的记录,加Record Lock;</p></li>
<li style="line-height: 1.6;"><p style="margin: 0 0 1.1em; line-height: 1.6;">不同索引的加锁顺序:where索引 –&gt; 主索引 –&gt; 其他二级索引;</p></li>
<li style="line-height: 1.6;"><p style="margin: 0 0 1.1em; line-height: 1.6;">如果没法走索引而走全表扫描,主索引的全部记录都会加 Next-Key Lock,加锁的顺序不定。此时该表除了不加锁的快照读,其他所有需要加锁的SQL如插入、更新、删除均不可执行。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">MySQL Server层对这种情况会做优化,不符合条件的记录会立刻释放锁,但这种优化违背了二阶段锁协议,而且InnoDB加锁的动作不会省略。</p></li>
</ol>
<p style="margin: 0 0 1.1em; line-height: 1.6;"><code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">INSERT</code> 的加锁:</p>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;"><p style="margin: 0 0 1.1em; line-height: 1.6;">插入之前,对插入的间隙加插入意向GAP锁 ;</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">插入意向GAP锁表明将向某个间隙插入记录,如果该间隙已被加上了GAP Lock或Next-Key Lock,则加锁失败。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">不同事务加的插入意向GAP锁互相兼容,否则就无法并发insert了。</p></li>
<li style="line-height: 1.6;"><p style="margin: 0 0 1.1em; line-height: 1.6;">插入成功后,对插入的这条记录加X Record Lock;</p></li>
<li style="line-height: 1.6;"><p style="margin: 0 0 1.1em; line-height: 1.6;">如果违反唯一性约束导致插入失败,则对记录加S Next-Key Lock。这一点在并发插入时可能导致死锁。</p></li>
</ol>
</div><div style="line-height: 1.6;">
<h4 style="font-family: inherit; font-weight: bold; color: inherit; margin-top: 10.5px; margin-bottom: 10.5px; font-size: 1.25em; margin: 1.2em 0 .6em 0; text-align: start; line-height: 1.6;">行锁的兼容性</h4>
<p style="margin: 0 0 1.1em; line-height: 1.6;">S锁和S锁完全兼容,兼容性检测只发生在S和X、X和S之间。行锁的兼容性矩阵如下(由<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">lock0lock.c:lock_rec_has_to_wait()</code> 函数推出):</p>
<table style="border-collapse: collapse; border-spacing: 0; margin-bottom: 20px; line-height: 1.6;">
<thead style="line-height: 1.6;">
<tr style="line-height: 1.6;">
<th align="center" style="font-weight: bold; vertical-align: bottom; padding: .5em; border-top: 0; border: 1px solid #ddd; line-height: 1.6;">.</th>
<th align="center" style="font-weight: bold; vertical-align: bottom; padding: .5em; border-top: 0; border: 1px solid #ddd; line-height: 1.6;">GAP</th>
<th align="center" style="font-weight: bold; vertical-align: bottom; padding: .5em; border-top: 0; border: 1px solid #ddd; line-height: 1.6;">II GAP</th>
<th align="center" style="font-weight: bold; vertical-align: bottom; padding: .5em; border-top: 0; border: 1px solid #ddd; line-height: 1.6;">RECORD</th>
<th align="center" style="font-weight: bold; vertical-align: bottom; padding: .5em; border-top: 0; border: 1px solid #ddd; line-height: 1.6;">NEXT-KEY</th>
</tr>
</thead>
<tbody style="line-height: 1.6;"><tr style="line-height: 1.6;">
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">GAP</td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">+</strong></td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">+</strong></td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">+</strong></td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">+</strong></td>
</tr>
<tr style="line-height: 1.6;">
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">II GAP</td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"></td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">+</strong></td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">+</strong></td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"></td>
</tr>
<tr style="line-height: 1.6;">
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">RECORD</td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">+</strong></td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">+</strong></td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"></td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"></td>
</tr>
<tr style="line-height: 1.6;">
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">NEXT-KEY</td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">+</strong></td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">+</strong></td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"></td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"></td>
</tr>
</tbody></table>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;">第一行,GAP锁不需要等待其他任何行锁(why?);</li>
<li style="line-height: 1.6;">第二行,GAP锁、Next-Key锁会阻止 Insert;插入意向锁互相是兼容的,即并发插入是允许的;</li>
<li style="line-height: 1.6;">第三行,RECORD锁和RECORD锁、Next-Key锁冲突;</li>
<li style="line-height: 1.6;">第二列,已有的插入意向GAP锁不会阻止任何锁。</li>
</ol>
</div><div style="line-height: 1.6;">
<h2 style="font-family: inherit; font-weight: bold; line-height: 1.1; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 2.15em; margin: 1.2em 0 .6em 0; text-align: start;">5. 查看锁</h2>
</div><div style="line-height: 1.6;">
<h3 style="font-family: inherit; font-weight: bold; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 1.7em; margin: 1.2em 0 .6em 0; text-align: start; line-height: 1.6;">InnoDB Lock Monitor</h3>
<p style="margin: 0 0 1.1em; line-height: 1.6;">InnoDB 提供了InnoDB Monitor,可以显示InnoDB的内部状态,打开该项特性后,每隔15秒MySQL就会把<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">SHOW ENGINE INNODB STATUS</code>命令的输出重定向到标准错误输出流,如果设置了innodb-status-file=1 ,还会将上述命令的输出额外写到一个名为<em style="line-height: 1.6;">innodb_status.pid</em>的文件中。此外,如果开启了InnoDB Lock Monitor,还会打印额外的锁信息。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">MySQL 5.6.16 以后使用以下两个命令打开 InnoDB Standard Monitor 和 InnoDB Lock Monitor:</p>
</div><div style="line-height: 1.6;">
<pre style="word-break: break-word; font-family: 'Source Code Pro',monospace; white-space: pre-wrap; display: block; background-color: rgba(102,128,153,0.05); color: #333; word-wrap: break-word; font-size: .9em; background: #f6f6f6; line-height: 1.6; margin: 0 0 1.1em; padding: 2px; border: 0; border-radius: 5px; text-align: start;" xml:space="preserve"><code style="font-family: 'Source Code Pro',monospace; font-size: inherit; background-color: transparent; white-space: pre-wrap; border-radius: 0; color: #f8f8f2; display: block; background: #23241f; padding: 18px 28px;"><div style="line-height: 1.6;"><div style="line-height: 1.6;"><span style="line-height: 1.6; color: #e6db74;">set</span> global innodb_status_output=ON;</div><div style="line-height: 1.6;"><span style="line-height: 1.6; color: #e6db74;">set</span> global innodb_status_output_locks=ON;</div></div></code></pre>
<p style="margin: 0 0 1.1em; line-height: 1.6;"><code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">SHOW ENGINE INNODB STATUS</code> 的输出示例:</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">———— <br/>
TRANSACTIONS <br/>
———— <br/>
Trx id counter 169246 <br/>
Purge done for trx’s n:o &lt; 169198 undo n:o &lt; 0 state: running but idle <br/>
History list length 802 <br/>
LIST OF TRANSACTIONS FOR EACH SESSION:</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">事务1</strong>: <br/>
—TRANSACTION 169245, ACTIVE 4 sec inserting <br/>
mysql tables in use 1, locked 1 <br/>
<strong style="font-weight: bold; line-height: 1.6;">LOCK WAIT 2 lock</strong> struct(s), heap size 360, 1 row lock(s) <em style="line-height: 1.6;">==== 事务在等待锁,涉及两个锁,其中1个是行锁</em> <br/>
MySQL thread id 699, OS thread handle 0x7fd4ad4e3700, query id 10304 localhost root update <br/>
<strong style="font-weight: bold; line-height: 1.6;">insert into t2 values(3,3)</strong> <em style="line-height: 1.6;">==== 正在执行的SQL</em> <br/>
——- TRX HAS BEEN WAITING 4 SEC FOR THIS LOCK TO BE GRANTED: <br/>
RECORD LOCKS space id 8 page no 3 n bits 72 index <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">PRIMARY</code> of table <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">test</code>.<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">t2</code> trx id 169245 lock_mode X locks gap before rec insert intention waiting <br/>
—————— <br/>
<strong style="font-weight: bold; line-height: 1.6;">TABLE LOCK</strong> table <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">test</code>.<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">t2</code> trx id 169245 <strong style="font-weight: bold; line-height: 1.6;">lock mode IX</strong> <em style="line-height: 1.6;">==== IX插入意向表锁</em> <br/>
<strong style="font-weight: bold; line-height: 1.6;">RECORD LOCKS</strong> space id 8 page no 3 n bits 72 index <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">PRIMARY</code> of table <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">test</code>.<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">t2</code> trx id 169245 <strong style="font-weight: bold; line-height: 1.6;">lock_mode X locks gap before rec insert intention</strong> waiting <em style="line-height: 1.6;">====在等待X插入意向GAP锁</em></p>
<p style="margin: 0 0 1.1em; line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">事务2</strong>: <br/>
—TRANSACTION 169244, ACTIVE 11 sec <br/>
<strong style="font-weight: bold; line-height: 1.6;">2 lock struct(s)</strong>, heap size 360, 1 row lock(s) <br/>
MySQL thread id 698, OS thread handle 0x7fd4a3c54700, query id 10305 localhost root init <br/>
show engine INNODB status <br/>
<strong style="font-weight: bold; line-height: 1.6;">TABLE LOCK table</strong> <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">test</code>.<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">t2</code> trx id 169244 <strong style="font-weight: bold; line-height: 1.6;">lock mode IX</strong> <br/>
<strong style="font-weight: bold; line-height: 1.6;">RECORD LOCKS</strong> space id 8 page no 3 n bits 72 index <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">PRIMARY</code> of table <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">test</code>.<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">t2</code> trx id 169244 <strong style="font-weight: bold; line-height: 1.6;">lock_mode X locks gap before rec</strong> <em style="line-height: 1.6;">====GAP锁</em></p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">各种锁在<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">show engine InnoDB status</code>中的表现:</p>
<table style="border-collapse: collapse; border-spacing: 0; margin-bottom: 20px; line-height: 1.6;">
<thead style="line-height: 1.6;">
<tr style="line-height: 1.6;">
<th style="font-weight: bold; vertical-align: bottom; padding: .5em; border-top: 0; border: 1px solid #ddd; line-height: 1.6;">锁类型</th>
<th style="font-weight: bold; vertical-align: bottom; padding: .5em; border-top: 0; border: 1px solid #ddd; line-height: 1.6;">输出</th>
</tr>
</thead>
<tbody style="line-height: 1.6;"><tr style="line-height: 1.6;">
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">LOCK_TABLE</td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">TABLE LOCK table xxx lock mode [S|X|IS|IX]</td>
</tr>
<tr style="line-height: 1.6;">
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">LOCK_REC_NOT_GAP</td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">RECORD LOCKS … lock_mode [X|S] locks rec but not gap</td>
</tr>
<tr style="line-height: 1.6;">
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">LOCK_ORNIDARY</td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">RECORD LOCKS … lock_mode [X|S]</td>
</tr>
<tr style="line-height: 1.6;">
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">LOCK_GAP</td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">RECORD LOCKS … lock_mode [X|S] locks gap before rec</td>
</tr>
<tr style="line-height: 1.6;">
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">LOCK_INSERT_INTENTION</td>
<td style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">RECORD LOCKS … lock_mode X insert intention</td>
</tr>
</tbody></table>
</div><div style="line-height: 1.6;">
<h3 style="font-family: inherit; font-weight: bold; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 1.7em; margin: 1.2em 0 .6em 0; text-align: start; line-height: 1.6;">INFORMATION_SCHEMA 内的表</h3>
<p style="margin: 0 0 1.1em; line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">INNODB_TRX</strong>: <br/>
当前正在执行的事务详情</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">INNODB_LOCKS</strong>: <br/>
每个引起阻塞的锁两个记录。1.哪个事务持有;2.哪个事务请求。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">示例:</p>
</div><div style="line-height: 1.6;">
<pre style="word-break: break-word; font-family: 'Source Code Pro',monospace; white-space: pre-wrap; display: block; background-color: rgba(102,128,153,0.05); color: #333; word-wrap: break-word; font-size: .9em; background: #f6f6f6; line-height: 1.6; margin: 0 0 1.1em; padding: 2px; border: 0; border-radius: 5px; text-align: start;" xml:space="preserve"><code style="font-family: 'Source Code Pro',monospace; font-size: inherit; background-color: transparent; white-space: pre-wrap; border-radius: 0; color: #f8f8f2; display: block; background: #23241f; padding: 18px 28px;"><div style="line-height: 1.6;"><div style="line-height: 1.6;">*************************** <span style="line-height: 1.6; color: #ae81ff;">1.</span> row ***************************</div><div style="line-height: 1.6;"> lock_id: <span style="line-height: 1.6; color: #ae81ff;">169245</span>:<span style="line-height: 1.6; color: #ae81ff;">8</span>:<span style="line-height: 1.6; color: #ae81ff;">3</span>:<span style="line-height: 1.6; color: #ae81ff;">3</span></div><div style="line-height: 1.6;">lock_trx_id: <span style="line-height: 1.6; color: #ae81ff;">169245</span></div><div style="line-height: 1.6;"> lock_mode: X,GAP <span style="line-height: 1.6; color: #75715e;">// -- row-&gt;lock_mode = lock_get_mode_str(lock)</span></div><div style="line-height: 1.6;"> lock_type: RECORD <span style="line-height: 1.6; color: #75715e;">// -- row-&gt;lock_type = lock_get_type_str(lock)</span></div><div style="line-height: 1.6;"> lock_table: test.t2 </div><div style="line-height: 1.6;"> lock_index: PRIMARY</div><div style="line-height: 1.6;"> lock_space: <span style="line-height: 1.6; color: #ae81ff;">8</span></div><div style="line-height: 1.6;"> lock_page: <span style="line-height: 1.6; color: #ae81ff;">3</span></div><div style="line-height: 1.6;"> lock_rec: <span style="line-height: 1.6; color: #ae81ff;">3</span></div><div style="line-height: 1.6;"> lock_data: <span style="line-height: 1.6; color: #ae81ff;">4</span></div><div style="line-height: 1.6;">*************************** <span style="line-height: 1.6; color: #ae81ff;">2.</span> row ***************************</div><div style="line-height: 1.6;"> lock_id: <span style="line-height: 1.6; color: #ae81ff;">169244</span>:<span style="line-height: 1.6; color: #ae81ff;">8</span>:<span style="line-height: 1.6; color: #ae81ff;">3</span>:<span style="line-height: 1.6; color: #ae81ff;">3</span></div><div style="line-height: 1.6;">lock_trx_id: <span style="line-height: 1.6; color: #ae81ff;">169244</span></div><div style="line-height: 1.6;"> lock_mode: X,GAP</div><div style="line-height: 1.6;"> lock_type: RECORD</div><div style="line-height: 1.6;"> lock_table: test.t2</div><div style="line-height: 1.6;"> lock_index: PRIMARY</div><div style="line-height: 1.6;"> lock_space: <span style="line-height: 1.6; color: #ae81ff;">8</span></div><div style="line-height: 1.6;"> lock_page: <span style="line-height: 1.6; color: #ae81ff;">3</span></div><div style="line-height: 1.6;"> lock_rec: <span style="line-height: 1.6; color: #ae81ff;">3</span></div><div style="line-height: 1.6;"> lock_data: <span style="line-height: 1.6; color: #ae81ff;">4</span></div></div></code></pre>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">lock_mode: [X|S|IX|IS], [GAP]</strong>,只有LOCK_GAP才会显示GAP,见 lock_get_mode_str()。</li>
<li style="line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">lock_type: [Record|Table]</strong>,只能区分表锁、行锁,行锁的细分模式无法识别。</li>
</ol>
<p style="margin: 0 0 1.1em; line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">INNODB_LOCK_WAITS</strong> <br/>
每个被锁阻塞的事务一个记录。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">示例:</p>
</div><div style="line-height: 1.6;">
<pre style="word-break: break-word; font-family: 'Source Code Pro',monospace; white-space: pre-wrap; display: block; background-color: rgba(102,128,153,0.05); color: #333; word-wrap: break-word; font-size: .9em; background: #f6f6f6; line-height: 1.6; margin: 0 0 1.1em; padding: 2px; border: 0; border-radius: 5px; text-align: start;" xml:space="preserve"><code style="font-family: 'Source Code Pro',monospace; font-size: inherit; background-color: transparent; white-space: pre-wrap; border-radius: 0; color: #f8f8f2; display: block; background: #23241f; padding: 18px 28px;"><div style="line-height: 1.6;"><div style="line-height: 1.6;">*************************** <span style="line-height: 1.6; color: #ae81ff;">1.</span> row ***************************</div><div style="line-height: 1.6;">requesting_trx_id: <span style="line-height: 1.6; color: #ae81ff;">169245</span> <span style="line-height: 1.6; color: #75715e;">// 请求锁的事务</span></div><div style="line-height: 1.6;">requested_lock_id: <span style="line-height: 1.6; color: #ae81ff;">169245</span>:<span style="line-height: 1.6; color: #ae81ff;">8</span>:<span style="line-height: 1.6; color: #ae81ff;">3</span>:<span style="line-height: 1.6; color: #ae81ff;">3</span> <span style="line-height: 1.6; color: #75715e;">// 请求的锁ID</span></div><div style="line-height: 1.6;"> blocking_trx_id: <span style="line-height: 1.6; color: #ae81ff;">169244</span> <span style="line-height: 1.6; color: #75715e;">// 持有锁的事务</span></div><div style="line-height: 1.6;"> blocking_lock_id: <span style="line-height: 1.6; color: #ae81ff;">169244</span>:<span style="line-height: 1.6; color: #ae81ff;">8</span>:<span style="line-height: 1.6; color: #ae81ff;">3</span>:<span style="line-height: 1.6; color: #ae81ff;">3</span> <span style="line-height: 1.6; color: #75715e;">// 导致阻塞的锁ID,和请求的锁ID是同一个锁,只是事务前缀不一样</span></div><div style="line-height: 1.6;"><span style="line-height: 1.6; color: #ae81ff;">1</span> <span style="line-height: 1.6; color: #f92672;">row in <span style="line-height: 1.6; color: #a6e22e;">set</span> <span style="line-height: 1.6; color: #f8f8f2;">(0.00 sec)</span></span></div></div></code></pre>
<p style="margin: 0 0 1.1em; line-height: 1.6;">用一个神奇的SQL把这些表join起来分析……</p>
</div><div style="line-height: 1.6;">
<pre style="word-break: break-word; font-family: 'Source Code Pro',monospace; white-space: pre-wrap; display: block; background-color: rgba(102,128,153,0.05); color: #333; word-wrap: break-word; font-size: .9em; background: #f6f6f6; line-height: 1.6; margin: 0 0 1.1em; padding: 2px; border: 0; border-radius: 5px; text-align: start;" xml:space="preserve"><code style="font-family: 'Source Code Pro',monospace; font-size: inherit; background-color: transparent; white-space: pre-wrap; border-radius: 0; color: #f8f8f2; display: block; background: #23241f; padding: 18px 28px;"><div style="line-height: 1.6;"><div style="line-height: 1.6;"><span style="line-height: 1.6;"><span style="line-height: 1.6; color: #f92672;">SELECT</span></span></div><div style="line-height: 1.6;"> r.trx_id waiting_trx_id,</div><div style="line-height: 1.6;"> r.trx_mysql_thread_id waiting_thread,</div><div style="line-height: 1.6;"> <span style="line-height: 1.6; color: #f92672;">left</span>(r.trx_query,<span style="line-height: 1.6; color: #ae81ff;">20</span>) waiting_query,</div><div style="line-height: 1.6;"> <span style="line-height: 1.6; color: #f92672;">concat</span>(<span style="line-height: 1.6; color: #f92672;">concat</span>(lw.lock_type,<span style="line-height: 1.6; color: #e6db74;">' '</span>),</div><div style="line-height: 1.6;"> lw.lock_mode) waiting_for_lock,</div><div style="line-height: 1.6;"> b.trx_id blocking_trx_id,</div><div style="line-height: 1.6;"> b.trx_mysql_thread_id blocking_thread,</div><div style="line-height: 1.6;"> <span style="line-height: 1.6; color: #f92672;">left</span>(b.trx_query,<span style="line-height: 1.6; color: #ae81ff;">20</span>) blocking_query,</div><div style="line-height: 1.6;"> <span style="line-height: 1.6; color: #f92672;">concat</span>(<span style="line-height: 1.6; color: #f92672;">concat</span>(lb.lock_type,<span style="line-height: 1.6; color: #e6db74;">' '</span>),</div><div style="line-height: 1.6;"> lb.lock_mode) blocking_lock </div><div style="line-height: 1.6;"><span style="line-height: 1.6; color: #f92672;">FROM</span></div><div style="line-height: 1.6;"> information_schema.innodb_lock_waits w </div><div style="line-height: 1.6;"><span style="line-height: 1.6; color: #f92672;">INNER</span> <span style="line-height: 1.6; color: #f92672;">JOIN</span></div><div style="line-height: 1.6;"> information_schema.innodb_trx b </div><div style="line-height: 1.6;"> <span style="line-height: 1.6; color: #f92672;">ON</span> b.trx_id = w.blocking_trx_id </div><div style="line-height: 1.6;"><span style="line-height: 1.6; color: #f92672;">INNER</span> <span style="line-height: 1.6; color: #f92672;">JOIN</span></div><div style="line-height: 1.6;"> information_schema.innodb_trx r </div><div style="line-height: 1.6;"> <span style="line-height: 1.6; color: #f92672;">ON</span> r.trx_id = w.requesting_trx_id </div><div style="line-height: 1.6;"><span style="line-height: 1.6; color: #f92672;">INNER</span> <span style="line-height: 1.6; color: #f92672;">JOIN</span></div><div style="line-height: 1.6;"> information_schema.innodb_locks lw </div><div style="line-height: 1.6;"> <span style="line-height: 1.6; color: #f92672;">ON</span> lw.lock_trx_id = r.trx_id </div><div style="line-height: 1.6;"><span style="line-height: 1.6; color: #f92672;">INNER</span> <span style="line-height: 1.6; color: #f92672;">JOIN</span></div><div style="line-height: 1.6;"> information_schema.innodb_locks lb </div><div style="line-height: 1.6;"> <span style="line-height: 1.6; color: #f92672;">ON</span> lb.lock_trx_id = b.trx_id</div></div></code></pre>
<p style="margin: 0 0 1.1em; line-height: 1.6;">结果:</p>
</div><div style="line-height: 1.6;">
<pre style="word-break: break-word; font-family: 'Source Code Pro',monospace; white-space: pre-wrap; display: block; background-color: rgba(102,128,153,0.05); color: #333; word-wrap: break-word; font-size: .9em; background: #f6f6f6; line-height: 1.6; margin: 0 0 1.1em; padding: 2px; border: 0; border-radius: 5px; text-align: start;" xml:space="preserve"><code style="font-family: 'Source Code Pro',monospace; font-size: inherit; background-color: transparent; white-space: pre-wrap; border-radius: 0; color: #f8f8f2; display: block; background: #23241f; padding: 18px 28px;"><div style="line-height: 1.6;"><div style="line-height: 1.6;">*************************** <span style="line-height: 1.6; color: #ae81ff;">1.</span> row ***************************</div><div style="line-height: 1.6;"> waiting_trx_id: <span style="line-height: 1.6; color: #ae81ff;">169245</span></div><div style="line-height: 1.6;"> waiting_thread: <span style="line-height: 1.6; color: #ae81ff;">699</span></div><div style="line-height: 1.6;"> waiting_query: insert into t2 value</div><div style="line-height: 1.6;">waiting_for_lock: RECORD X,GAP</div><div style="line-height: 1.6;"> blocking_trx_id: <span style="line-height: 1.6; color: #ae81ff;">169244</span></div><div style="line-height: 1.6;"> blocking_thread: <span style="line-height: 1.6; color: #ae81ff;">698</span></div><div style="line-height: 1.6;"> blocking_query: SELECT r.trx_id wait</div><div style="line-height: 1.6;"> blocking_lock: RECORD X,GAP</div></div></code></pre>
</div><div style="line-height: 1.6;">
<h2 style="font-family: inherit; font-weight: bold; line-height: 1.1; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 2.15em; margin: 1.2em 0 .6em 0; text-align: start;">6. 两个简单的死锁示例</h2>
</div><div style="line-height: 1.6;">
<h3 style="font-family: inherit; font-weight: bold; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 1.7em; margin: 1.2em 0 .6em 0; text-align: start; line-height: 1.6;">不走索引的DELETE引发的死锁</h3>
<p style="margin: 0 0 1.1em; line-height: 1.6;"><code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">SHOW ENGINE INNODB STATUS</code>显示的死锁现场:</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">———————— <br/>
LATEST DETECTED DEADLOCK <br/>
———————— <br/>
150626 11:02:07 <br/>
<em style="line-height: 1.6;">*</em> (1) TRANSACTION: <br/>
TRANSACTION 13260F7, ACTIVE 0 sec starting index read <br/>
mysql tables in use 1, locked 1 <br/>
LOCK WAIT 5 lock struct(s), heap size 1248, 3 row lock(s), undo log entries 2 <br/>
MySQL thread id 216331, OS thread handle 0x7f5784637700, query id 136610709 10.32.57.98 movie_mc updating <br/>
<strong style="font-weight: bold; line-height: 1.6;">DELETE FROM mc_message WHERE msg_session_id = 1250079</strong> <br/>
<em style="line-height: 1.6;">*</em> (1) WAITING FOR THIS LOCK TO BE GRANTED: <br/>
<strong style="font-weight: bold; line-height: 1.6;">RECORD LOCKS</strong> space id 10 page no 5 n bits 328 index <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">PRIMARY</code> of table <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">movie_message_center</code>.<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">mc_message</code> trx id 13260F7 <strong style="font-weight: bold; line-height: 1.6;">lock_mode X</strong> waiting</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;"><em style="line-height: 1.6;">*</em> (2) TRANSACTION: <br/>
TRANSACTION 13260F5, ACTIVE 0 sec fetching rows <br/>
mysql tables in use 1, locked 1 <br/>
4815 lock struct(s), heap size 588216, <strong style="font-weight: bold; line-height: 1.6;">1237194 row lock(s)</strong>, undo log entries 1 <br/>
MySQL thread id 219612, OS thread handle 0x7f5727c78700, query id 136610669 10.32.56.108 movie_mc updating <br/>
<strong style="font-weight: bold; line-height: 1.6;">DELETE FROM mc_message WHERE msg_session_id = 1348342</strong> <br/>
<em style="line-height: 1.6;">*</em> (2) HOLDS THE LOCK(S): <br/>
RECORD LOCKS space id 10 page no 5 n bits 328 index <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">PRIMARY</code> of table <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">movie_message_center</code>.<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">mc_message</code> trx id 13260F5 lock_mode X <br/>
<em style="line-height: 1.6;">*</em> (2) WAITING FOR THIS LOCK TO BE GRANTED: <br/>
RECORD LOCKS space id 10 page no 8425 n bits 328 index <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">PRIMARY</code> of table <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">movie_message_center</code>.<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">mc_message</code> trx id 13260F5 lock_mode X waiting <br/>
<em style="line-height: 1.6;">*</em> WE ROLL BACK TRANSACTION (1)</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">RR级别,对一张表的两个简单delete语句引起了死锁。由于没有走索引,delete导致主索引的所有记录及间隙都被锁上,LOG中也可以看到,第二个事务持有123万把行锁(Next-Key Lock),且由于加锁的顺序是不定的,导致死锁。</p>
</div><div style="line-height: 1.6;">
<h3 style="font-family: inherit; font-weight: bold; color: inherit; margin-top: 21px; margin-bottom: 10.5px; font-size: 1.7em; margin: 1.2em 0 .6em 0; text-align: start; line-height: 1.6;">并发INSERT引发的死锁</h3>
<p style="margin: 0 0 1.1em; line-height: 1.6;">这种死锁现象涉及三个以上并发事务,执行同一条insert语句引发死锁。</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">表的结构:</p>
</div><div style="line-height: 1.6;">
<pre style="word-break: break-word; font-family: 'Source Code Pro',monospace; white-space: pre-wrap; display: block; background-color: rgba(102,128,153,0.05); color: #333; word-wrap: break-word; font-size: .9em; background: #f6f6f6; line-height: 1.6; margin: 0 0 1.1em; padding: 2px; border: 0; border-radius: 5px; text-align: start;" xml:space="preserve"><code style="font-family: 'Source Code Pro',monospace; font-size: inherit; background-color: transparent; white-space: pre-wrap; border-radius: 0; color: #f8f8f2; display: block; background: #23241f; padding: 18px 28px;"><div style="line-height: 1.6;"><div style="line-height: 1.6;"><span style="line-height: 1.6;"><span style="line-height: 1.6; color: #f92672;">CREATE</span> <span style="line-height: 1.6; color: #f92672;">TABLE</span> t1 (i <span style="line-height: 1.6; color: #e6db74;">INT</span>, <span style="line-height: 1.6; color: #f92672;">PRIMARY</span> <span style="line-height: 1.6; color: #f92672;">KEY</span> (i)) <span style="line-height: 1.6; color: #f92672;">ENGINE</span> = <span style="line-height: 1.6; color: #f92672;">InnoDB</span>;</span></div></div></code></pre>
<p style="margin: 0 0 1.1em; line-height: 1.6;">事务执行序列:</p>
<table style="border-collapse: collapse; border-spacing: 0; margin-bottom: 20px; line-height: 1.6;">
<thead style="line-height: 1.6;">
<tr style="line-height: 1.6;">
<th align="center" style="font-weight: bold; vertical-align: bottom; padding: .5em; border-top: 0; border: 1px solid #ddd; line-height: 1.6;">tx0</th>
<th align="center" style="font-weight: bold; vertical-align: bottom; padding: .5em; border-top: 0; border: 1px solid #ddd; line-height: 1.6;">tx1</th>
<th align="center" style="font-weight: bold; vertical-align: bottom; padding: .5em; border-top: 0; border: 1px solid #ddd; line-height: 1.6;">tx2</th>
</tr>
</thead>
<tbody style="line-height: 1.6;"><tr style="line-height: 1.6;">
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"><code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">INSERT INTO t1 VALUES(1)</code>,成功</td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"></td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"></td>
</tr>
<tr style="line-height: 1.6;">
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"></td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"><code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">INSERT INTO t1 VALUES(1)</code>,阻塞</td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"><code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">INSERT INTO t1 VALUES(1)</code>,阻塞</td>
</tr>
<tr style="line-height: 1.6;">
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">rollback</td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"></td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"></td>
</tr>
<tr style="line-height: 1.6;">
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;"></td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">死锁</td>
<td align="center" style="padding: .5em; vertical-align: top; border-top: 1px solid #ddd; border: 1px solid #ddd; line-height: 1.6;">死锁</td>
</tr>
</tbody></table>
<p style="margin: 0 0 1.1em; line-height: 1.6;">用 <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">SHOW ENGINE INNODB STATUS</code>分析一下:</p>
<p style="margin: 0 0 1.1em; line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">第一步:tx0插入成功</strong></p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">—TRANSACTION 0, ACTIVE 48 sec <br/>
1 lock struct(s), heap size 360, 0 row lock(s), undo log entries 1 <br/>
MySQL thread id 702, OS thread handle 0x7fd4ad481700, query id 10384 localhost root cleaning up <br/>
<strong style="font-weight: bold; line-height: 1.6;">TABLE LOCK table <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">test</code>.<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">t1</code> trx id 169250 lock mode IX</strong></p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">按之前的分析,tx0插入成功过后应对记录加X Record Lock,但log只显示了一个IX表锁,这是因为InnoDB对锁有两种实现,一种隐式,一种显式。显式的锁需要维护特有的数据结构,隐式锁是根据当前事务ID和记录中的事务ID计算出来的,开销更小:</p>
<ol style="margin-top: 0; margin-bottom: 1.1em; line-height: 1.6;"><li style="line-height: 1.6;">隐式锁是针对被修改的B+Tree记录,因此都是Record类型的锁。不可能是Gap或Next-Key类型;</li>
<li style="line-height: 1.6;"><code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">INSERT</code>成功后对记录加的X锁,都是隐式锁;</li>
<li style="line-height: 1.6;"><code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">UPDATE</code>、<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">DELETE</code> 对 where 索引和主索引用显式锁,其他二级索引上的Record Lock用隐式锁;</li>
<li style="line-height: 1.6;">隐式锁发生冲突时会转换成显式锁。</li>
</ol>
<p style="margin: 0 0 1.1em; line-height: 1.6;"><strong style="font-weight: bold; line-height: 1.6;">第二步:tx1、tx2插入阻塞</strong></p>
<p style="margin: 0 0 1.1em; line-height: 1.6;">—<strong style="font-weight: bold; line-height: 1.6;">TRANSACTION 2</strong>, ACTIVE 2 sec inserting <br/>
mysql tables in use 1, locked 1 <br/>
LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s) <br/>
MySQL thread id 704, OS thread handle 0x7fd4a3b90700, query id 10389 localhost root update <br/>
INSERT INTO t1 VALUES(1) <br/>
<strong style="font-weight: bold; line-height: 1.6;">——- TRX HAS BEEN WAITING 2 SEC FOR THIS LOCK TO BE GRANTED:</strong> <br/>
<strong style="font-weight: bold; line-height: 1.6;">RECORD LOCKS space id 442 page no 3 n bits 72 index <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">PRIMARY</code> of table <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">test</code>.<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">t1</code> trx id 169252 lock mode S locks rec but not gap waiting</strong> <br/>
—————— <br/>
TABLE LOCK table <code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">test</code>.<code style="font-family: 'Source Code Pro',monospace; font-size: .9em; padding: 2px 4px; color: #c7254e; background-color: #f9f2f4; white-space: normal; border-radius: 4px;">t1</code> trx id 169252 lock mode IX <br/>