MySQL鎖:02.InnoDB鎖
InnoDB鎖
- 預設是行鎖(row lock)
- InnoDB是通過在索引記錄上加鎖,實現行鎖
- 因此沒有索引時就無法實現行鎖,從而升級成全表記錄鎖,等同於表鎖。
- 索引效率很低時鎖也會升級。需要加鎖的資料量過多,也會直接升級鎖範圍。 因為這樣代價會低很多。
如同用書佔座,只有當其他人想坐過來的時候,幫佔座的人才會出面提出該座位已經被佔用(被鎖)
InnoDB行鎖實現機制
- 基於索引實現,逐行檢查,逐行加鎖
- 沒有索引的列上需要加鎖時,會先對所有記錄加鎖,再根據實際情況決定是否釋放鎖。
- 輔助索引上加鎖時,同時要回溯到主鍵索引上再加一次鎖。
- 加鎖的基本單位預設時lock_ordinary,當索引就具有唯一性的時候退化為lock_rec_not_gap
- 等值條件逐行加鎖時,會向右遍歷到第一個不滿足條件的記錄,然後lock_ordinary退化為lock_gap
- 如果發生唯一性檢測(insert\update動作),那麼會發生lock_ordinary , 再退化成lock_rec_not_gap
- 唯一索引的範圍條件加鎖時,也會對第一個不滿足條件的記錄加鎖
InnoDB隱式、顯式鎖
- 顯式鎖(explicit-lock)
- select .. from .. where .. for update / for share
- 隱式鎖(implicit-lock)
- update set .. where ..
- 任何輔助索引上鎖,或非索引列上鎖,都要回溯到主鍵上再加鎖。
- 和其他session有衝突時,隱式鎖轉換為顯式鎖。
InnoDB鎖型別
共享鎖
select .. for share/ lock in share mode
- 不允許其他事務修改被鎖定的行,只能讀
- 自動提交模式下的普通select是一致性非鎖定讀,不加鎖。
排他鎖
- 對一行記錄DML時,至少加上排他鎖
- 鎖範圍視情況而定,可能是record lock、next-key lock、或者可能只有 gap lock
- 執行DML,或select.. for update
意向鎖
- InnoDB特有,載入在表級別上的鎖。
- Intention shared(IS),事務想要獲得表中某幾行的共享鎖
- Intention exclusive(IX), 事務想要獲得表中某幾行的排他鎖
- 意向鎖時載入在資料表B+樹結構的根節點,也就是對整個表加意向鎖
- 意向鎖的作用,避免在執行DML時,對錶執行DDL操作導致資料不一致
- IS和IX 是可以相容的。
InnoDB鎖相容性
X | IX | S | IS | AutoInc | |
---|---|---|---|---|---|
X | × | × | × | × | × |
IX | × | √ | × | √ | √ |
S | × | × | √ | √ | × |
IS | × | √ | √ | √ | √ |
AutoInc | × | √ | × | √ | × |
InnoDB行鎖範圍、粒度
InnoDB對行鎖有進一步的細粒度:
- LOCK_REC_NOT_GAP,record lock without gap lock.
- LOCK_GAP,gap lock
- LOCK_ORDINARY,next-key lock = record lock + gap lock ,普通輔助索引RR級別的加鎖範圍。
- LOCK_INSERT_INTENTION
InnoDB行鎖粒度一覽
lock wait 表示等待鎖。
lock_ordinary | next-key lock,普通鎖,LOCK_S record lock + gap lock ,next-key lock | 鎖定記錄本身和前面的gap,record lock + gap lock (也叫next-key lock) RR級別下,利用next-key lock來避免產生幻讀 當innodb_locks_unsafe_for_binlog=1時,lock_ordinary會降級為lock_rec_not_gap,相當於降級到RC。 8.0版本取消了引數innodb_locks_unsafe_for_binlog,即不再允許RR級別的幻讀情景。 |
---|---|---|
lock_gap | gap lock | 鎖定一個範圍,但不包含記錄本身。 只鎖住索引記錄之間、或第一條索引記錄(INFIMUM)之前、又或最後一條索引記錄(SUPEREMUM)之後的範圍,並不鎖住記錄本身 RR級別下,對非唯一索引記錄當前讀時,除了對命中的記錄加lock_ordinary鎖,還會對該記錄之後的gap加gap lock,這是為了保證可重複讀的需要,避免其他事務插入資料造成幻讀。 innodb有兩條虛擬記錄,最小記錄和最大記錄,用來構建B+tree。 如果條件是where <= n, 這時會從n開始到最小值(虛擬最小記錄)之間範圍加鎖 如果條件是where >= n, 這時會從n開始到最大值(虛擬最小記錄)之間範圍加鎖 |
lock_rec_not_gap | record lock,鎖定記錄,但不鎖gap。 | record lock,單個記錄上的鎖。 僅鎖住記錄本身,不鎖前面的gap RC下的行鎖大多數都是這個鎖型別 RR下的主鍵、唯一索引等值條件下加鎖也通常是這個型別鎖 RR下的非唯一索引加鎖時(lock_ordinary),也會同時回溯到主鍵上加lock_rec_not_gap鎖。 但唯一性約束檢測時,即使是在RC下,總是要先加lock_s\lock_ordinary鎖。 |
lock_insert_intention | 意向插入鎖 | 是一種特殊的gap lock。 當插入索引記錄的時候用來判斷是否有其他事務的範圍鎖衝突,如果有就需要等待。 同一個GAP中,只要不是同一個位置就可以有多個插入意向鎖並存。 例如5~10區間,同時插入6、8就不會相互衝突阻塞,而同時插入9就會引發衝突阻塞等待。 插入意向鎖和間隙鎖(gap lock)並不相容,一個gap加了lock gap後,無法再加insert_intention。 |
lock_conv_by_other 鎖時由其他事務建立的(比如隱式鎖轉換)
意向插入鎖的示意:
操作InnoDB表時的加鎖等級
- RR級別以及等值條件加鎖時:
- 主鍵索引等值條件加鎖為lock_rec_not_gap
- 唯一輔助索引等值條件加鎖為lock_rec_not_gap
- 普通輔助索引等值條件加鎖為lock_ordinary
- 沒有索引的話加鎖為全表範圍lock_ordinary
- RC級別以及5.7及以前版本 RR& innodb_locks_unsafe_for_binlog =1 時
- 預設只有lock_rec_not_gap,只有在檢查外來鍵約束或者duplicate ey檢查時才加lock_orainary | lock_s
MyISAM引擎有表鎖,InnoDB引擎也可以加表鎖。
InnoDB自增鎖 auto-inc lock
binlog_format=row時,可以放心的設定innodb_autoinc_lock_mode=2,降低自增鎖的影響。
5.1之後新增innodb_autoinc_lock_mode選項。5.1以前,所有自增鎖都是表級別鎖,5.1以後可以有不同的選項。
同樣的,也是在5.1以後binlog format支援多種方式(row,statement,mixed)。
- 傳統模式(模式為0):
- 對單表上的併發影響極大
- 當任何一條SQL要插入新資料, 都要求發起一個表級別自增鎖,請求得到最新的自增ID , sql執行完成後,表級別自增鎖釋放。
- 如果是多條資料的話,可能會造成嚴重的鎖等待。
- 可以保證主從時insert .. select一致性,但大量insert時併發效率很低
- 前預設模式(模式為1)
- 不再用鎖方式,改為mutex,先使用新方式預判一個動作大約會插入多少資料量,首先分配10個自增ID,用不完也不回收。當其它session請求自增ID時,會造成自增列自增空洞,不過影響不大。
- 如果遇到不確定的情況,如load data , insert select 時會繼續使用舊模式,使用表級別鎖,直到動作完成才會釋放表級別自增鎖。
- 新方式(模式為2,8.0.3開始預設為2)
- 模式為1時有退化,但是,由於binlog format=row時可以保證主從一致性,在保證主從一致性的前提下,自增鎖就可以統一退化成mutex模式,總是預估資料量、快速分配並釋放,這樣可以提高併發度。
- 不退化,古老版本不適合replication環境,可能造成主從資料不一致。但是8.0.3開始為預設值了,同樣的binlog_format預設值也是row了。
InnoDB自旋鎖 InnoDB spin lock
自旋鎖 保護共享資源而提出的鎖機制,和互斥鎖類似,在任何時刻下都只能有一個持有者,控制事務併發時CPU時間片分配。
可以利用自旋鎖的狀態來判斷InnoDB執行緒內部爭用嚴重與否。
- 用於控制InnoDB內部執行緒排程而存在著的輪詢檢測
- innodb_spin_wait_delay,控制輪詢間隔,預設為6毫秒。(A執行緒獲取CPU時間片後,B執行緒每隔6毫秒嘗試獲取CPU時間片的資源。)
- 當CPU負載非常高的時候可能也無法保證所有執行緒都能被合理的分配,這時會導致執行緒處於休眠狀態,spin round 可能也會很高。
另一種描述方式:
- 保障innodb內部執行緒的資源分配,innodb內部有很多工作執行緒,每個執行緒都要搶CPU的時間片。
- 自旋鎖來保障執行緒公平的分配CPU時間片。A執行緒獲取CPU時間片後,B執行緒輪詢嘗試獲取CPU時間片的資源。
- 當CPU負載非常高的時候可能也無法保證所有執行緒都能被合理的分配,這時會導致執行緒處於休眠狀態(長時間獲得不到資源,會識別為高負載,轉為sleep)。
通過自旋鎖狀態來判斷資料庫負載
-
檢視spin lock wait
mysql> show engine innodb status\G … ---------- SEMAPHORES ---------- OS WAIT ARRAY INFO: reservation count 239413 OS WAIT ARRAY INFO: signal count 560637 RW-shared spins 0, rounds 1028345, OS waits 118311 RW-excl spins 0, rounds 3590208, OS waits 45541 RW-sx spins 805351, rounds 5406426, OS waits 61835 Spin rounds per wait: 1028345.00 RW-shared, 3590208.00 RW-excl, 6.71 RW-sx ------------ - RW-shared spins 0 自旋0次, rounds 1028345 迴圈1028345圈, OS waits 118311 請求不到便sleep,sleep次數。 - OS waits / rounds - 118311 / 1028345= 0.115 - 45541 / 3590208 = 0.0127 - 61835 / 5406426 = 0.0114
- rounds, 表示spin一次空轉多少圈,也就是返回來詢問的次數。
- OS waits,表示sleep。當突然增長比較快時,說明latch爭用比較嚴重。
- 如果OS waits值比較高,說明latch爭用比較嚴重。
- OS waits/rounds 超過1% 說明系統負載比較高。
- OS wait 比較大的話, 重點查buffer pool是否夠用,以及是否有很多SQL沒有使用索引,導致持有innodb page時間較長。
- OS waits,表示sleep。當突然增長比較快時,說明latch爭用比較嚴重。
---------- SEMAPHORES ---------- OS WAIT ARRAY INFO: reservation count 596113 OS WAIT ARRAY INFO: signal count 846843 RW-shared spins 0, rounds 4277086, OS waits 137734 RW-excl spins 0, rounds 22496950, OS waits 218313 RW-sx spins 637341, rounds 11383745, OS waits 170045 Spin rounds per wait: 4277086.00 RW-shared, 22496950.00 RW-excl, 17.86 RW-sx 218313/22496950 = 0.0097 170045/11383745 = 0.0149
- rounds, 表示spin一次空轉多少圈,也就是返回來詢問的次數。
InnoDB 行鎖
-
預設都是加lock_ordinary鎖
-
如果是唯一索引列上的等值查詢,則退化成lock_rec_not_gap
-
所有版本,非唯一索引列上的範圍查詢,遇到第一個不符合條件的記錄也會加上lock_ordinary。
-
8.0.18版本以前,主要指<=場景:唯一索引列上的範圍查詢,遇到第一個不符合條件的記錄也會加上lock_ordinary ,在RC下會釋放,RR下不會釋放。
-
8.0.18版本以前,非唯一索引列上的等值查詢,向右遍歷遇到第一個不符合條件的記錄時,先加上lock_ordinary,再退化成lock_gap。
鎖排查可以用的檢視和資料字典
mysql> show engine innodb status \G
mysql> select * from performance_schema.data_lock_waits;
mysql> select * from performance_schema.data_locks;
mysql> select * from performance_schema.metadata_locks;
檢視InnoDB鎖
- 檢視InnoDB鎖
- show global status
Innodb_row_lock_current_waits | 當前等待的行鎖數量 (這個可能不準確。當前即便沒有發生,可能也大於0 .使用 select count(*) from sys.innodb_lock_waits 來確認是否真有行鎖發生。) |
---|---|
Innodb_row_lock_time | 請求行鎖總耗時(ms) |
Innodb_row_lock_time_avg | 請求行鎖平均耗時(ms) |
Innodb_row_lock_time_max | 請求行鎖最大耗時(ms) |
Innodb_row_lock_waits | 行鎖發生次數 |
-
show processlist
-
show engine innodb status
-
sys var: innodb_status_output & innodb_status_output_locks
-
sys.innodb_lock_waits & sys.schema_table_lock_waits
-
pfs.data_locks , 老版本是 innodb_locks
-
pfs.data_lock_waits
-
pfs.metadata_locks
InnoDB 行鎖相容性
請求的鎖型別 | 請求的鎖型別 | 請求的鎖型別 | 請求的鎖型別 | ||
---|---|---|---|---|---|
lock_ordinary | lock_rec_not_gap | lock_gap | lock_insert_intention | ||
已獲得的鎖型別 | lock_ordinary | X | X | O | X |
已獲得的鎖型別 | lock_rec_not_gap | X | X | O | O |
已獲得的鎖型別 | lock_gap | O | O | O | X |
已獲得的鎖型別 | lock_insert_intention | O | O | O | O |
-
gap只和insert intention鎖衝突
-
insert intention和任何鎖都不衝突,除非也在相同位置做意向插入鎖
-
先獲得意向插入鎖的,再嘗試上gap lock是可以的
-
但是反過來 ,先獲得gap lock的,再嘗試加上意向插入鎖便會阻塞,
-
原因是:先獲得意向插入鎖時,實際上插入已經成功,意向插入鎖會被轉變為對具體記錄的ordinary 或 rec_not_gap ,此時二者都與lock gap相容。
InnoDB 讀模式
快照讀和當前讀。
快照讀,snapshot read
- 基於read view 讀可見版本,不加鎖
- start transaction with consistent read + select
- 普通select
- 一致性快照讀需要RR
- 發起RR級別,再發起快照讀,再執行select。
快照 read view
- 由基於某個時間點的一組InnoDB內部活躍事務構建而成的列表
- 發起一個快照讀時,將當前InnoDB內部活躍事務加入列表,活躍事務會記錄影響了哪些資料。
- 讀資料時,每條資料頭部資訊都有資料最新事務的id版本號,可以判斷讀到資料版本號和read view的關係, 大於小於還是在範圍內, 來確定是要直接讀版本,還是要讀舊版本資料。
當前讀,current read
- 讀(已提交的)最新版本,並加鎖
- S鎖,
select ..lock in share mode
- X鎖,
select ..for update /DML
思考和討論
-
那些情況下會觸發整個例項都可能 不可讀寫 的全域性鎖?
-
用xtrabackup備份全例項資料時,會造成鎖等待嗎? 如果是mysqldump呢?
-
會話1發起backup lock,會話2執行mysqldump/xtrabackup備份,會被阻塞嗎?
mysql1> begin; Query OK, 0 rows affected (0.00 sec) mysql1> flush table with read lock; Query OK, 0 rows affected (0.00 sec) mysql1> create database oo; ERROR 1223 (HY000): Can't execute the query because you have a conflicting read lock mysql2> create database oo; --hang mysql3> select * from metadata_locks; +-------------+--------------------+----------------+-------------+-----------------------+---------------------+---------------+-------------+-------------------+-----------------+----------------+ | OBJECT_TYPE | OBJECT_SCHEMA | OBJECT_NAME | COLUMN_NAME | OBJECT_INSTANCE_BEGIN | LOCK_TYPE | LOCK_DURATION | LOCK_STATUS | SOURCE | OWNER_THREAD_ID | OWNER_EVENT_ID | +-------------+--------------------+----------------+-------------+-----------------------+---------------------+---------------+-------------+-------------------+-----------------+----------------+ | GLOBAL | NULL | NULL | NULL | 139619211751216 | SHARED | EXPLICIT | GRANTED | lock.cc:1035 | 63 | 43 | | COMMIT | NULL | NULL | NULL | 139619186354560 | SHARED | EXPLICIT | GRANTED | lock.cc:1110 | 63 | 43 | |*GLOBAL | NULL | NULL | NULL | 139618850829760 | INTENTION_EXCLUSIVE | STATEMENT |*PENDING | lock.cc:747 | 65 | 5 | | TABLE | performance_schema | metadata_locks | NULL | 139619054809168 | SHARED_READ | TRANSACTION | GRANTED | sql_parse.cc:6052 | 64 | 261 | +-------------+--------------------+----------------+-------------+-----------------------+---------------------+---------------+-------------+-------------------+-----------------+----------------+ 4 rows in set (0.01 sec)
換一個順序
mysql1> lock instance for backup; Query OK, 0 rows affected (0.00 sec) mysql3> select * from metadata_locks; +-------------+--------------------+----------------+-------------+-----------------------+-------------+---------------+-------------+------------------------+-----------------+----------------+ | OBJECT_TYPE | OBJECT_SCHEMA | OBJECT_NAME | COLUMN_NAME | OBJECT_INSTANCE_BEGIN | LOCK_TYPE | LOCK_DURATION | LOCK_STATUS | SOURCE | OWNER_THREAD_ID | OWNER_EVENT_ID | +-------------+--------------------+----------------+-------------+-----------------------+-------------+---------------+-------------+------------------------+-----------------+----------------+ |*BACKUP LOCK | NULL | NULL | NULL | 139619211751216 | SHARED | EXPLICIT | GRANTED | sql_backup_lock.cc:101 | 63 | 46 | | TABLE | performance_schema | metadata_locks | NULL | 139619054809168 | SHARED_READ | TRANSACTION | GRANTED | sql_parse.cc:6052 | 64 | 263 | +-------------+--------------------+----------------+-------------+-----------------------+-------------+---------------+-------------+------------------------+-----------------+----------------+ 2 rows in set (0.00 sec) mysql2> begin; Query OK, 0 rows affected (0.00 sec) mysql2> flush table with read lock; Query OK, 0 rows affected (0.00 sec) mysql3> select * from metadata_locks; +-------------+--------------------+----------------+-------------+-----------------------+-------------+---------------+-------------+------------------------+-----------------+----------------+ | OBJECT_TYPE | OBJECT_SCHEMA | OBJECT_NAME | COLUMN_NAME | OBJECT_INSTANCE_BEGIN | LOCK_TYPE | LOCK_DURATION | LOCK_STATUS | SOURCE | OWNER_THREAD_ID | OWNER_EVENT_ID | +-------------+--------------------+----------------+-------------+-----------------------+-------------+---------------+-------------+------------------------+-----------------+----------------+ | BACKUP LOCK | NULL | NULL | NULL | 139619211751216 | SHARED | EXPLICIT | GRANTED | sql_backup_lock.cc:101 | 63 | 46 | |*GLOBAL | NULL | NULL | NULL | 139618851123296 | SHARED | EXPLICIT | GRANTED | lock.cc:1035 | 65 | 10 | |*COMMIT | NULL | NULL | NULL | 139618850764288 | SHARED | EXPLICIT | GRANTED | lock.cc:1110 | 65 | 10 | | TABLE | performance_schema | metadata_locks | NULL | 139619053138368 | SHARED_READ | TRANSACTION | GRANTED | sql_parse.cc:6052 | 64 | 266 | +-------------+--------------------+----------------+-------------+-----------------------+-------------+---------------+-------------+------------------------+-----------------+----------------+ 4 rows in set (0.00 sec) mysql1> create database oo; --hang mysql3> select * from metadata_locks; +-------------+--------------------+----------------+-------------+-----------------------+---------------------+---------------+-------------+------------------------+-----------------+----------------+ | OBJECT_TYPE | OBJECT_SCHEMA | OBJECT_NAME | COLUMN_NAME | OBJECT_INSTANCE_BEGIN | LOCK_TYPE | LOCK_DURATION | LOCK_STATUS | SOURCE | OWNER_THREAD_ID | OWNER_EVENT_ID | +-------------+--------------------+----------------+-------------+-----------------------+---------------------+---------------+-------------+------------------------+-----------------+----------------+ | BACKUP LOCK | NULL | NULL | NULL | 139619211751216 | SHARED | EXPLICIT | GRANTED | sql_backup_lock.cc:101 | 63 | 46 | | GLOBAL | NULL | NULL | NULL | 139618851123296 | SHARED | EXPLICIT | GRANTED | lock.cc:1035 | 65 | 10 | | COMMIT | NULL | NULL | NULL | 139618850764288 | SHARED | EXPLICIT | GRANTED | lock.cc:1110 | 65 | 10 | |*GLOBAL | NULL | NULL | NULL | 139619186354560 | INTENTION_EXCLUSIVE | STATEMENT | PENDING | lock.cc:747 | 63 | 47 | | TABLE | performance_schema | metadata_locks | NULL | 139619053138368 | SHARED_READ | TRANSACTION | GRANTED | sql_parse.cc:6052 | 64 | 267 | +-------------+--------------------+----------------+-------------+-----------------------+---------------------+---------------+-------------+------------------------+-----------------+----------------+ 5 rows in set (0.00 sec) mysql2> unlock tables; --release FTWRL Query OK, 0 rows affected (0.00 sec) mysql1> lock instance for backup; ----前面的備份鎖還沒釋放 Query OK, 0 rows affected (0.00 sec) mysql1> create database oo; ----阻塞的DDL事務恢復執行了。 ERROR 1007 (HY000): Can't create database 'oo'; database exists mysql1> create database ooo; ----再執行一個DDL,成功。此時備份鎖還在呢。 Query OK, 1 row affected (0.21 sec) mysql3> select * from metadata_locks; ---備份鎖還在噢。 +-------------+--------------------+----------------+-------------+-----------------------+-------------+---------------+-------------+------------------------+-----------------+----------------+ | OBJECT_TYPE | OBJECT_SCHEMA | OBJECT_NAME | COLUMN_NAME | OBJECT_INSTANCE_BEGIN | LOCK_TYPE | LOCK_DURATION | LOCK_STATUS | SOURCE | OWNER_THREAD_ID | OWNER_EVENT_ID | +-------------+--------------------+----------------+-------------+-----------------------+-------------+---------------+-------------+------------------------+-----------------+----------------+ | BACKUP LOCK | NULL | NULL | NULL | 139619211751216 | SHARED | EXPLICIT | GRANTED | sql_backup_lock.cc:101 | 63 | 46 | | TABLE | performance_schema | metadata_locks | NULL | 139619053138368 | SHARED_READ | TRANSACTION | GRANTED | sql_parse.cc:6052 | 64 | 269 | +-------------+--------------------+----------------+-------------+-----------------------+-------------+---------------+-------------+------------------------+-----------------+----------------+ 2 rows in set (0.00 sec)
死鎖
- 如果多個事務都需要訪問資料,另一個事務已經以互斥方式鎖定該資料,則會發生死鎖。
- 事務A等待事務B,同時事務B等待事務A,會產生死鎖
- InnoDB有死鎖檢測執行緒,如果檢測到死鎖,會馬上丟擲異常並回滾一個事務,回滾原則為“回滾代價較小的、影響較小的事務”,例如產生undo較少的事務會被回滾。
- 如何判斷事務之間是否會發生死鎖?
-
事務T1需要等待事務T2,畫一條T1到T2的線
-
以此類推
-
圖中如果有迴路就表示有死鎖。
-
- 使用show engine innodb status 可以檢視到最後的死鎖資訊
- 可以設定innodb_print_all_deadlocks = 1 來使日誌中記錄全部死鎖資訊
- 高併發場景中(秒殺),關閉innodb_deadlock_detect選項,降低死鎖檢測的開銷,提高併發效率。同時降低innodb_lock_wait_timeout,縮短鎖等待時間。
- 表級鎖不會發生死鎖,但是也無法讀寫併發執行。
關於死鎖
-
偶爾死鎖不可怕,頻繁死鎖才需要關注
-
程式中應有事務失敗檢測及自動重複提交機制
-
多用小事務,並及時顯式提交/回滾
-
調整事務隔離級別為RC,以消除gap lock,降低死鎖發生概率
-
事務中涉及多個表,或者涉及多行記錄時,每個事務的操作順序都要保持一致,降低死鎖概率,最好用儲存過程/儲存函式固化
-
通過索引優化SQL效率,降低死鎖概率
-
死鎖不是“鎖死”,死鎖會快速檢測到,快速回滾。而“鎖死”則是行時間鎖等待。
-
innodb_rollback_on_timeout = on 時,一旦sql超時,整個事務回滾。
鎖優化
InnoDB鎖優化
- 儘可能讓所有的資料檢索都通過索引來完成,從而避免InnoDB因為無法通過索引鍵加鎖而升級為全表記錄級鎖
- 合理設計索引,讓InnoDB在索引鍵上面加鎖的時候儘可能準確,儘可能的縮小鎖定範圍,避免造成不必要的鎖定而影響其他query執行
- 儘可能減少範圍資料檢索過濾條件,降低過多的資料被加上lock_ordinary
- 多使用primary key或者unique key
MySQL鎖優化
- 避免MyISAM,改用InnoDB
- 多使用primary key或者unique key
- 確保所有SQL都能走索引
- 檢查索引定義,提高索引效率
- 多用等值查詢,減少範圍查詢
- 避免大事務,長事務
常見SQL的鎖模式
select … from | 一致性非鎖定讀 如果是serializable級別:Lock_ordinary|S |
---|---|
lock in share mode | Lock_ordinary |
for update | Lock_ordinary |
update/delete | Lock_ordinary |
update t … where col in (select .. from s ..) | s表加Lock_ordinary |
普通 insert | Lock_insert_intention|X 寫入請求檢測到有重複值時,會加鎖Lock_ordinary|X,可能引發死鎖 |
insert… on duplicate key update | Lock_ordinary |
insert into t select … from s | t表加Lock_rec_not_gap | X s表加Lock_ordinary | S 隔離級別為RC或啟用innodb_locks_unsafe_for_binlog時,s表上採用無鎖一致性讀, 即:RC不加鎖,RR加nextkey-lock |
create table … select | 同 insert.. select |
replace | 無衝突/重複值時,和insert一樣:Lock_insert_intention | X, 否則Lock_ordinary | X |
replace into t select .. from s where | s表加Lock_ordinary |
auto_increment列上寫新資料時 | 索引末尾加 record lock |
請求自增列計數器時,InnoDB使用一個auto-inc mutex, 但只對請求的那個SQL有影響(lock_mode = 1 時) | -------------------------------- |
有外來鍵約束欄位上進行insert/update/delete操作時 | 除了自身的鎖,還會在外表約束列上同時加Lock_rec_not_gap |
nextkey-lock 只發生在RR隔離級別下 |