mysql鎖機制學習筆記

鎖是用來解決因資源共享而造成的併發問題。

根據加鎖範圍,MySQL裏面的鎖大致可以分成全局鎖、表級鎖和行鎖。

全局鎖:

 flush tables with read lock 簡稱FTWRL將會關閉所有正在使用的表,並且禁止任何表被打開。通過unlock tables釋放鎖,獲取單個全局鎖,與任何表鎖都衝突。讓整個庫處於只讀狀態,之後其他線程的以下語句會被阻賽:數據更新(增刪改)、數據定義、更新類事務。使用場景是做全庫邏輯備份,也就是把整個庫每個表都select出來存成文本。

存在的危險:

1、如果在主庫上備份,那麼備份期間都不能執行更新,業務基本上停擺。

2、如果在從庫上備份,那麼備份期間從庫不能執行主庫同步過來的binlog,會導致主從延遲。

官方自帶的mysqldump使用參數 -single-transaction ,導數據之前就會啓動一個事務,來確拿到一致性視圖。由於MVCC的支持,這個過程的數據是可以正常更新的。只適用於事務引擎,如innodb。MyISAM表適用於flush tables with read lock。不建議使用set global readonly=true,原因有下面:

1、在有些系統中,readonly的值會被用來做其他邏輯,比如用來判斷一個庫是主庫還是從庫,因此修改global變量的方式影響面更大。不建議使用。

2、在異常處理機制上,FTWRL命令之後由於客戶端發生的異常斷開,那麼mysql會自動釋放這個全局鎖,整庫可以回到正常更新狀態要,而設爲set global readonly=true,如果客戶端發生異常,則數據庫一直處於鎖狀態,導致整長時間處於不可寫狀態。風險較高。

 

表級鎖分爲表鎖和元數據鎖(meta data lock)。

表鎖不是存儲引擎層管理的,是由server層管理的。commit或rollback 並不能釋放用lock tables加的表級鎖,必須用unlcok tables釋放表鎖。僅當autocommit、innodb_table_locks=1時,innodb層才知道加的表鎖。不要用unlock tables釋放鎖,因爲unlock tables會隱式提交事務。也可以在客戶端斷開的時候自動釋放鎖。lock tables語法除了限制別的線程讀寫外,也限定了本線接下來的操作對象。語法lock table xx read/write。

 

命名鎖是一種表鎖:rename table old_table_name to new_table_name。

另一類的表級鎖是MDL即meta data lock,MDL不需要顯式使用,在訪問一個表的時候會被自動加上。

MDL的作用是保證讀寫的正確性。例如,如果一個查詢正在遍歷一個表中的數據,而執行期間另一個線程對這個表結構做變更,刪了一列,那麼查詢線程拿到的結果跟表結構對不上,這是肯定有誤的。

當對一個表增刪改查時,加MDL讀鎖,當對錶結構變更操作時候加MDL寫鎖。

1、讀鎖之間不互斥,因此可以有多個線程同時對一張表增刪改查。

2、讀寫鎖之間、寫鎖之間是互斥的,用來保正變更表結構操作的安全性。因此如果有兩個線程要同時給一個表加字段,其中一個要等另一個執行完成才能開始執行。

事務中的MDL鎖,在語句執行開始時申請,但是語句結束後並不會馬上釋放,而會等到整個事務提交後再釋放。

出現查詢一行數據或是少量數據長時間不返回,可能就是有線程在表上請求或持有MDL寫鎖,把select語句堵了。

mysql裏對錶做flush操作有兩種情況:

flush tables t with read lock; flush tables with read lock;指定表,則只關閉表t,如果沒有指定則關閉所有打開的表。所以出現waiting for table flush狀態可能情況是:有一個flush tables命令被別的語句堵住了,然後又堵住了當前select語句。

 

等行鎖:

當佔有行鎖的update語句,這個語句已經是之前執行完成了的,現在執行kill query無法讓這個事務去掉行鎖,通過kill (select * from sys.innodb_lcok_waits where locked_table='test.t')直接斷開這個連接,被斷開時,會自動回滾這個連接裏面正在執行的線程,也就釋放了鎖。

 

帶lock in shar mode的語句是當前讀,所以速度快,而普通的select * from t where id=1在事務中是一致性讀,需要從當前依次執行undolog,執行100萬次後,纔將1返回。如下圖:

 

select * from t where b='01234567890abcd'在表t的b字段上有索引,且字段長度爲10。mysql在執行時,只截了前10個字節匹配,這樣滿足條件的行有10萬行。因爲是select * 所以要做10萬次回表,但是每次回表查出整行到server層判斷b的值不是01234567890abcd,所以最終結果返回空。

 

mysql加鎖原則:

1、加鎖的基本單位是next-key,next_key lock是前開後閉區間

2、查找過程中訪問到的對象纔會加鎖

3、索引上的等值查詢,給唯一索引加鎖的時候,next-key lock退化爲行鎖。

4、索引上的等值查詢,向右遍歷時且最後一個值滿足等值條件的時候,next-key lock退化爲間隙鎖

5、唯一索引上的範圍查詢會訪問到滿足條件的第一值爲止。這個是bug.

 

InnoDB存儲引擎的鎖的算法有三種:

· Record lock:單個行記錄上的鎖,鎖是加在索引上的

· Gap lock:間隙鎖,鎖定一個範圍,不包括記錄本身;對索引項之間的間隙、每一條記錄前的間隙或最後一條記錄後的間隙加鎖。鎖的是兩個值之間空隙,跟間隙鎖存在衝突關係的,是“往這個間隙中插入一個記錄”這個操作,即是說這個間隙已經被鎖了,其他線程還在往這個間隙插入數據造成的衝突。間隙鎖之間不存在衝突關係。間隙鎖記爲開區間。

· Next-key lock:record+gap 鎖定一個範圍,包含記錄本身,前面兩種鎖的組合,對記錄及其前面的間隙加鎖,記爲前開後閉區間。

 

這意味着,如果不通過索引條件檢索數據,那麼innodb將對錶中的所有記錄加鎖,實際和表鎖一樣。

由於mysql的行鎖是針對索引加的鎖,不是針對記錄加的鎖,所以雖然訪問的是不同的記錄,但是如果使用的相同的索引鍵是會出現鎖衝突的。例如select * from table where id=1 and name='na' for update 與select * from table where id=1 and name='mm' for update ,這兩個語句雖然記不同,但是用了相同的索引。

如果使用相等條件請求給一個不存在的記錄加鎖,innodb也會使用next-key鎖。

 

對於基於語句的binlog格式的恢復和複製,由於binlog是按照事務提交的先後順序記錄的,因此要正確恢復或複製數據,就必須滿足:在一個事務未提交前,其他併發事務不能插入滿足其鎖定條件的任何記錄。也就是不允許出現幻讀。

 

加鎖案例:

1、等值查詢間隙鎖

 

a:update t set d=d+1 where id=7;根據原則1,加鎖單位是next-key,所以鎖住的區間是(5,10];

b:由於是等值查詢,id=10不滿足條件,所以退化爲間隙鎖,鎖住的區間是(5,10)

 

2、非唯一索引等值鎖

 

a:有符合條件的數據。根據原則1加鎖單位是next-key,鎖住區間是(0,5],next-key lock;

b:假如有索引c,因此僅訪問c=5這一條記錄是不能馬上停下來的,需要向右遍歷,查到c=10才停止,根據原則2訪問到的對象都要加鎖。因此給(5,10]加next-key lock

c:根據原則3,等值判斷,向右遍歷,最後一個值不滿足c=5,因此退化爲間隙鎖(5,10)

d:根據原則2,只有訪問到的對象纔會加鎖,這個session A的查詢用到了覆蓋索引,所以主鍵索引上沒有加任何鎖。這就是爲什麼session B的update 語句可以執行完成。但是session c會被阻塞。

例子中用lock in share mode只鎖覆蓋索引,如果是for update系統認爲要更新數據,所以會順便給主鍵索引加上滿足條件的行鎖

這個例子說明,鎖是加在索引上的,如果用lock in share mode來給行加讀鎖避免數據被更新,就必須得繞過覆蓋索引的優化,在查詢字段中加入索引中不存在的字段。比如,將sesion A的查詢語句改成select d from t where c=5 lock in share mode。

 

3、主鍵索引範圍鎖

 

select * from t where id=10 for update;

select * from t where id>=10 and id<11 for update;

語句2加鎖邏輯

a:執行時,找到第一個id=10,本應加next-key lock (5,10],根據原則3,主鍵id上的等值條件,退化成行鎖,只加了id=10這一行的行鎖。

b:範圍查找就往後繼續找,找到id=15這一行停止,因此需要加next-key lock(10,15]。

所以這時候鎖的範圍就是主鍵索引上行鎖id=10和next-key lock (10,15]。

 

4、非唯一索引範圍鎖

將上例中的條件部分的字段改爲c,字段c有非唯一索引。在第一次用c=10定位記錄時,加了next-key lock (5,10]鎖,沒有應用原則3,即不會蛻變爲行鎖,所以最終索引c上(5,10],(10,15]這兩個next-key lock。

 

5、唯一索引範圍鎖bug

 

sessionA是一個範圍查詢,按照原則1,應該是在索引id上只加(10,15]這個next-key lock,並且因爲id是唯一鍵,所以應該到15這一行就應該停止了。但實際上innode 掃描到第一個不滿足條件爲止,所以鎖住了id=20這行,最終的next-key lock是(10,20],導致sessionB和sessionC會被鎖住。

 

 

6、非唯一索引上存在"等值"的例子

例如在表中插入 insert into t(id,c,d) values(10,10,30),(30,10,30);在字段c上有非唯一索引,但是它們的主鍵值不同(普通索引的葉子節點存儲了主鍵值),因此c=10的記錄之間也是有間隙的。

 

sessionA在向右查找,直到(c=15,id=15)這一行,循環才結束。根據原則3,這是一個等值查詢,向右查劃到了不滿足條件的行,所以會退化成(c=10,id=10)到(c=15,id=15)的間隙鎖。注意都是開區間。

 

7、limit語句加鎖

 

因爲在delete語句中明確加了limit 2的限制,因此在遍歷到(c=10,id=30)這一行後,滿足條件的語句已經有兩條,循環結束了。因此索引c上的加鎖範圍就變成了從(c=5,id=5)到(c=10,id=30)這個前開後閉區間。可以看到(c=10,id=30)之後的這個間隙鎖沒有在加鎖範圍裏,因此insert 語句插入c=12是可以執行的。因此,加limit可以減小鎖的範圍。

 

8、死鎖

 

next-key lock實際上是間隙鎖和行鎖加起來的結果。

a:sessionA啓動事務後執行查詢語解句加lock in share mode,在索引c上加了next-key lock (5,10]和間隙鎖(10,15)

b:sessionB的update 語句也要在索引c上加next-key lock (5,10],進行鎖等待

c:然後session C要插入(8,8,8)這一行被sessionB的間隙鎖鎖住由於出現了死鎖,innodb 讓session B回滾。

sessionB的next-key lock(5,10]實際分了兩步,先是加(5,10)的間隙鎖,加鎖成功;然後加c=10的行鎖,這時候才被鎖住的。也就是說在分析加鎖規則的時候可以用next-key lock來分析。但是要知道,具體執行的時候,是要分成間隙鎖和行鎖兩來執行的。

 

 

 

在innodb事務中,行鎖是在需要的時候加上的,但並不是不需要就立刻釋放,而百要等到事務結束時才釋放的。這就兩階段鎖協議。

出現死鎖後有兩種策略:

1、直接進入等待,直到超時,這個時間可以通過參數innodb_lock_wait_timeout(默認50秒)來設置。

2、發起死鎖檢測,主動回滾死鎖鏈條中某一個事務,讓其他事務得以斷續,將參數innodb_deadlock_detect(默認開啓)設置爲on表示開啓這個邏輯。

 

 

加讀鎖lock table tablename read:

會話0:如果一個會話對A表加了read鎖,則該會話可以對A表進行讀操作,不能進行寫操作,即如果給A表加了讀鎖,則當前會話只能對A表進行讀操作,不能對其他表操作

會話0:對當前表可以讀操作,寫操作一直等待,等待釋放鎖

會話1:訪問其他表可以讀,對其他表寫可以

總結:會話0給A表加了鎖,其他會話的操作:

a.可以對其他表(A表以外的表)進行查、寫操作

b:對A表可以讀,寫需要等待釋放鎖

lock table table read local 表示myisam表併發插入的情況下,允許其他用戶在表尾併發插入記錄。

釋放鎖:unlock tables;

 

加寫鎖:

會話0:

lock table table_name write;可以對該表任何操作,但是不能操作其他表

其他會話:

對會話0中加寫鎖的表可以進行增刪改查的前提是:等待會話0釋放寫鎖

 

show table like '%table_locks%'得到結果如下:

table_locks_immediate 可能獲取到的鎖

table_locks_waited 需要等待的表鎖數(如果該值越大,說明存在越大的鎖竟爭)

行鎖的注意事項:

a:如果沒有索引,則行鎖會轉爲表鎖。如果索引列發生了數據轉換,則索引列失效,則會從行鎖轉爲表鎖

b: 行鎖的一種特殊情況,間隙鎖,值在範圍內,但卻不存在,即在條件where中沒有要查詢的數據,mysql會自動加間隙加鎖。

 

行鎖分析:

查看加鎖的表:show open tables;

show status like '%innodb_row_lock%':

innodb_row_lock_current_waits:當前正在等待鎖的數量

innodb_row_lcok_time, 等待總時長,從系統啓動到現在一共等待的時間

innodb_row_lock_time_avg 平均等待時長,從系統啓動到現在平均等待時間

innodb_row_lock_time_max 最大等待時長,從系統啓動到現在最大等待時間

 

 

 

在會話0:set innodb_locks_unsafe_for_binlog='on'默認是off,可以使其他會話1能夠更新會話0未提交的鎖定的記錄。因爲設爲on後,innodb不再對錶加鎖。如果一定要使用這種方式,可以通過select * from table into outfile 和load data infile ,這種方式不會給table加鎖;或是使用於row的binlog格式和基於行數據的複製。

 

 

相關知識點:

· innodb對於行的查詢使用next-key lock

· Next-locking keying爲了解決Phantom Problem幻讀問題

· 當查詢的索引含有唯一屬性時,將next-key lock降級爲record key

· Gap間隙鎖設計的目的是爲了阻止多個事務將記錄插入到同一範圍內,而這會導致幻讀問題的產生

· 有兩種方式顯式關閉gap鎖:(除了外鍵約束和唯一性檢查外,其餘情況僅使用record lock)

A. 將事務隔離級別設置爲RC

B. 將參數innodb_locks_unsafe_for_binlog設置爲1

在repeatable read級別下,如果兩個線程同時對相同條件的記錄用select for update,在沒有符合該條件的記錄下,兩個線程都會加鎖成功。程序發現記錄尚不存在,試圖插入一條新記錄。如果兩個線程都這麼做,就會出現死鎖。這種情況將隔離級別改成read commited可以避免。

如果出現死鎖,可以用show innodb status命令來確定最後一個死鎖產生的原因。

特定的語句進行顯示鎖定:

select * from tableName where id=1 for update排他鎖 ;

select .... lock in share mode共享鎖 ;

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章