MySQL事務與鎖詳解

事務

什么是事務?
維基百科的定義:事務是數(shù)據(jù)庫管理系統(tǒng)(DBMS)執(zhí)行過程中的一個邏輯單位匾竿,由 一個有限的數(shù)據(jù)庫操作序列構(gòu)成筷转。
這里面有兩個關鍵點,第一個催式,它是數(shù)據(jù)庫最小的工作單元函喉,是不可以再分的。第 二個荣月,它可能包含了一個或者一系列的 DML 語句管呵,包括 insert delete update
(單條 DDL(create drop)和 DCL(grant revoke)也會有事務)

事務的四大特性

事務的四大特性:ACID。
第一個喉童,原子性撇寞,Atomicity,也就是我們剛才說的不可再分堂氯,也就意味著我們對數(shù) 據(jù)庫的一系列的操作蔑担,要么都是成功,要么都是失敗咽白,不可能出現(xiàn)部分成功或者部分失 敗的情況啤握。以轉(zhuǎn)賬的場景為例,一個賬戶的余額減少,對應一個賬戶的增加溉卓,這兩個一 定是同時成功或者同時失敗的。
全部成功比較簡單没陡,問題是如果前面一個操作已經(jīng)成功了蹲蒲,后面的操作失敗了番甩,怎 么讓它全部失敗呢?這個時候我們必須要回滾届搁。
原子性缘薛,在 InnoDB 里面是通過 undo log 來實現(xiàn)的,它記錄了數(shù)據(jù)修改之前的值(邏 輯日志)卡睦,一旦發(fā)生異常宴胧,就可以用 undo log 來實現(xiàn)回滾操作

第二個,一致性表锻,consistent恕齐,指的是數(shù)據(jù)庫的完整性約束沒有被破壞,事務執(zhí)行的 前后都是合法的數(shù)據(jù)狀態(tài)瞬逊。比如主鍵必須是唯一的显歧,字段長度符合要求。
除了數(shù)據(jù)庫自身的完整性約束码耐,還有一個是用戶自定義的完整性追迟。
比如說轉(zhuǎn)賬的這個場景,A 賬戶余額減少 1000骚腥,B 賬戶余額只增加了 500,這個時 候因為兩個操作都成功了瓶逃,按照我們對原子性的定義束铭,它是滿足原子性的, 但是它沒有 滿足一致性厢绝,因為它導致了會計科目的不平衡
還有一種情況契沫,A 賬戶余額為 0,如果這個時候轉(zhuǎn)賬成功了昔汉,A 賬戶的余額會變成 -1000懈万,雖然它滿足了原子性的,但是我們知道靶病,借記卡的余額是不能夠小于 0 的会通,所以 也違反了一致性。用戶自定義的完整性通常要在代碼中控制娄周。

第三個涕侈,隔離性,Isolation煤辨,我們有了事務的定義以后裳涛,在數(shù)據(jù)庫里面會有很多的 事務同時去操作我們的同一張表或者同一行數(shù)據(jù)木张,必然會產(chǎn)生一些并發(fā)或者干擾的操作, 那么我們對隔離性的定義端三,就是這些很多個的事務舷礼,對表或者行的并發(fā)操作,應該是透 明的郊闯,互相不干擾的妻献。通過這種方式,我們最終也是保證業(yè)務數(shù)據(jù)的一致性虚婿。

最后一個叫做持久性旋奢,Durable,事務的持久性是什么意思呢然痊?我們對數(shù)據(jù)庫的任意 的操作至朗,增刪改,只要事務提交成功剧浸,那么結(jié)果就是永久性的锹引,不可能因為我們系統(tǒng)宕 機或者重啟了數(shù)據(jù)庫的服務器,它又恢復到原來的狀態(tài)了唆香。這個就是事務的持久性嫌变。
持久性怎么實現(xiàn)呢?數(shù)據(jù)庫崩潰恢復(crash-safe)是通過什么實現(xiàn)的躬它?
持久性是通過 redo log 和 double write 雙寫緩沖來實現(xiàn)的腾啥,我們操作數(shù)據(jù)的時候,會先寫到內(nèi)存的 buffer pool 里面冯吓,同時記錄 redo log倘待,如果在刷盤之前出現(xiàn)異常,在 重啟后就可以讀取 redo log 的內(nèi)容组贺,寫入到磁盤凸舵,保證數(shù)據(jù)的持久性。
當然失尖,恢復成功的前提是數(shù)據(jù)頁本身沒有被破壞啊奄,是完整的,這個通過雙寫緩沖 (double write)保證掀潮。

原子性菇夸,隔離性,持久性胧辽,最后都是為了實現(xiàn)一致性峻仇。

數(shù)據(jù)庫什么時候會出現(xiàn)事務

無論是我們在 Navicat 的這種工具里面去操作,還是在我們的 Java 代碼里面通過 API 去操作邑商,還是加上@Transactional 的注解或者 AOP 配置摄咆,其實最終都是發(fā)送一個 指令到數(shù)據(jù)庫去執(zhí)行凡蚜,Java 的 JDBC 只不過是把這些命令封裝起來了。
我們先來看一下我們的操作環(huán)境吭从。版本(5.7)朝蜘,存儲引擎(InnnoDB),事務隔離 級別(RR)涩金。

select version(); 
show variables like '%engine%'; 
show global variables like "tx_isolation";

執(zhí)行這樣一條更新語句的時候谱醇,它有事務嗎?

update student set sname = '貓老公 111' where id=1;

實際上步做,它自動開啟了一個事務副渴,并且提交了,所以最終寫入了磁盤全度。
這個是開啟事務的第一種方式煮剧,自動開啟和自動提交。
InnoDB 里面有一個 autocommit 的參數(shù)(分成兩個級別将鸵, session 級別和 global 級別)勉盅。

show variables like 'autocommit';

它的默認值是 ON。autocommit 這個參數(shù)是什么意思呢顶掉?是否自動提交草娜。如果它的 值是 true/on 的話,我們在操作數(shù)據(jù)的時候痒筒,會自動開啟一個事務宰闰,和自動提交事務。
否則簿透,如果我們把 autocommit 設置成 false/off议蟆,那么數(shù)據(jù)庫的事務就需要我們手 動地去開啟和手動地去結(jié)束。
手動開啟事務也有幾種方式萎战,一種是用 begin;一種是用 start transaction舆逃。
那么怎么結(jié)束一個事務呢蚂维?我們結(jié)束也有兩種方式,第一種就是提交一個事務路狮, commit虫啥;還有一種就是 rollback,回滾的時候奄妨,事務也會結(jié)束涂籽。還有一種情況,客戶端 的連接斷開的時候砸抛,事務也會結(jié)束评雌。
后面我們會講到树枫,當我們結(jié)束一個事務的時候,事務持有的鎖就會被釋放景东,無論是 提交還是回滾砂轻。
我們用 begin 手工開啟一個事務,執(zhí)行第二個 update斤吐,但是數(shù)據(jù)沒有寫入磁盤搔涝,因 為事務還沒有提交,這個時候 commit 一下和措,再刷新一下庄呈,OK,寫入了
這個就是我們開啟和結(jié)束事務的兩種方式派阱。

事務并發(fā)會帶來什么問題诬留?

當很多事務并發(fā)地去操作數(shù)據(jù)庫的表或者行的時候,如果沒有我們剛才講的事務的 Isolation 隔離性的時候颁褂,會帶來哪些問題呢故响?


image.png

我們有兩個事務,一個是 Transaction A颁独,一個是 Transaction B彩届,在第一個事務里 面,它首先通過一個 where id=1 的條件查詢一條數(shù)據(jù)誓酒,返回 name=Ada樟蠕,age=16 的 這條數(shù)據(jù)。然后第二個事務靠柑,它同樣地是去操作 id=1 的這行數(shù)據(jù)寨辩,它通過一個 update 的語句,把這行 id=1 的數(shù)據(jù)的 age 改成了 18歼冰,但是注意靡狞,它沒有提交。

這個時候隔嫡,在第一個事務里面甸怕,它再次去執(zhí)行相同的查詢操作,發(fā)現(xiàn)數(shù)據(jù)發(fā)生了變 化腮恩,獲取到的數(shù)據(jù) age 變成了 18梢杭。那么,這種在一個事務里面秸滴,由于其他的時候修改了 數(shù)據(jù)并且沒有提交武契,而導致了前后兩次讀取數(shù)據(jù)不一致的情況,這種事務并發(fā)的問題, 我們把它定義成什么咒唆?

這個叫做臟讀届垫。

如果在轉(zhuǎn)賬的案例里面,我們第一個事務基于讀取到的第二個事務未提交的余額進 行了操作钧排,但是第二個事務進行了回滾敦腔,這個時候就會導致數(shù)據(jù)不一致。

這種讀取到其他事務未提交的數(shù)據(jù)的情況恨溜,我們把它叫做臟讀符衔。

我們再來看第二個


image.png

同樣是兩個事務,第一個事務通過 id=1 查詢到了一條數(shù)據(jù)糟袁。然后在第二個事務里面 執(zhí)行了一個 update 操作判族,這里大家注意一下,執(zhí)行了 update 以后它通過一個 commit 提交了修改项戴。然后第一個事務讀取到了其他事務已提交的數(shù)據(jù)導致前后兩次讀取數(shù)據(jù)不 一致的情況形帮,就像這里,age 到底是等于 16 還是 18周叮,那么這種事務并發(fā)帶來的問題辩撑, 我們把它叫做什么?

這種一個事務讀取到了其他事務已提交的數(shù)據(jù)導致前后兩次讀取數(shù)據(jù)不一致的情 況仿耽,我們把它叫做不可重復讀合冀。

image.png

在第一個事務里面我們執(zhí)行了一個范圍查詢,這個時候滿足條件的數(shù)據(jù)只有一條项贺。 在第二個事務里面君躺,它插入了一行數(shù)據(jù),并且提交了开缎。重點:插入了一行數(shù)據(jù)棕叫。在第一 個事務里面再去查詢的時候,它發(fā)現(xiàn)多了一行數(shù)據(jù)奕删。這種情況俺泣,我們把它叫做什么呢?

一個事務前后兩次讀取數(shù)據(jù)數(shù)據(jù)不一致完残,是由于其他事務插入數(shù)據(jù)造成的砌滞,這種情 況我們把它叫做幻讀

不可重復讀和幻讀坏怪,的區(qū)別在那里呢?

不可重復讀是修改或者刪除绊茧,幻讀是插入铝宵。

小結(jié):我們剛才講了事務并發(fā)帶來的三大問題,現(xiàn)在來給大家總結(jié)一下。無論是臟 讀鹏秋,還是不可重復讀尊蚁,還是幻讀,它們都是數(shù)據(jù)庫的讀一致性的問題侣夷,都是在一個事務里面前后兩次讀取出現(xiàn)了不一致的情況横朋。

讀一致性的問題,必須要由數(shù)據(jù)庫提供一定的事務隔離機制來解決百拓。就像我們?nèi)ワ?店吃飯琴锭,基本的設施和衛(wèi)生保證都是飯店提供的。那么我們使用數(shù)據(jù)庫衙传,隔離性的問題也必須由數(shù)據(jù)庫幫助我們來解決决帖。

SQL92 標準

所以,就有很多的數(shù)據(jù)庫專家聯(lián)合制定了一個標準蓖捶,也就是說建議數(shù)據(jù)庫廠商都按 照這個標準地回,提供一定的事務隔離級別,來解決事務并發(fā)的問題俊鱼,這個就是 SQL92 標準刻像。
我們來看一下 SQL92 標準的官網(wǎng)。
http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt

這里面有一張表格(搜索_iso)并闲,里面定義了四個隔離級別细睡,右邊的 P1 P2 P3 就是 代表事務并發(fā)的 3 個問題,臟讀焙蚓,不可重復讀纹冤,幻讀。Possible 代表在這個隔離級別下购公, 這個問題有可能發(fā)生萌京,換句話說,沒有解決這個問題宏浩。Not Possible 就是解決了這個問題知残。

我們詳細地分析一下這 4 個隔離級別是怎么定義的。

  • Read Uncommitted(未提交讀)
    一個事務可以讀取到其 他事務未提交的數(shù)據(jù)比庄,會出現(xiàn)臟讀求妹,所以叫做 RU,它沒有解決任何的問題佳窑。
  • Read Committed(已提交讀)
    也就是一個事務只能讀取 到其他事務已提交的數(shù)據(jù)制恍,不能讀取到其他事務未提交的數(shù)據(jù),它解決了臟讀的問題神凑, 但是會出現(xiàn)不可重復讀的問題净神。
  • Repeatable Read (可重復讀)
    它解決了不可重復讀的問題何吝, 也就是在同一個事務里面多次讀取同樣的數(shù)據(jù)結(jié)果是一樣的,但是在這個級別下鹃唯,沒有定義解決幻讀的問題爱榕。
  • Serializable(串行化)
    在這個隔離級別里面,所有的事務都是串 行執(zhí)行的坡慌,也就是對數(shù)據(jù)的操作需要排隊黔酥,已經(jīng)不存在事務的并發(fā)操作了,所以它解決 了所有的問題洪橘。

這個是 SQL92 的標準跪者,但是不同的數(shù)據(jù)庫廠商或者存儲引擎的實現(xiàn)有一定的差異, 比如 Oracle 里面就只有兩種 RC(已提交讀)和 Serializable(串行化)梨树。那么 InnoDB 的實現(xiàn)又是怎么樣的呢坑夯?

MySQL InnoDB 對隔離級別的支持

在 MySQL InnoDB 里面,不需要使用串行化的隔離級別去解決所有問題抡四。那我們來 看一下 MySQL InnoDB 里面對數(shù)據(jù)庫事務隔離級別的支持程度是什么樣的柜蜈。

image.png

InnoDB 支持的四個隔離級別和 SQL92 定義的基本一致,隔離級別越高指巡,事務的并 發(fā)度就越低淑履。唯一的區(qū)別就在于,InnoDB 在 RR 的級別就解決了幻讀的問題藻雪。這個也是 InnoDB 默認使用 RR 作為事務隔離級別的原因秘噪,既保證了數(shù)據(jù)的一致性,又支持較高的 并發(fā)度勉耀。

兩大實現(xiàn)方案

那么大家想一下指煎,如果要解決讀一致性的問題,保證一個事務中前后兩次讀取數(shù)據(jù) 結(jié)果一致便斥,實現(xiàn)事務隔離至壤,應該怎么做?我們有哪一些方法呢枢纠?你的思路是什么樣的呢像街? 總體上來說,我們有兩大類的方案晋渺。

1.LBCC

第一種镰绎,我既然要保證前后兩次讀取數(shù)據(jù)一致,那么我讀取數(shù)據(jù)的時候木西,鎖定我要 操作的數(shù)據(jù)畴栖,不允許其他的事務修改就行了。這種方案我們叫做基于鎖的并發(fā)控制 Lock Based Concurrency Control(LBCC)八千。
如果僅僅是基于鎖來實現(xiàn)事務隔離驶臊,一個事務讀取的時候不允許其他時候修改挪挤,那 就意味著不支持并發(fā)的讀寫操作,而我們的大多數(shù)應用都是讀多寫少的关翎,這樣會極大地 影響操作數(shù)據(jù)的效率

2.MVCC

所以我們還有另一種解決方案,如果要讓一個事務前后兩次讀取的數(shù)據(jù)保持一致鸠信, 那么我們可以在修改數(shù)據(jù)的時候給它建立一個備份或者叫快照纵寝,后面再來讀取這個快照 就行了。這種方案我們叫做多版本的并發(fā)控制 Multi Version Concurrency Control (MVCC)星立。
MVCC 的核心思想是: 我可以查到在我這個事務開始之前已經(jīng)存在的數(shù)據(jù)爽茴,即使它 在后面被修改或者刪除了。在我這個事務之后新增的數(shù)據(jù)绰垂,我是查不到的
問題:這個快照什么時候創(chuàng)建室奏?讀取數(shù)據(jù)的時候,怎么保證能讀取到這個快照而不 是最新的數(shù)據(jù)劲装?這個怎么實現(xiàn)呢
InnoDB 為每行記錄都實現(xiàn)了兩個隱藏字段:
DB_TRX_ID胧沫,6 字節(jié):插入或更新行的最后一個事務的事務 ID,事務編號是自動遞 增的(我們把它理解為創(chuàng)建版本號占业,在數(shù)據(jù)新增或者修改為新數(shù)據(jù)的時候绒怨,記錄當前事 務 ID)
DB_ROLL_PTR,7 字節(jié):回滾指針(我們把它理解為刪除版本號谦疾,數(shù)據(jù)被刪除或記 錄為舊數(shù)據(jù)的時候南蹂,記錄當前事務 ID)。
我們把這兩個事務 ID 理解為版本號念恍。
https://www.processon.com/view/link/5d29999ee4b07917e2e09298 MVCC 演示圖

其實這兩個字段大多數(shù)翻譯出來是時間六剥,但是存儲是版本號:
在InnoDB中,會在每行數(shù)據(jù)后添加兩個額外的隱藏的值來實現(xiàn)MVCC峰伙,這兩個值一個記錄這行數(shù)據(jù)何時被創(chuàng)建疗疟,另外一個記錄這行數(shù)據(jù)何時過期(或者被刪除)。 在實際操作中词爬,存儲的并不是時間秃嗜,而是事務的版本號甫恩,每開啟一個新事務铐达,事務的版本號就會遞增捞奕。
來自美團技術(shù)文章

第一個事務埃脏,初始化數(shù)據(jù)(檢查初始數(shù)據(jù))


image.png

此時的數(shù)據(jù)蚌讼,創(chuàng)建版本是當前事務 ID望几,刪除版本為空:


image.png

第二個事務端礼,執(zhí)行第 1 次查詢括饶,讀取到兩條原始數(shù)據(jù)囊咏,這個時候事務 ID 是 2:

image.png

第三個事務恕洲,插入數(shù)據(jù):


image.png

此時的數(shù)據(jù)塔橡,多了一條 tom,它的創(chuàng)建版本號是當前事務編號霜第,3:


image.png

第二個事務葛家,執(zhí)行第 2 次查詢:


image.png

MVCC 的查找規(guī)則:只能查找創(chuàng)建版本小于等于當前事務 ID 的數(shù)據(jù),和刪除版本大 于當前事務 ID 的行(或未刪除)泌类。
也就是不能查到在我的事務開始之后插入的數(shù)據(jù)癞谒,tom 的創(chuàng)建 ID 大于 2,所以還是 只能查到兩條數(shù)據(jù)刃榨。

第四個事務弹砚,刪除數(shù)據(jù),刪除了 id=2 jack 這條記錄:


image.png

image.png

此時的數(shù)據(jù)枢希,jack 的刪除版本被記錄為當前事務 ID桌吃,4,其他數(shù)據(jù)不變:


image.png

在第二個事務中苞轿,執(zhí)行第 3 次查詢:
image.png

查找規(guī)則:只能查找創(chuàng)建版本小于等于當前事務 ID 的數(shù)據(jù)茅诱,和刪除版本大于當前事 務 ID 的行(或未刪除)。

也就是呕屎,在我事務開始之后刪除的數(shù)據(jù)让簿,所以 jack 依然可以查出來。所以還是這兩 條數(shù)據(jù)秀睛。
第五個事務尔当,執(zhí)行更新操作,這個事務事務 ID 是 5:


image.png

此時的數(shù)據(jù)蹂安,更新數(shù)據(jù)的時候椭迎,舊數(shù)據(jù)的刪除版本被記錄為當前事務 ID 5(undo), 產(chǎn)生了一條新數(shù)據(jù)田盈,創(chuàng)建 ID 為當前事務 ID 5:
image.png

第二個事務畜号,執(zhí)行第 4 次查詢:
image.png

查找規(guī)則:只能查找創(chuàng)建版本小于等于當前事務 ID 的數(shù)據(jù),和刪除版本大于當前事 務 ID 的行(或未刪除)允瞧。
因為更新后的數(shù)據(jù) penyuyan 創(chuàng)建版本大于 2简软,代表是在事務之后增加的,查不出 來述暂。
而舊數(shù)據(jù) qingshan 的刪除版本大于 2痹升,代表是在事務之后刪除的,可以查出來畦韭。
通過以上演示我們能看到疼蛾,通過版本號的控制,無論其他事務是插入艺配、修改察郁、刪除衍慎, 第一個事務查詢到的數(shù)據(jù)都沒有變化

在 InnoDB 中,MVCC 是通過 Undo log 實現(xiàn)的皮钠。
Oracle稳捆、Postgres 等等其他數(shù)據(jù)庫都有 MVCC 的實現(xiàn)
需要注意,在 InnoDB 中麦轰,MVCC 和鎖是協(xié)同使用的眷柔,這兩種方案并不是互斥的。
第一大類解決方案是鎖原朝,鎖又是怎么實現(xiàn)讀一致性的呢?

MySQL InnoDB 鎖的基本類型

https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html
官網(wǎng)把鎖分成了 8 類镶苞。所以我們把前面的兩個行級別的鎖(Shared and Exclusive Locks)喳坠,和兩個表級別的鎖(Intention Locks)稱為鎖的基本模式。
后面三個 Record Locks茂蚓、Gap Locks壕鹉、Next-Key Locks,我們把它們叫做鎖的算法聋涨, 也就是分別在什么情況下鎖定什么范圍

鎖的粒度

我們講到 InnoDB 里面既有行級別的鎖晾浴,又有表級別的鎖,我們先來分析一下這兩種鎖定粒度的一些差異牍白。
表鎖脊凰,顧名思義,是鎖住一張表茂腥;行鎖就是鎖住表里面的一行數(shù)據(jù)狸涌。鎖定粒度,表 鎖肯定是大于行鎖的最岗。

那么加鎖效率帕胆,表鎖應該是大于行鎖還是小于行鎖呢?大于般渡。為什么懒豹?表鎖只需要 直接鎖住這張表就行了,而行鎖驯用,還需要在表里面去檢索這一行數(shù)據(jù)脸秽,所以表鎖的加鎖 效率更高。

第二個沖突的概率晨汹?表鎖的沖突概率比行鎖大豹储,還是小淘这?

大于剥扣,因為當我們鎖住一張表的時候巩剖,其他任何一個事務都不能操作這張表。但是 我們鎖住了表里面的一行數(shù)據(jù)的時候钠怯,其他的事務還可以來操作表里面的其他沒有被鎖 定的行佳魔,所以表鎖的沖突概率更大。

表鎖的沖突概率更大晦炊,所以并發(fā)性能更低鞠鲜,這里并發(fā)性能就是小于。

InnoDB 里面我們知道它既支持表鎖又支持行鎖断国,另一個常用的存儲引擎 MyISAM 支 持什么粒度的鎖贤姆?這是第一個問題。第二個就是 InnoDB 已經(jīng)支持行鎖了稳衬,那么它也可 以通過把表里面的每一行都鎖住來實現(xiàn)表鎖霞捡,為什么還要提供表鎖呢?

要搞清楚這個問題薄疚,我們就要來了解一下 InnoDB 里面的基本的鎖的模式(lock mode)碧信,這里面有兩個行鎖和兩個表鎖。

共享鎖

第一個行級別的鎖就是我們在官網(wǎng)看到的 Shared Locks (共享鎖)街夭,我們獲取了 一行數(shù)據(jù)的讀鎖以后砰碴,可以用來讀取數(shù)據(jù),所以它也叫做讀鎖板丽,注意不要在加上了讀鎖 以后去寫數(shù)據(jù)呈枉,不然的話可能會出現(xiàn)死鎖的情況。而且多個事務可以共享一把讀鎖檐什。那怎么給一行數(shù)據(jù)加上讀鎖呢碴卧?

我們可以用 select …… lock in share mode; 的方式手工加上一把讀鎖

釋放鎖有兩種方式,只要事務結(jié)束乃正,鎖就會自動事務住册,包括提交事務和結(jié)束事務。
我們也來驗證一下瓮具,看看共享鎖是不是可以重復獲取荧飞。


image.png

排它鎖

第二個行級別的鎖叫做 Exclusive Locks(排它鎖),它是用來操作數(shù)據(jù)的名党,所以又 叫做寫鎖叹阔。只要一個事務獲取了一行數(shù)據(jù)的排它鎖,其他的事務就不能再獲取這一行數(shù) 據(jù)的共享鎖和排它鎖传睹。

排它鎖的加鎖方式有兩種耳幢,第一種是自動加排他鎖。我們在操作數(shù)據(jù)的時候,包括 增刪改睛藻,都會默認加上一個排它鎖启上。

還有一種是手工加鎖,我們用一個 FOR UPDATE 給一行數(shù)據(jù)加上一個排它鎖店印,這個 無論是在我們的代碼里面還是操作數(shù)據(jù)的工具里面冈在,都比較常用。

釋放鎖的方式跟前面是一樣的按摘。
排他鎖的驗證:


image.png

這個是兩個行鎖包券,接下來就是兩個表鎖。

意向鎖

意向鎖是什么呢炫贤?我們好像從來沒有聽過溅固,也從來沒有使用過,其實他們是由數(shù)據(jù) 庫自己維護的

也就是說兰珍,當我們給一行數(shù)據(jù)加上共享鎖之前发魄,數(shù)據(jù)庫會自動在這張表上面加一個 意向共享鎖。

當我們給一行數(shù)據(jù)加上排他鎖之前俩垃,數(shù)據(jù)庫會自動在這張表上面加一個意向排他鎖。
反過來說:
如果一張表上面至少有一個意向共享鎖汰寓,說明有其他的事務給其中的某些數(shù)據(jù)行加 上了共享鎖口柳。
如果一張表上面至少有一個意向排他鎖,說明有其他的事務給其中的某些數(shù)據(jù)行加 上了排他鎖有滑。

select * from t2 where id =4 for update;

TABLE LOCK table gupao.t2 trx id 24467lock mode IX RECORD LOCKS space id 64 page no 3 n bits 72 index PRIMARY of table gupao.t2 trx id 24467 lock_mode X locks rec but not gap

那么這兩個表級別的鎖存在的意義是什么呢跃闹?第一個,我們有了表級別的鎖毛好,在 InnoDB 里面就可以支持更多粒度的鎖望艺。它的第二個作用,我們想一下肌访,如果說沒有意向鎖的話找默,當我們準備給一張表加上表鎖的時候,我們首先要做什么吼驶?是不是必須先要去 判斷有沒其他的事務鎖定了其中了某些行惩激?如果有的話,肯定不能加上表鎖蟹演。那么這個 時候我們就要去掃描整張表才能確定能不能成功加上一個表鎖风钻,如果數(shù)據(jù)量特別大,比如有上千萬的數(shù)據(jù)的時候酒请,加表鎖的效率是不是很低骡技?

但是我們引入了意向鎖之后就不一樣了。我只要判斷這張表上面有沒有意向鎖羞反,如 果有布朦,就直接返回失敗囤萤。如果沒有,就可以加鎖成功喝滞。所以 InnoDB 里面的表鎖阁将,我們 可以把它理解成一個標志。就像火車上廁所有沒有人使用的燈右遭,是用來提高加鎖的效率 的做盅。

image.png

以上就是 MySQL 里面的 4 種基本的鎖的模式,或者叫做鎖的類型窘哈。

到這里我們要思考兩個問題吹榴,首先,鎖的作用是什么滚婉?它跟 Java 里面的鎖是一樣的图筹, 是為了解決資源競爭的問題,Java 里面的資源是對象让腹,數(shù)據(jù)庫的資源就是數(shù)據(jù)表或者數(shù) 據(jù)行远剩。

所以鎖是用來解決事務對數(shù)據(jù)的并發(fā)訪問的問題的。

那么骇窍,鎖到底鎖住了什么呢瓜晤?

當一個事務鎖住了一行數(shù)據(jù)的時候,其他的事務不能操作這一行數(shù)據(jù)腹纳,那它到底是 鎖住了這一行數(shù)據(jù)痢掠,還是鎖住了這一個字段,還是鎖住了別的什么東西呢嘲恍?

行鎖的原理

沒有索引的表(假設鎖住記錄)

首先我們有三張表足画,一張沒有索引的 t1,一張有主鍵索引的 t2佃牛,一張有唯一索引的 t3淹辞。

CREATE TABLE `t1` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `t1` (`id`, `name`) VALUES (1, '1');
INSERT INTO `t1` (`id`, `name`) VALUES (2, '2');
INSERT INTO `t1` (`id`, `name`) VALUES (3, '3');
INSERT INTO `t1` (`id`, `name`) VALUES (4, '4');
CREATE TABLE `t2` (
  `id` int(11) NOT NULL,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `t2` (`id`, `name`) VALUES (1, '1');
INSERT INTO `t2` (`id`, `name`) VALUES (4, '4');
INSERT INTO `t2` (`id`, `name`) VALUES (7, '7');
INSERT INTO `t2` (`id`, `name`) VALUES (10, '10');
CREATE TABLE `t3` (
  `id` int(11) ,
  `name` varchar(255) ,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `t3` (`id`, `name`) VALUES (1, '1');
INSERT INTO `t3` (`id`, `name`) VALUES (4, '4');
INSERT INTO `t3` (`id`, `name`) VALUES (7, '7');
INSERT INTO `t3` (`id`, `name`) VALUES (10, '10');

我們先假設 InnoDB 的鎖鎖住了是一行數(shù)據(jù)或者一條記錄。

我們先來看一下 t1 的表結(jié)構(gòu)俘侠,它有兩個字段桑涎,int 類型的 id 和 varchar 類型的 name。 里面有 4 條數(shù)據(jù)兼贡,1攻冷、2、3遍希、4等曼。

image.png

現(xiàn)在我們在兩個會話里面手工開啟兩個事務。
在第一個事務里面,我們通過 where id =1 鎖住第一行數(shù)據(jù)禁谦。
在第二個事務里面胁黑,我們嘗試給 id=3 的這一行數(shù)據(jù)加鎖,大家覺得能成功嗎州泊?
很遺憾丧蘸,我們看到紅燈亮起,這個加鎖的操作被阻塞了遥皂。這就有點奇怪了力喷,第一個 事務鎖住了 id=1 的這行數(shù)據(jù),為什么我不能操作 id=3 的數(shù)據(jù)呢演训?
我們再來操作一條不存在的數(shù)據(jù)弟孟,插入 id=5。它也被阻塞了样悟。實際上這里整張表都 被鎖住了拂募。所以,我們的第一個猜想被推翻了窟她,InnoDB 的鎖鎖住的應該不是 Record

那為什么在沒有索引或者沒有用到索引的情況下陈症,會鎖住整張表?這個問題我們先 留在這里震糖。
我們繼續(xù)看第二個演示爬凑。

有主鍵索引的表

我們看一下 t2 的表結(jié)構(gòu)。字段是一樣的试伙,不同的地方是 id 上創(chuàng)建了一個主鍵索引。 里面的數(shù)據(jù)是 1于样、4疏叨、7、10

image.png

image.png

第一種情況穿剖,使用相同的 id 值去加鎖蚤蔓,沖突;使用不同的 id 加鎖糊余,可以加鎖成功秀又。 那么,既然不是鎖定一行數(shù)據(jù)贬芥,有沒有可能是鎖住了 id 的這個字段呢吐辙?

唯一索引(假設鎖住字段)

我們看一下 t3 的表結(jié)構(gòu)。字段還是一樣的蘸劈, id 上創(chuàng)建了一個主鍵索引昏苏,name 上 創(chuàng)建了一個唯一索引。里面的數(shù)據(jù)是 1、4贤惯、7洼专、10

image.png

在第一個事務里面,我們通過 name 字段去鎖定值是 4 的這行數(shù)據(jù)孵构。
在第二個事務里面屁商,嘗試獲取一樣的排它鎖,肯定是失敗的颈墅,這個不用懷疑蜡镶。
在這里我們懷疑 InnoDB 鎖住的是字段,所以這次我換一個字段精盅,用 id=4 去給這行 數(shù)據(jù)加鎖帽哑,大家覺得能成功嗎?
很遺憾叹俏,又被阻塞了妻枕,說明鎖住的是字段的這個推測也是錯的,否則就不會出現(xiàn)第 一個事務鎖住了 name粘驰,第二個字段鎖住 id 失敗的情況屡谐。
既然鎖住的不是 record,也不是 column蝌数,InnoDB 里面鎖住的到底是什么呢愕掏?在這 三個案例里面,我們要去分析一下他們的差異在哪里顶伞,也就是這三張表的結(jié)構(gòu)饵撑,是什么 區(qū)別導致了加鎖的行為的差異?其實答案就是索引唆貌。InnoDB 的行鎖滑潘,就是通過鎖住索引來實現(xiàn)的。

那么我們還有兩個問題沒有解決:
1锨咙、為什么表里面沒有索引的時候语卤,鎖住一行數(shù)據(jù)會導致鎖表?
或者說酪刀,如果鎖住的是索引粹舵,一張表沒有索引怎么辦? 所以骂倘,一張表有沒有可能沒有索引眼滤?
1)如果我們定義了主鍵(PRIMARY KEY),那么 InnoDB 會選擇主鍵作為聚集索引历涝。
2)如果沒有顯式定義主鍵柠偶,則 InnoDB 會選擇第一個不包含有 NULL 值的唯一索 引作為主鍵索引情妖。
3)如果也沒有這樣的唯一索引,則 InnoDB 會選擇內(nèi)置 6 字節(jié)長的 ROWID 作 為隱藏的聚集索引诱担,它會隨著行記錄的寫入而主鍵遞增毡证。
所以,為什么鎖表蔫仙,是因為查詢沒有使用索引料睛,會進行全表掃描,然后把每一個隱 藏的聚集索引都鎖住了

2摇邦、為什么通過唯一索引給數(shù)據(jù)行加鎖恤煞,主鍵索引也會被鎖住施籍?
大家還記得在 InnoDB 里面居扒,當我們使用輔助索引的時候,它是怎么檢索數(shù)據(jù)的嗎丑慎? 輔助索引的葉子節(jié)點存儲的是什么內(nèi)容喜喂?
在輔助索引里面,索引存儲的是二級索引和主鍵的值竿裂。比如name=4玉吁,存儲的是name 的索引和主鍵 id 的值 4
而主鍵索引里面除了索引之外,還存儲了完整的數(shù)據(jù)腻异。所以我們通過輔助索引鎖定 一行數(shù)據(jù)的時候进副,它跟我們檢索數(shù)據(jù)的步驟是一樣的,會通過主鍵值找到主鍵索引悔常,然后也鎖定影斑。


image.png

現(xiàn)在我們已經(jīng)搞清楚 4 個鎖的基本類型和鎖的原理了,在官網(wǎng)上机打,還有 3 種鎖矫户,我 們把它理解為鎖的算法。我們也來看下 InnoDB 在什么時候分別鎖住什么范圍姐帚。

鎖的算法

我們先來看一下我們測試用的表,t2障涯,這張表有一個主鍵索引罐旗。
我們插入了 4 行數(shù)據(jù),主鍵值分別是 1唯蝶、4九秀、7、10粘我。
為了讓大家真正理解這三種行鎖算法的區(qū)別鼓蜒,我們需要了解一下三種范圍的概念痹换。
因為我們用主鍵索引加鎖,我們這里的劃分標準就是主鍵索引的值都弹。

image.png

這些數(shù)據(jù)庫里面存在的主鍵值娇豫,我們把它叫做 Record,記錄畅厢,那么這里我們就有 4 個 Record冯痢。
根據(jù)主鍵,這些存在的 Record 隔開的數(shù)據(jù)不存在的區(qū)間框杜,我們把它叫做 Gap浦楣,間 隙,它是一個左開右開的區(qū)間咪辱。
最后一個振劳,間隙(Gap)連同它左邊的記錄(Record),我們把它叫做臨鍵的區(qū)間油狂, 它是一個左開右閉的區(qū)間历恐。
t2 的主鍵索引,它是整型的选调,可以排序夹供,所以才有這種區(qū)間。如果我的主鍵索引不 是整形仁堪,是字符怎么辦呢哮洽?字符可以排序嗎? 用 ASCII 碼來排序弦聂。
我們已經(jīng)弄清楚了三個范圍的概念鸟辅,下面我們就來看一下在不同的范圍下,行鎖是 怎么表現(xiàn)的莺葫。

記錄鎖(Record Lock)

第一種情況喜每,當我們對于唯一性的索引(包括唯一索引和主鍵索引)使用等值查詢,精準匹配到一條記錄的時候乡摹,這個時候使用的就是記錄鎖状知。
比如 where id = 1 4 7 10 。
這個演示我們在前面已經(jīng)看過了堡纬。我們使用不同的 key 去加鎖聂受,不會沖突,它只鎖 住這個 record烤镐。

間隙鎖(Gap Lock)

第二種情況蛋济,當我們查詢的記錄不存在,沒有命中任何一個 record炮叶,無論是用等值 查詢還是范圍查詢的時候碗旅,它使用的都是間隙鎖渡处。
舉個例子,where id >4 and id <7祟辟,where id = 6医瘫。


image.png

重復一遍,當查詢的記錄不存在的時候川尖,使用間隙鎖登下。
注意,間隙鎖主要是阻塞插入 insert叮喳。相同的間隙鎖之間不沖突被芳。
Gap Lock 只在 RR 中存在。如果要關閉間隙鎖馍悟,就是把事務隔離級別設置成 RC畔濒, 并且把 innodb_locks_unsafe_for_binlog 設置為 ON。
這種情況下除了外鍵約束和唯一性檢查會加間隙鎖锣咒,其他情況都不會用間隙鎖侵状。

臨鍵鎖(Next_Key Lock)

第三種情況,當我們使用了范圍查詢毅整,不僅僅命中了 Record 記錄趣兄,還包含了 Gap 間隙,在這種情況下我們使用的就是臨鍵鎖悼嫉,它是 MySQL 里面默認的行鎖算法艇潭,相當于 記錄鎖+間隙鎖 組合加一起。
其他兩種退化的情況:
唯一性索引戏蔑,等值查詢匹配到一條記錄的時候蹋凝,退化成記錄鎖。
沒有匹配到任何記錄的時候总棵,退化成間隙鎖鳍寂。
比如我們使用>5 <9, 它包含了記錄不存在的區(qū)間情龄,也包含了一個 Record 7迄汛。

image.png

臨鍵鎖,鎖住最后一個 key 的下一個左開右閉的區(qū)間骤视。

select * from t2 where id >5 and id <=7 for update; -- 鎖住(4,7]和(7,10] 
select * from t2 where id >8 and id <=10 for update; -- 鎖住 (7,10]鞍爱,(10,+∞)

為什么要鎖住下一個左開右閉的區(qū)間?——就是為了解決幻讀的問題

隔離級別的實現(xiàn)

所以尚胞,我們再回過頭來看下這張圖片硬霍,為什么 InnoDB 的 RR 級別能夠解決幻讀的 問題帜慢,就是用臨鍵鎖實現(xiàn)的笼裳。
我們再回過頭來看下這張圖片唯卖,這個就是MySQL InnoDB里面事務隔離級別的實現(xiàn)。


image.png

最后我們來總結(jié)一下四個事務隔離級別的實現(xiàn):

  • Read Uncommited
    RU 隔離級別:不加鎖躬柬。
  • Serializable
    Serializable 所有的 select 語句都會被隱式的轉(zhuǎn)化為 select ... in share mode拜轨,會 和 update、delete 互斥允青。

這兩個很好理解橄碾,主要是 RR 和 RC 的區(qū)別?

  • Repeatable Read
    RR 隔離級別下颠锉,普通的 select 使用快照讀(snapshot read)法牲,底層使用 MVCC 來實 現(xiàn)。
    加鎖的 select(select ... in share mode / select ... for update)以及更新操作 update, delete 等語句使用當前讀(current read)琼掠,底層使用記錄鎖拒垃、或者間隙鎖臨鍵鎖瓷蛙。
  • Read Commited
    RC 隔離級別下悼瓮,普通的 select 都是快照讀,使用 MVCC 實現(xiàn)艰猬。
    加鎖的 select 都使用記錄鎖横堡,因為沒有 Gap Lock。

除了兩種特殊情況——外鍵約束檢查(foreign-key constraint checking)以及重復 鍵檢查(duplicate-key checking)時會使用間隙鎖封鎖區(qū)間冠桃。 所以 RC 會出現(xiàn)幻讀的問題命贴。

事務隔離級別怎么選?

https://dev.mysql.com/doc/refman/5.7/en/innodb-transaction-isolation-levels.html
RU 和 Serializable 肯定不能用腊满。為什么有些公司要用 RC套么,或者說網(wǎng)上有些文章推 薦有 RC?
RC 和 RR 主要有幾個區(qū)別:
1碳蛋、 RR 的間隙鎖會導致鎖定范圍的擴大胚泌。
2、 條件列未使用到索引肃弟,RR 鎖表玷室,RC 鎖行。
3笤受、 RC 的“半一致性”(semi-consistent)讀可以增加 update 操作的并發(fā)性穷缤。
在 RC 中,一個 update 語句箩兽,如果讀到一行已經(jīng)加鎖的記錄津肛,此時 InnoDB 返回記 錄最近提交的版本,由 MySQL 上層判斷此版本是否滿足 update 的 where 條件汗贫。若滿 足(需要更新)身坐,則 MySQL 會重新發(fā)起一次讀操作秸脱,此時會讀取行的最新版本(并加鎖)。

實際上部蛇,如果能夠正確地使用鎖(避免不使用索引去枷鎖)摊唇,只鎖定需要的數(shù)據(jù), 用默認的 RR 級別就可以了涯鲁。

在我們使用鎖的時候巷查,有一個問題是需要注意和避免的,我們知道抹腿,排它鎖有互斥 的特性岛请。一個事務或者說一個線程持有鎖的時候,會阻止其他的線程獲取鎖警绩,這個時候 會造成阻塞等待髓需,如果循環(huán)等待,會有可能造成死鎖房蝉。

這個問題我們需要從幾個方面來分析僚匆,一個是鎖為什么不釋放,第二個是被阻塞了 怎么辦搭幻,第三個死鎖是怎么發(fā)生的咧擂,怎么避免。

死鎖

鎖的釋放與阻塞

回顧:鎖什么時候釋放檀蹋?
事務結(jié)束(commit松申,rollback);客戶端連接斷開俯逾。

如果一個事務一直未釋放鎖贸桶,其他事務會被阻塞多久?會不會永遠等待下去桌肴?如果 是皇筛,在并發(fā)訪問比較高的情況下,如果大量事務因無法立即獲得所需的鎖而掛起坠七,會占 用大量計算機資源水醋,造成嚴重性能問題,甚至拖跨數(shù)據(jù)庫彪置。
[Err] 1205 - Lock wait timeout exceeded; try restarting transaction
MySQL 有一個參數(shù)來控制獲取鎖的等待時間拄踪,默認是 50 秒。

show VARIABLES like 'innodb_lock_wait_timeout';
image.png

對于死鎖拳魁,是無論等多久都不能獲取到鎖的惶桐,這種情況,也需要等待 50 秒鐘嗎?那 不是白白浪費了 50 秒鐘的時間嗎姚糊?
我們先來看一下什么時候會發(fā)生死鎖

死鎖的發(fā)生和檢測

image.png

image.png

在第一個事務中想虎,檢測到了死鎖,馬上退出了叛拷,第二個事務獲得了鎖,不需要等待 50 秒:
[Err] 1213 - Deadlock found when trying to get lock; try restarting transaction
為什么可以直接檢測到呢岂却?是因為死鎖的發(fā)生需要滿足一定的條件忿薇,所以在發(fā)生死 鎖時,InnoDB 一般都能通過算法(wait-for graph)自動檢測到躏哩。

那么死鎖需要滿足什么條件署浩?死鎖的產(chǎn)生條件:
因為鎖本身是互斥的,(1)同一時刻只能有一個事務持有這把鎖扫尺,(2)其他的事 務需要在這個事務釋放鎖之后才能獲取鎖筋栋,而不可以強行剝奪,(3)當多個事務形成等 待環(huán)路的時候正驻,即發(fā)生死鎖弊攘。

舉例:
理發(fā)店有兩個總監(jiān)。一個負責剪頭的 Tony 總監(jiān)姑曙,一個負責洗頭的 Kelvin 總監(jiān)襟交。
Tony 不能同時給兩個人剪頭,這個就叫互斥伤靠。
Tony 在給別人在剪頭的時候捣域,你不能讓他停下來幫你剪頭,這個叫不能強行剝奪宴合。
如果Tony的客戶對Kelvin總監(jiān)說:你不幫我洗頭我怎么剪頭焕梅?Kelvin的客戶對Tony 總監(jiān)說:你不幫我剪頭我怎么洗頭?這個就叫形成等待環(huán)路

如果鎖一直沒有釋放卦洽,就有可能造成大量阻塞或者發(fā)生死鎖贞言,造成系統(tǒng)吞吐量下降, 這時候就要查看是哪些事務持有了鎖

查看鎖信息(日志)

SHOW STATUS 命令中阀蒂,包括了一些行鎖的信息:

show status like 'innodb_row_lock_%';
image.png

Innodb_row_lock_current_waits:當前正在等待鎖定的數(shù)量蜗字;
Innodb_row_lock_time :從系統(tǒng)啟動到現(xiàn)在鎖定的總時間長度,單位 ms脂新;
Innodb_row_lock_time_avg :每次等待所花平均時間挪捕;
Innodb_row_lock_time_max:從系統(tǒng)啟動到現(xiàn)在等待最長的一次所花的時間;
Innodb_row_lock_waits :從系統(tǒng)啟動到現(xiàn)在總共等待的次數(shù)争便。

SHOW 命令是一個概要信息级零。InnoDB 還提供了三張表來分析事務與鎖的情況:

select * from information_schema.INNODB_TRX; -- 當前運行的所有事務 ,還有具體的語句
image.png
select * from information_schema.INNODB_LOCKS; -- 當前出現(xiàn)的鎖
image.png
select * from information_schema.INNODB_LOCK_WAITS; -- 鎖等待的對應關系
image.png

找出持有鎖的事務之后呢?
如果一個事務長時間持有鎖不釋放奏纪,可以 kill 事務對應的線程 ID鉴嗤,也就是 INNODB_TRX 表中的 trx_mysql_thread_id,例如執(zhí)行 kill 4序调,kill 7醉锅,kill 8。
當然发绢,死鎖的問題不能每次都靠 kill 線程來解決硬耍,這是治標不治本的行為。我們應該 盡量在應用端边酒,也就是在編碼的過程中避免经柴。
有哪些可以避免死鎖的方法呢?

死鎖的避免

1墩朦、 在程序中坯认,操作多張表時,盡量以相同的順序來訪問(避免形成等待環(huán)路)氓涣;
2牛哺、 批量操作單張表數(shù)據(jù)的時候,先對數(shù)據(jù)進行排序(避免形成等待環(huán)路)劳吠;
3荆隘、 申請足夠級別的鎖,如果要操作數(shù)據(jù)赴背,就申請排它鎖椰拒;
4、 盡量使用索引訪問數(shù)據(jù)凰荚,避免沒有 where 條件的操作燃观,避免鎖表;
5便瑟、 如果可以缆毁,大事務化成小事務;
6到涂、 使用等值查詢而不是范圍查詢查詢數(shù)據(jù)脊框,命中記錄,避免間隙鎖對并發(fā)的影響践啄。

——學自咕泡學院

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末浇雹,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子屿讽,更是在濱河造成了極大的恐慌昭灵,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異烂完,居然都是意外死亡试疙,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門抠蚣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來祝旷,“玉大人,你說我怎么就攤上這事嘶窄』初耍” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵护侮,是天一觀的道長。 經(jīng)常有香客問我储耐,道長羊初,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任什湘,我火速辦了婚禮长赞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘闽撤。我一直安慰自己得哆,他們只是感情好,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布哟旗。 她就那樣靜靜地躺著贩据,像睡著了一般。 火紅的嫁衣襯著肌膚如雪闸餐。 梳的紋絲不亂的頭發(fā)上饱亮,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機與錄音舍沙,去河邊找鬼近上。 笑死,一個胖子當著我的面吹牛拂铡,可吹牛的內(nèi)容都是我干的壹无。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼感帅,長吁一口氣:“原來是場噩夢啊……” “哼斗锭!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起失球,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤拒迅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體璧微,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡作箍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了前硫。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胞得。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖屹电,靈堂內(nèi)的尸體忽然破棺而出阶剑,到底是詐尸還是另有隱情,我是刑警寧澤危号,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布牧愁,位于F島的核電站,受9級特大地震影響外莲,放射性物質(zhì)發(fā)生泄漏猪半。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一偷线、第九天 我趴在偏房一處隱蔽的房頂上張望磨确。 院中可真熱鬧,春花似錦声邦、人聲如沸乏奥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽邓了。三九已至,卻和暖如春媳瞪,著一層夾襖步出監(jiān)牢的瞬間驶悟,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工材失, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留痕鳍,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓龙巨,卻偏偏與公主長得像笼呆,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子旨别,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 作者:劉仁鵬參考資料: 《MySQL技術(shù)內(nèi)幕 InnoDB存儲引擎》 MySQL的并發(fā)控制與加鎖分析 1.基礎知識...
    agile4j閱讀 5,461評論 5 17
  • 索引 數(shù)據(jù)庫中的查詢操作非常普遍秸弛,索引就是提升查找速度的一種手段 索引的類型 從數(shù)據(jù)結(jié)構(gòu)角度分 1.B+索引:傳統(tǒng)...
    一凡呀閱讀 2,859評論 0 8
  • 微信公眾號【黃小斜】大廠程序員铭若,互聯(lián)網(wǎng)行業(yè)新知洪碳,終身學習踐行者。關注后回復「Java」叼屠、「Python」瞳腌、「C++...
    程序員黃小斜閱讀 383評論 0 1
  • InnoDB 鎖 數(shù)據(jù)庫使用鎖是為了支持更好的并發(fā),提供數(shù)據(jù)的完整性和一致性镜雨。InnoDB是一個支持行鎖的存儲引擎...
    大富帥閱讀 1,464評論 0 4
  • 你有沒有感覺到嫂侍,越大越怕冷了呢?盡管我們才二十幾歲荚坞,還是最美好的年紀挑宠,還是最能折騰的年紀…… “多穿點吧,別凍著颓影,...
    夜闌珊閱讀 413評論 0 1