MySQL鎖:03.InnoDB行鎖

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

經過索引實現行鎖,在索引記錄上加鎖。mysql

沒有索引就沒法實現行鎖,升級成全表記錄鎖,等同於表鎖。sql

理解InnoDB獨特的行鎖運行機制,認識特有的四種行鎖粒度——lock_ordinary、lock_gap、lock_rec_not_gap、lock_insert_intentionsession

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 行鎖兼容性

請求的鎖類型 請求的鎖類型 請求的鎖類型 請求的鎖類型
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鎖衝突spa

  • insert intention和任何鎖都不衝突,除非也在相同位置作意向插入鎖code

  • 先得到意向插入鎖的,再嘗試上gap lock是能夠的orm

  • 可是反過來 ,先得到gap lock的,再嘗試加上意向插入鎖便會阻塞,htm

  • 緣由是:先得到意向插入鎖時,實際上插入已經成功,意向插入鎖會被轉變爲對具體記錄的ordinary 或 rec_not_gap ,此時兩者都與lock gap兼容。blog

InnoDB行鎖之共享鎖

共享鎖:

  • 不容許其餘事務修改被鎖定的行,只能讀
  • select .. for share/ lock in share mode
  • 自動提交模式下的普通select是一致性非鎖定讀,不加鎖。

自動提交模式下, 不使用begin開啓事務,直接select的話:索引

select * from xxx where .. 不加鎖

select * from xxx where .. for share ,也查詢不到加鎖, 可是其實是加鎖的,只不過鎖的時間及其的短暫。

驗證:

此時,用排他鎖來驗證自動提交模式的for share到底是否產生鎖動做。

能夠看出,自動提交模式下select(不加for share)是一致性非鎖定讀,可是加for share後,是會有鎖定動做的,只不過沒有阻塞的狀況下,鎖的持續時間是很是短暫的。

查看InnoDB鎖

  • 開啓參數:innodb_status_output_locks=1; 以支持使用 show engine innodb status 查看鎖詳情。

    mysql> begin ; select * from k1 where id=4 for update;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> show engine innodb status \G
    ..
    MySQL thread id 31, OS thread handle 139620328457984, query id 1297 localhost root
    **TABLE LOCK table `kk`.`k1` trx id 1901 lock mode IX**
    RECORD LOCKS space id 3 page no 10 n bits 1056 index id of table `kk`.`k1` trx id 1901 lock_mode X
    Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
     0: len 4; hex 80000004; asc   ;;
     1: len 6; hex 000000000902; asc    ;;
     
    RECORD LOCKS space id 3 page no 9 n bits 320 index GEN_CLUST_INDEX of table `kk`.`k1` trx id 1901 lock_mode X locks rec but not gap
    Record lock, heap no 248 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
     0: len 6; hex 000000000902; asc    ;;
     1: len 6; hex 000000000663; asc   c;;
     2: len 7; hex 82000000940110; asc    ;;
     3: len 4; hex 80000004; asc   ;;
     4: SQL NULL;
     5: SQL NULL;
     
    RECORD LOCKS space id 3 page no 10 n bits 1056 index id of table `kk`.`k1` trx id 1901 lock_mode X locks gap before rec
    Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
     0: len 4; hex 80000016; asc   ;;
     1: len 6; hex 000000000200; asc    ;;
    ..
    
    - space id 3 表的表空間ID
    - page no 10 鎖所在datapage的ID
    - heap no 4,slot no,記錄在page物理上的第幾個位置。
  • 也能夠查詢P_S表

    mysql> select * from performance_schema.data_locks;
    +--------+----------------------------------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+---------------+-------------+-----------+
    | ENGINE | ENGINE_LOCK_ID             | ENGINE_TRANSACTION_ID | THREAD_ID | EVENT_ID | OBJECT_SCHEMA | OBJECT_NAME | PARTITION_NAME | SUBPARTITION_NAME | INDEX_NAME | OBJECT_INSTANCE_BEGIN | LOCK_TYPE | LOCK_MODE   | LOCK_STATUS | LOCK_DATA |
    +--------+----------------------------------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+---------------+-------------+-----------+
    | INNODB | 139620969519720:1072:139620864029240  |         2211 |    85 |    79 | kk      | k1     | NULL      | NULL       | NULL    |    139620864029240 | TABLE   | IX      | GRANTED   | NULL   |
    | INNODB | 139620969519720:15:4:5:139620864026200 |         2211 |    85 |    79 | kk      | k1     | NULL      | NULL       | PRIMARY  |    139620864026200 | RECORD  | X,REC_NOT_GAP | GRANTED   | 4     |
    +--------+----------------------------------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+---------------+-------------+-----------+
    2 rows in set (0.00 sec)
  • 再看下IS鎖的狀況

mysql> begin ; select * from k1 where id=4 for share;
Query OK, 0 rows affected (0.00 sec)
 
mysql> show engine innodb status \G
看不到IS鎖信息。
1 row in set (0.00 sec)
 
mysql> select * from performance_schema.data_locks;
+--------+----------------------------------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+---------------+-------------+-----------+
| ENGINE | ENGINE_LOCK_ID             | ENGINE_TRANSACTION_ID | THREAD_ID | EVENT_ID | OBJECT_SCHEMA | OBJECT_NAME | PARTITION_NAME | SUBPARTITION_NAME | INDEX_NAME | OBJECT_INSTANCE_BEGIN | LOCK_TYPE | LOCK_MODE   | LOCK_STATUS | LOCK_DATA |
+--------+----------------------------------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+---------------+-------------+-----------+
| INNODB | 139620969519720:1072:139620864029240  |    421095946230376 |    85 |    83 | kk      | k1     | NULL      | NULL       | NULL    |    139620864029240 | TABLE   | IS      | GRANTED   | NULL   |
| INNODB | 139620969519720:15:4:5:139620864026200 |    421095946230376 |    85 |    83 | kk      | k1     | NULL      | NULL       | PRIMARY  |    139620864026200 | RECORD  | S,REC_NOT_GAP | GRANTED   | 4     |
+--------+----------------------------------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+---------------+-------------+-----------+
2 rows in set (0.00 sec)

驗證一下IS和IX的兼容

t1:
mysql> begin ; select * from k1 where id=4 for share;
Query OK, 0 rows affected (0.00 sec)
 
+------+------+------+
| id  | dtl | name |
+------+------+------+
|  4 | NULL | NULL |
+------+------+------+
1 row in set (0.00 sec)
t2:
mysql> begin ; select * from k1 where id=11 for update;
Query OK, 0 rows affected (0.00 sec)
 
Empty set (0.00 sec)
 
--注意:加鎖加不在相同行,不然hang。
 
t3:
mysql> select * from performance_schema.data_locks;
+--------+----------------------------------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+---------------+-------------+------------------------+
| ENGINE | ENGINE_LOCK_ID             | ENGINE_TRANSACTION_ID | THREAD_ID | EVENT_ID | OBJECT_SCHEMA | OBJECT_NAME | PARTITION_NAME | SUBPARTITION_NAME | INDEX_NAME | OBJECT_INSTANCE_BEGIN | LOCK_TYPE | LOCK_MODE   | LOCK_STATUS | LOCK_DATA       |
+--------+----------------------------------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+---------------+-------------+------------------------+
| INNODB | 139620969519720:1072:139620864029240  |         2212 |    85 |    89 | kk      | k1     | NULL      | NULL       | NULL    |    139620864029240 | TABLE   | IX      | GRANTED   | NULL          |
| INNODB | 139620969519720:15:4:1:139620864026200 |         2212 |    85 |    89 | kk      | k1     | NULL      | NULL       | PRIMARY  |    139620864026200 | RECORD  | X       | GRANTED   | supremum pseudo-record |
| INNODB | 139620969521464:1072:139620864041176  |    421095946232120 |    84 |   110 | kk      | k1     | NULL      | NULL       | NULL    |    139620864041176 | TABLE   | IS      | GRANTED   | NULL          |
| INNODB | 139620969521464:15:4:5:139620864038296 |    421095946232120 |    84 |   110 | kk      | k1     | NULL      | NULL       | PRIMARY  |    139620864038296 | RECORD  | S,REC_NOT_GAP | GRANTED   | 4           |
+--------+----------------------------------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+---------------+-------------+------------------------+
4 rows in set (0.00 sec)

InnoDB行鎖實現機制

  • 基於索引實現,逐行檢查,逐行加鎖
  • 沒有索引的列上須要加鎖時,會先對全部記錄加鎖,再根據實際狀況決定是否釋放鎖。
  • 輔助索引上加鎖時,同時要回溯到主鍵索引上再加一次鎖。
  • 加鎖的基本單位默認時lock_ordinary,當索引就具備惟一性的時候退化爲lock_rec_not_gap
  • 等值條件逐行加鎖時,會向右遍歷到第一個不知足條件的記錄,而後lock_ordinary退化爲lock_gap
  • 若是發生惟一性檢測(insert\update動做),那麼會發生lock_ordinary , 再退化成lock_rec_not_gap
  • 惟一索引的範圍條件加鎖時,也會對第一個不知足條件的記錄加鎖

對普通索引上鎖

普通索引next-key lock + 主鍵 not gap + 普通索引的下一個記錄的gap lock(見示意圖)。

mysql> select * from k2;
+----+------+------+
| id | dtl  | un   |
+----+------+------+
| 1  |  1   |  1   |
| 2  |  2   |  2   |
| 3  |  5   |  5   |  (回溯到pk上鎖)
------------------------------------------  <- gap (同時鎖住普通索引下一個記錄前的gap)
| 4  |  7   |  7   |*
| 5  |  11  |  11  |
+----+------+------+
5 rows in set (0.00 sec)
  • 輔助索引上鎖的驗證明驗
mysql> show create table k2\G
*************************** 1. row ***************************
    Table: k2
Create Table: CREATE TABLE `k2` (
 `id` int NOT NULL AUTO_INCREMENT,
 `dtl` int DEFAULT NULL,
 `un` int DEFAULT NULL,
 PRIMARY KEY (`id`),
 UNIQUE KEY `dtl` (`dtl`),
 KEY `un` (`un`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
 
mysql> select * from k2;
+----+------+------+
| id | dtl | un  |
+----+------+------+
| 1 |  1 |  1 |
| 2 |  2 |  2 |
| 3 |  5 |  5 |
|  4 |  7 |  7 |*
| 5 |  11 |  11 |
+----+------+------+
5 rows in set (0.00 sec)
 
mysql> begin ; select * from k2 where un=5 for update;
Query OK, 0 rows affected (0.00 sec)
 
+----+------+------+
| id | dtl | un  |
+----+------+------+
| 3 |  5 |  5 |
+----+------+------+
1 row in set (0.00 sec)
 
mysql> select * from performance_schema.data_locks\G
*************************** 1. row ***************************
        ENGINE: INNODB
    ENGINE_LOCK_ID: 139620969521464:1061:139620864041176
ENGINE_TRANSACTION_ID: 1944
      THREAD_ID: 70
       EVENT_ID: 65
    OBJECT_SCHEMA: kk
     OBJECT_NAME: k2
    PARTITION_NAME: NULL
  SUBPARTITION_NAME: NULL
      INDEX_NAME: NULL
OBJECT_INSTANCE_BEGIN: 139620864041176
       LOCK_TYPE: TABLE*
      LOCK_MODE: IX*
     LOCK_STATUS: GRANTED
      LOCK_DATA: NULL
*************************** 2. row ***************************
        ENGINE: INNODB
    ENGINE_LOCK_ID: 139620969521464:4:6:9:139620864038296
ENGINE_TRANSACTION_ID: 1944
      THREAD_ID: 70
       EVENT_ID: 65
    OBJECT_SCHEMA: kk
     OBJECT_NAME: k2
    PARTITION_NAME: NULL
  SUBPARTITION_NAME: NULL
      INDEX_NAME: un*
OBJECT_INSTANCE_BEGIN: 139620864038296
      LOCK_TYPE: RECORD (nextkey-lock)*
      LOCK_MODE: X
     LOCK_STATUS: GRANTED
       LOCK_DATA: 5, 3    (un key value, primary key value, index Condion特性)*
*************************** 3. row ***************************
        ENGINE: INNODB
    ENGINE_LOCK_ID: 139620969521464:4:4:11:139620864038640
ENGINE_TRANSACTION_ID: 1944
      THREAD_ID: 70
       EVENT_ID: 65
    OBJECT_SCHEMA: kk
     OBJECT_NAME: k2
    PARTITION_NAME: NULL
  SUBPARTITION_NAME: NULL
      INDEX_NAME: PRIMARY*
OBJECT_INSTANCE_BEGIN: 139620864038640
      LOCK_TYPE: RECORD
       LOCK_MODE: X,REC_NOT_GAP*
     LOCK_STATUS: GRANTED
      LOCK_DATA: 3*
*************************** 4. row ***************************
        ENGINE: INNODB
    ENGINE_LOCK_ID: 139620969521464:4:6:10:139620864038984
ENGINE_TRANSACTION_ID: 1944
      THREAD_ID: 70
       EVENT_ID: 65
    OBJECT_SCHEMA: kk
     OBJECT_NAME: k2
    PARTITION_NAME: NULL
  SUBPARTITION_NAME: NULL
      INDEX_NAME: un*
OBJECT_INSTANCE_BEGIN: 139620864038984
      LOCK_TYPE: RECORD
      LOCK_MODE: X,GAP*      確認下一個記錄不符合條件,回退nextkey-lock 爲 lock-gap.
     LOCK_STATUS: GRANTED
      LOCK_DATA: 7, 4   (un key value, primary key value, index Condion特性)*
4 rows in set (0.00 sec)

InnoDB隱式、顯式鎖

  • 顯式鎖(explicit-lock)

    • select .. from .. where .. for update / for share
  • 隱式鎖(implicit-lock)

    • update set .. where ..
    • 任何輔助索引上鎖,或非索引列上鎖,都要回溯到主鍵上再加鎖。
    • 和其餘session有衝突時,隱式鎖轉換爲顯式鎖。(沒實驗驗證出來。)

若是發生惟一性檢測(insert\update動做),那麼會發生lock_ordinary , 再退化成lock_rec_not_gap

相關文章
相關標籤/搜索