加鎖流程一直很迷,尤其幾個session攪在一起。自以為的初窺門徑其實根本不值一提。今天碰巧搜到有大佬從原始碼層面分析insert加鎖,就順便記一下。
原文部落格:
鎖的種類-mysql官方文件
常見 sql 語句的加鎖分析
select, select for update, select lock in share mode詳解
隔離級別為rr,兩個session分別用insert into t where id=***;和select ...lock in share mode;是否會引起幻讀?
insert可以看作兩步:1.獲取意向鎖,2.寫資料,獲取互斥鎖。那麼如果兩步之間,另乙個session使用了select,讀到的資料會與在第二步之後讀到的資料條目數不一致——幻讀。
session1session2
1.insert拿意向寫鎖成功
(1).如果在這個節骨眼上執行select,上意向讀鎖(is),意向寫鎖(ix)與select的意向讀鎖(is)不衝突,因此可以讀取,讀到的是insert前的東西
2.insert插入資料
3.insert完成後,id的聚簇索引對應條目上加行鎖防止其他session讀到
4.commit,釋放行鎖
(2).如果這時候執行select,由於insert所在的session執行了commit,行鎖被釋放,仍然可以讀取,讀到的是insert後的東西,與(1)資料條目不一致,幻讀
那麼究竟有沒有問題呢?還真沒有。這裡就要了解一下幾樣鎖的實現了。
實際上,大佬看原始碼後發現,insert語句的底層實現中,insert語句如果沒遇到衝突,那麼什麼鎖都不會加。並沒有所謂「意向鎖」,以及insert之後的"行鎖"。
那麼我們之前對鎖的理解豈不是出了好大的問題?也不是。我們理解中的insert加鎖,實際上是通過「隱式鎖」實現的。
隱式鎖的關鍵在於轉換。即:本來沒有鎖,其他session執行時檢測到這個帶著隱式鎖的session也在操作同一條記錄,那麼會幫那個session把鎖加上,然後自己乖乖排隊等鎖釋放。
比如要執行一條需要加鎖的select語句,這條語句會首先判斷是否存在其他session活躍,然後檢查活躍session是否已存在排他記錄鎖,如果session活躍且不存在鎖,則為該session加上排他記錄鎖(行鎖)。然後本session的鎖是在之後呼叫lock_rec_lock
函式來加的。
所以現在實際流程變成了這樣:
執行 insert 語句,判斷是否有和插入意向鎖衝突的鎖,如果有,加插入意向鎖,進入鎖等待;如果沒有,直接寫資料,不加任何鎖;
執行 select ... lock in share mode 語句,判斷記錄上是否存在活躍的事務,如果存在,則為 insert 事務建立乙個排他記錄鎖,並將自己加入到鎖等待佇列;
session1session2
1.插入意向鎖不與其它鎖衝突(因為目前沒有其它活躍session)
(1).執行select,根據前面的流程分析,雖然insert所在session處於活躍狀態,但還沒插入資料,因此不需要給insert-session加行級x鎖,直接返回資料,過程中加上gap鎖
但這裡沒有begin和commit,gap鎖直接釋放
2.啥鎖也不加,直接insert插入資料
3.commit
(2).如果這時候執行select,仍然可以讀取到insert後的東西,與(1)資料條目不一致,幻讀
還是幻讀?
不慌,分析一下幻讀的原因。可以發現,由於意向鎖的檢測與實際insert資料之間存在時間差,因此select總能有機會趁機而入,造成幻讀。
好在mysql開發者大佬先我們一步考慮到這一點,於是引入了一項更輕量級的鎖機制來保證insert這兩步操作的原子性,即latch
latch,一般也把它翻譯成 「鎖」,但它和我們之前接觸的行鎖表鎖(lock)是有區別的。這是一種輕量級的鎖,鎖定時間一般非常短,它是用來保證併發執行緒可以安全的操作臨界資源,通常沒有死鎖檢測機制。latch 可以分為兩種:mutex(互斥量)和 rw-lock(讀寫鎖)。這裡我們用到的是 rw-lock。
引入這個概念之後我們再回頭看insert。之前所說insert過程中加「隱式鎖」或者直白點兒不加鎖,指的統統都是我們傳統意義上的lock, 而實際上,insert在插入資料前後,各有一步關於latch的操作:mtr_start(&mtr)和mtr_commit(&mtr),即mini-transaction迷你事務的啟動和提交。既然叫做事務,那這個函式的操作肯定是原子性的,事實上確實如此,insert
會在檢查鎖衝突和寫資料之前,會對記錄所在的頁加乙個 rw-x-latch 鎖,執行完寫資料之後再釋放該鎖(實際上寫資料的操作就是寫 redo log,將髒頁加入 flush list,這個後面有時間再深入分析了)。這個鎖的釋放非常快,但是這個鎖足以保證在插入資料的過程中其他事務無法訪問記錄所在的頁。mini-transaction 也可以包含子事務,實際上在insert
的執行過程中就會加多個 mini-transaction,這中間的過程可以參考這篇部落格:大佬的另一篇博文
每個 mini-transaction 會遵守下面的幾個規則:
所以,最後的最後,真相只有乙個:insert
和select ... lock in share mode
不會發生幻讀。整個流程如下:
執行insert
語句,對要操作的頁加 rw-x-latch,然後判斷是否有和插入意向鎖衝突的鎖,如果有,加插入意向鎖,進入鎖等待;如果沒有,直接寫資料,不加任何鎖,結束後釋放 rw-x-latch;
執行select ... lock in share mode
語句,對要操作的頁加 rw-s-latch,如果頁面上存在 rw-x-latch 會被阻塞,沒有的話則判斷記錄上是否存在活躍的事務,如果存在,則為insert
事務建立乙個排他記錄鎖,並將自己加入到鎖等待佇列,最後也會釋放 rw-s-latch;
再放個**吧
session1session2
1.insert加rw-x-latch成功
2.insert判斷沒有其它活躍session存在
(1-1).此時select執行,rw-s-latch獲取時發現insert的rw-s-latch卡在那裡,阻塞
3.insert插入資料
4.insert插入完成,釋放rw-x-latch
(1-2).終於等到rw-s-latch了,結果發現insert所在session還沒提交,只好幫它加上行級x鎖然後自己默默回去排隊
5.commit;
(1-3).獲取到行級s鎖了,讀取資料
(2).由於(1)一直被阻塞到session1提交才能返回結果,因此如果這時候執行select,顯然與(1)讀取到的資料一致
呼,分析結束,再次鳴謝aneasystone大佬不嫌麻煩自己調**幫助大家理解原理
Mysql insert語句的優化
1 如果你同時從同一客戶插入很多行,使用多個值表的insert語句。這比使用分開insert語句快 在一些情況中幾倍 insert into test values 1,2 1,3 1,4 2 如果你從不同客戶插入很多行,能通過使用insert delayed語句得到更高的速度。delayed的含義...
Mysql insert語句的優化
這比使用分開insert語句快 在一些情況中幾倍 insert into test values 1,2 1,3 1,4 2 假設你從不同客戶插入非常多行,能通過使用insert delayed語句得到更高的速度。delayed的含義是讓insert 語句立即執行。事實上資料都被放在記憶體的佇列中,...
mysql insert 主鍵重複
mysql中insert into和replace into以及insert ignore用法區別 mysql中常用的三種插入資料的語句 insert into表示插入資料,資料庫會檢查主鍵,如果出現重複會報錯 replace into表示插入替換資料,需求表中有primarykey,或者uniqu...