InnoDB中鎖的模式

Ⅰ、總覽

  • S行級共享鎖
    lock in share mode
  • X行級排它鎖
    增刪改
  • IS意向共享鎖
  • IX意向排他鎖
  • AI自增鎖

Ⅱ、鎖之間的兼容性

X IX S IS
X × × × ×
IX × ×
S × ×
IS ×

2.1 意向鎖

意向鎖揭示了下一層級請求的鎖類型,意向鎖全兼容mysql

  • IS:事務想要得到一張表中某幾行的共享鎖
  • IX:事務想要得到一張表中某幾行的排它鎖

InnoDB存儲引擎中意向鎖都是表鎖,是否是讀下來很懵逼?sql

若是沒有意向鎖,當你去鎖一張表的時候,你就須要對錶下的全部記錄都進行加鎖操做,且對其餘事務剛剛插入的記錄(遊標已經掃過的範圍)就無法在上面加鎖了,此時就沒有實現鎖表的功能數據庫

對一棵樹加鎖的概念:
從上往下的,先加意向鎖再加記錄鎖,內存操做,很快,釋放操做則是從記錄鎖開始從下往上進行釋放服務器

假設數據庫四個層級,庫,表,頁,記錄session

假如此時有事務tx1須要在記錄A上進行加X鎖:
1. 在該記錄所在的數據庫上加一把意向鎖IX
2. 在該記錄所在的表上加一把意向鎖IX
3. 在該記錄所在的頁上加一把意向鎖IX
4. 最後在該記錄A上加上一把X鎖
假如此時有事務tx2須要對記錄B(假設和記錄A在同一個頁中)加S鎖:
1. 在該記錄所在的數據庫上加一把意向鎖IS
2. 在該記錄所在的表上加一把意向鎖IS
3. 在該記錄所在的頁上加一把意向鎖IS
4. 最後在該記錄B上加一把S鎖
假如此時有事務tx3須要在記錄A上進行加S鎖:
1. 在該記錄所在的數據庫上加一把意向鎖IS
2. 在該記錄所在的表上加一把意向鎖IS
3. 在該記錄所在的頁上加一把意向鎖IS
4. 發現該記錄被鎖定(tx1的X鎖),那麼tx3須要等待,直到tx1進行commit

tips:併發

  • 共享鎖和排它鎖不是說只能加在記錄級別上,是能夠加在各個級別上的
    innodb表鎖的獲取:
    lock table l read; lock table l write; unlock tables; 這是server層的鎖(mdl鎖)
    從原理上講innodb也是能夠對錶加X鎖的,可是沒有一個具體的命令來觸發,也能夠把lock table l read; 理解爲加X鎖性能

    一般來講不須要加表級別的鎖,mysqldump都不加,ddl不支持online的時候就是先對一張表先加一個S鎖,如今不同了測試

  • 爲何意向鎖都是互相兼容的?由於在當前級別上並無加鎖啊ui

可是在MySQL中沒有數據庫級別的鎖和頁級別的鎖,這就意味着一共就兩層,全部的意向鎖都是表鎖,意向鎖是innodb層級的spa

tips:
MySQL8.0中全部的鎖都在innodb層,如今的鎖一部分在innodb層一部分在server層,server層的很差理解

Ⅱ、自增鎖

  • 一個表一個自增列,自增鎖作自增併發處理
  • auto_increment pk 表明這個列的自增有一把鎖
  • 在事務提交前釋放
    其餘鎖在事務提交時才釋放
  • Think about
    insert ... select ...

tips:
MySQL的自增存在一個回溯的問題,5.7版本以前都是非持久化的,都是服務啓動時候執行下面這個sql獲取自增值,從下個位置開始繼續自增,若是數據庫重啓了,以前的自增值可能被重複使用,8.0已解決,這個值會被寫到元數據表(innodb引擎)中。

select max(auto_inc_col) from t for update;

2.1 自增列的約束

(root@localhost) [test]> create table t (a int auto_increment, b int) engine = innodb;
ERROR 1075 (42000): Incorrect table definition; there can be only one auto column and it must be defined as a key
(root@localhost) [test]> create table t (a int auto_increment, b int, key(b,a)) engine = innodb;
ERROR 1075 (42000): Incorrect table definition; there can be only one auto column and it must be defined as a key
(root@localhost) [test]> create table t (a int auto_increment, b int, key(a,b)) engine = innodb;
Query OK, 0 rows affected (0.04 sec)

InnoDB自增列必須被定義爲一個key,且必須是這個key的開始部分

WHY?

select max(auto_inc_col) from t for update;

避免重啓執行上面這句的時候掃全表 ,myisam是非彙集索引的,不是用這個方式來採集自增值的,8.0雖然持久化了,但仍是有這個限制

經測試,myisam自增列也須要被定義爲一個key,可是不須要是key的開始部分

2.2 自增的參數

(root@localhost) [test]> show variables like 'auto_increment%';
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| auto_increment_increment | 1     |    -- 步長
| auto_increment_offset    | 1     |    --初始值
+--------------------------+-------+
2 rows in set (0.01 sec)

多節點全局惟一
N臺服務器:A:[offset = 1, increment=N] , B:[offset = 2, increment=N] , C:[offset = 3, increment=N]...N:[offset = N, increment=N]

注意,這不能用來作多主,若是有額外的惟一索引就保證不了全局惟一了

2.3 自增鎖分析

session1:

(root@localhost) [test]> create table t_ai_l(a int auto_increment, b int, primary key(a));
Query OK, 0 rows affected (0.02 sec)

(root@localhost) [test]> begin;
Query OK, 0 rows affected (0.00 sec)

(root@localhost) [test]> insert into t_ai_l values(NULL, 10);
Query OK, 1 row affected (0.00 sec)

事務不提交

session2:

(root@localhost) [test]> begin;
Query OK, 0 rows affected (0.00 sec)

(root@localhost) [test]> insert into t_ai_l values(NULL, 20);
Query OK, 1 row affected (0.00 sec)

咦?沒等待耶,amazing!

AI鎖在事務提交前就釋放了,相似latch,使用完就釋放了

session1&2:

(root@localhost) [test]> rollback;
Query OK, 0 rows affected (0.02 sec)

session1:

(root@localhost) [test]> begin;
Query OK, 0 rows affected (0.00 sec)

(root@localhost) [test]> insert into t_ai_l values(NULL, 30);
Query OK, 1 row affected (0.00 sec)

(root@localhost) [test]> commit;
Query OK, 0 rows affected (0.00 sec)

(root@localhost) [test]> select * from t_ai_l;
+---+------+
| a | b    |
+---+------+
| 3 |   30 |
+---+------+
1 row in set (0.00 sec)

能夠看到雖然rollback,但AI鎖是提交過了的,自增值不會跟着回滾,這樣自增值就不連續,但連續也沒什麼用

也就是說,僅僅是這條sql執行的這段時間裏,其餘session是不能夠對這個表操做的,插入過程太長,對insert也會阻塞

執行這條sql的時候,自增是被鎖住的,因此插進去以後都是連續的值

2.4 利用sleep()分析自增鎖

session1:

(root@localhost) [test]> begin;
Query OK, 0 rows affected (0.00 sec)

(root@localhost) [test]> insert into t_ai_l (a,b) select NULL, sleep(1) from tmp limit 10000;
~~~

session2:

(root@localhost) [test]> show engine innodb status\G
...
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 421958478908128, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 31217775, ACTIVE 10 sec
mysql tables in use 2, locked 2
4 lock struct(s), heap size 1136, 11 row lock(s), undo log entries 10
MySQL thread id 2255, OS thread handle 140482757068544, query id 3006342 localhost root User sleep
insert into t_ai_l (a,b) select NULL, sleep(1) from tmp limit 10000
TABLE LOCK table `test`.`tmp` trx id 31217775 lock mode IS
RECORD LOCKS space id 1408 page no 4 n bits 624 index PRIMARY of table `test`.`tmp` trx id 31217775 lock mode S
Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 4; hex 80000001; asc     ;;
 1: len 6; hex 000001cd15db; asc       ;;
 2: len 7; hex d4000001760110; asc     v  ;;
 3: len 4; hex 80000001; asc     ;;

Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 4; hex 80000002; asc     ;;
 1: len 6; hex 000001cd15dc; asc       ;;
 2: len 7; hex d5000001300110; asc     0  ;;
 3: len 4; hex 80000002; asc     ;;

...

TABLE LOCK table `test`.`t_ai_l` trx id 31217775 lock mode AUTO-INC
TABLE LOCK table `test`.`t_ai_l` trx id 31217775 lock mode IX
...

插入數據過程分析:

  • tmp表被加了IS鎖,表中記錄被加S鎖,注意不會一次性全部記錄加鎖,是被查到的記錄就被鎖住,最終事務結束後釋放全部鎖
  • t_ai_l表上有兩個鎖AUTO-INC和IX

session2:

(root@localhost) [test]> insert into t_ai_l (a,b) select NULL, sleep(1) from tmp limit 10000;
~~~

session3:

(root@localhost) [test]> show engine innodb status\G
...
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 421958478909040, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 31218060, ACTIVE 15 sec setting auto-inc lock
mysql tables in use 2, locked 2
LOCK WAIT 3 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 2255, OS thread handle 140482757068544, query id 3006385 localhost root Sending data
insert into t_ai_l (a,b) select NULL, b from tmp limit 10000
------- TRX HAS BEEN WAITING 15 SEC FOR THIS LOCK TO BE GRANTED:
TABLE LOCK table `test`.`t_ai_l` trx id 31218060 lock mode AUTO-INC waiting
------------------
TABLE LOCK table `test`.`tmp` trx id 31218060 lock mode IS
RECORD LOCKS space id 1408 page no 4 n bits 624 index PRIMARY of table `test`.`tmp` trx id 31218060 lock mode S
Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 4; hex 80000001; asc     ;;
 1: len 6; hex 000001cd15db; asc       ;;
 2: len 7; hex d4000001760110; asc     v  ;;
 3: len 4; hex 80000001; asc     ;;

TABLE LOCK table `test`.`t_ai_l` trx id 31218060 lock mode AUTO-INC waiting
---TRANSACTION 31218051, ACTIVE 40 sec
mysql tables in use 2, locked 2
4 lock struct(s), heap size 1136, 40 row lock(s), undo log entries 39
MySQL thread id 2254, OS thread handle 140482756536064, query id 3006383 localhost root User sleep
insert into t_ai_l (a,b) select NULL, sleep(1) from tmp limit 10000
TABLE LOCK table `test`.`tmp` trx id 31218051 lock mode IS
RECORD LOCKS space id 1408 page no 4 n bits 624 index PRIMARY of table `test`.`tmp` trx id 31218051 lock mode S
Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 4; hex 80000001; asc     ;;
 1: len 6; hex 000001cd15db; asc       ;;
 2: len 7; hex d4000001760110; asc     v  ;;
 3: len 4; hex 80000001; asc     ;;

Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 4; hex 80000002; asc     ;;
 1: len 6; hex 000001cd15dc; asc       ;;
 2: len 7; hex d5000001300110; asc     0  ;;
 3: len 4; hex 80000002; asc     ;;
...

insert into t_ai_l (a,b) select NULL, b from tmp limit 10000 在等待三個鎖

  • t_ai_l表上的AUTO-INC鎖
  • tmp表上的IS鎖
  • tmp表中第一條記錄上的S鎖

這樣設計的初衷是但願批量插入的自增值是連續的,但其實是犧牲了併發度的

2.5 自增鎖的分類

- 說明
insert-like 全部插入語句都屬於此類
simple inserts 插入以前能肯定插入多少行(insert into table_1 values(NULL, 1), (NULL, 2);)
bulk inserts 插入以前不肯定插入多少行(insert into table_1 select * from t;)
mixed-mode inserts 插入內容部分自增部分肯定(insert ... on duplicate key update不推薦)

2.6 如何提高自增併發度

(root@localhost) [test]> show variables like 'innodb_autoinc_lock_mode';
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| innodb_autoinc_lock_mode | 1     |
+--------------------------+-------+
1 row in set (0.00 sec)

此參數可設置爲[0|1|2]

  • 0 sql語句執行完釋放AI鎖,若數據量大sql執行完以前其餘事務是沒法插入的,保證了在此sql語句內插入的數據自增值是連續的
  • 1(default,大部分狀況用1) 對於bulk inserts,和設置0同樣

simple inserts則能夠併發插入,在sql運行完以前肯定自增值以後就能夠釋放AI鎖了

+
            bulk inserts    |   simple inserts
                            |
+-------------------------------------------------------+
                            |
       acquire AI_Lock      |   acquire AI_Lock
                            |
 insert ... select ...  |   ai = ai + M
                            | 
           ai = ai + N      |   release AI_Lock
                            |
       release AI_Lock      |   insert ... select ...
                            +
bulk inserts不知道要插入多少行,因此只能等insert結束後,才知道N的值,而後一次性(ai + N)
simple inserts知道插入的行數(M),因此能夠先(ai + M),而後將鎖釋放掉,給別的事務用,而後本身慢慢插入數據
  • 2 全部自增均可以併發(不一樣於Simple inserts的方式 ) 同一sql語句自增可能不連續

row-based binlog

for (i = ai; until_no_rec; i++) {
    acquire AI_Lock         # 插入前申請鎖
    insert one record...    # 只插入一條記錄
    ai = ai + 1             # 自增值+1
    release AI_Lock         # 釋放鎖
}
併發度增長了,但性能不必定變好,尤爲是單線程的時候,頻繁申請和釋放鎖會致使開銷大
雖然不連續,但插入進去至少是單調遞增因此基本知足業務需求

tips: 這種狀況嚴格意義上是不連續,但因爲併發度不夠再加上limit是預先批量申請分配這種不阻塞不是很好演示,因此看上去是連續的,其實不是,limit大一點應該是能夠的,但等待時間太長了,也能夠經過mysqlslap測測

相關文章
相關標籤/搜索