MySQL鎖:02.InnoDB鎖

傳送門:MySQL鎖:01.總覽
傳送門:MySQL鎖:02.InnoDB鎖
傳送門:MySQL鎖:03.InnoDB行鎖 html

InnoDB鎖

  • 默認是行鎖(row lock)
  • InnoDB是經過在索引記錄上加鎖,實現行鎖
  • 所以沒有索引時就沒法實現行鎖,從而升級成全表記錄鎖,等同於表鎖。
  • 索引效率很低時鎖也會升級。須要加鎖的數據量過多,也會直接升級鎖範圍。 由於這樣代價會低不少。

如同用書佔座,只有當其餘人想坐過來的時候,幫佔座的人才會出面提出該座位已經被佔用(被鎖)mysql

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 modesql

  • 不容許其餘事務修改被鎖定的行,只能讀
  • 自動提交模式下的普通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 表示等待鎖。session

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區間,同時插入六、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時間較長。
    ----------
    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

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

思考和討論

  1. 那些狀況下會觸發整個實例均可能 不可讀寫 的全局鎖?

  2. 用xtrabackup備份全實例數據時,會形成鎖等待嗎? 若是是mysqldump呢?

  3. 會話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隔離級別下
相關文章
相關標籤/搜索