1. 程式人生 > 實用技巧 >InnoDB鎖機制

InnoDB鎖機制

測試RC級別不同場景下鎖的處理

主鍵+RC

[root@db ~]# vim /etc/my.cnf
transaction_isolation=read-committed
autocommit=0
innodb_lock_wait_timeout=3600

重啟mysql,建立表

[root@db ~]# systemctl restart mysqld

mysql> show variables like '%wait%';
+---------------------------------------------------+----------+
| Variable_name                                     | Value    |
+---------------------------------------------------+----------+
| innodb_lock_wait_timeout                          | 3600
| | innodb_log_wait_for_flush_spin_hwm | 400 | | innodb_spin_wait_delay | 6 | | innodb_spin_wait_pause_multiplier | 50 | | lock_wait_timeout | 31536000 | | mysqlx_wait_timeout | 28800
| | performance_schema_events_waits_history_long_size | 10000 | | performance_schema_events_waits_history_size | 10 | | wait_timeout | 28800 | +---------------------------------------------------+----------+ 9 rows in set (0.01 sec) mysql> select @@transaction_isolation;
+-------------------------+ | @@transaction_isolation | +-------------------------+ | READ-COMMITTED | +-------------------------+ 1 row in set (0.00 sec) mysql> create table t1 (id int primary key,name varchar(10)); Query OK, 0 rows affected (0.19 sec) mysql> insert into t1 values(1,'tom'),(2,'jerry'),(3,'jack'); Query OK, 3 rows affected (0.01 sec) Records: 3 Duplicates: 0 Warnings: 0 mysql> select * from t1; +----+-------+ | id | name | +----+-------+ | 1 | tom | | 2 | jerry | | 3 | jack | +----+-------+ 3 rows in set (0.00 sec)

A終端更新資料

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> delete from t1 where id=3;
Query OK, 1 row affected (0.00 sec)

mysql> select * from t1;
+----+-------+
| id | name  |
+----+-------+
|  1 | tom   |
|  2 | jerry |
+----+-------+
2 rows in set (0.00 sec)

B終端嘗試修改資料

mysql> select * from t1;
+----+-------+
| id | name  |
+----+-------+
|  1 | tom   |
|  2 | jerry |
|  3 | jack  |
+----+-------+
3 rows in set (0.00 sec)

mysql> update t1 set name='john' where id =3;

#這時已經產生鎖等待了

檢視鎖情況

mysql> select * from sys.innodb_lock_waits\G;
*************************** 1. row ***************************
                wait_started: 2020-07-08 15:41:09
                    wait_age: 00:09:41
               wait_age_secs: 581                                   ## 等待的持續時間
                locked_table: `test`.`t1`                           ## 發生鎖的物件
         locked_table_schema: test                                  ## 鎖發生的庫
           locked_table_name: t1                                    ## 鎖發生的表
      locked_table_partition: NULL
   locked_table_subpartition: NULL
                locked_index: PRIMARY                               ## 發生鎖的鍵型別
                 locked_type: RECORD                                ## 鎖型別,表鎖、行鎖
              waiting_trx_id: 17175                                 ## 發生鎖的事務ID號
         waiting_trx_started: 2020-07-08 15:03:20
             waiting_trx_age: 00:47:30
     waiting_trx_rows_locked: 1                                     ## 鎖影響的行數
   waiting_trx_rows_modified: 0
                 waiting_pid: 9                                     ## 發生鎖等待的執行緒ID,與show processlist;語句的Id相同
               waiting_query: update t1 set name='john' where id =3  ## 發生鎖等待的語句
             waiting_lock_id: 139989104688592:146:4:4:139989023260296
           waiting_lock_mode: X,REC_NOT_GAP                         ## 鎖等待的模式
             blocking_trx_id: 17174                                  ## 阻塞其他語句的事務ID
                blocking_pid: 8                                      ## 阻塞其他語句執行的SQL執行緒ID
              blocking_query: NULL                                   ## 阻塞的語句是什麼
            blocking_lock_id: 139989104687720:146:4:4:139989023254232
          blocking_lock_mode: X,REC_NOT_GAP                          ## 阻塞的模式
        blocking_trx_started: 2020-07-08 15:02:45
            blocking_trx_age: 00:48:05
    blocking_trx_rows_locked: 1
  blocking_trx_rows_modified: 1
     sql_kill_blocking_query: KILL QUERY 8                           ## 解決鎖阻塞的辦法
sql_kill_blocking_connection: KILL 8
1 row in set (0.00 sec)

檢視連線執行緒ID

mysql> show processlist;
+----+-----------------+-----------+------+---------+------+------------------------+---------------------------------------+
| Id | User            | Host      | db   | Command | Time | State                  | Info                                  |
+----+-----------------+-----------+------+---------+------+------------------------+---------------------------------------+
|  4 | event_scheduler | localhost | NULL | Daemon  | 3760 | Waiting on empty queue | NULL                                  |
|  8 | root            | localhost | test | Sleep   | 3382 |                        | NULL                                  |
|  9 | root            | localhost | test | Query   | 1092 | updating               | update t1 set name='john' where id =3 |
| 10 | root            | localhost | NULL | Query   |    0 | starting               | show processlist                      |
+----+-----------------+-----------+------+---------+------+------------------------+---------------------------------------+
4 rows in set (0.00 sec)

檢視發生阻塞的語句是什麼

1、查阻塞的SQL執行緒

mysql> select * from performance_schema.threads where PROCESSLIST_ID=8\G;
*************************** 1. row ***************************
          THREAD_ID: 47
               NAME: thread/sql/one_connection
               TYPE: FOREGROUND
     PROCESSLIST_ID: 8
   PROCESSLIST_USER: root
   PROCESSLIST_HOST: localhost
     PROCESSLIST_DB: test
PROCESSLIST_COMMAND: Sleep
   PROCESSLIST_TIME: 4191
  PROCESSLIST_STATE: NULL
   PROCESSLIST_INFO: select * from t1          ## 這裡得出的語句是不可靠的
   PARENT_THREAD_ID: 1
               ROLE: NULL
       INSTRUMENTED: YES
            HISTORY: YES
    CONNECTION_TYPE: Socket
       THREAD_OS_ID: 1839
     RESOURCE_GROUP: USR_default
1 row in set (0.00 sec)

2、繼續查詢events_statements_history查詢語句,從下至上依次進行分析

mysql> select * from performance_schema.events_statements_history where THREAD_ID=47\G;

唯一索引+RC

id為唯一索引而不是主鍵,在RC隔離級別下執行delete from t1 where id =3的加鎖情況如下:

A終端建立索引,並開啟事務

mysql> alter table t1 add unique key uk_n(name);
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> desc t1;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int(11)     | NO   | PRI | NULL    |       |
| name  | varchar(10) | YES  | UNI | NULL    |       |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> delete from t1 where name='tom';
Query OK, 1 row affected (0.00 sec)

B終端執行修改輔助索引列值操作

mysql> update t1 set id=5 where name ='tom';

檢視鎖情況

這時鎖變成了輔助索引的鎖,因為上面的條件是輔助索引,delete語句會加兩把鎖(輔助索引的條件,和回表時對聚簇索引的鎖)

mysql> select *  from sys.innodb_lock_waits \G;
*************************** 1. row ***************************
                wait_started: 2020-07-08 19:01:42
                    wait_age: 00:05:13
               wait_age_secs: 313
                locked_table: `test`.`t1`
         locked_table_schema: test
           locked_table_name: t1
      locked_table_partition: NULL
   locked_table_subpartition: NULL
                locked_index: uk_n
                 locked_type: RECORD
              waiting_trx_id: 17210
         waiting_trx_started: 2020-07-08 18:59:40
             waiting_trx_age: 00:07:15
     waiting_trx_rows_locked: 1
   waiting_trx_rows_modified: 0
                 waiting_pid: 13
               waiting_query: update t1 set id=5 where name ='tom'
             waiting_lock_id: 139989104688592:146:5:4:139989023260296
           waiting_lock_mode: X,REC_NOT_GAP
             blocking_trx_id: 17209
                blocking_pid: 11
              blocking_query: NULL
            blocking_lock_id: 139989104687720:146:5:4:139989023254232
          blocking_lock_mode: X,REC_NOT_GAP
        blocking_trx_started: 2020-07-08 19:00:28
            blocking_trx_age: 00:06:27
    blocking_trx_rows_locked: 2
  blocking_trx_rows_modified: 1
     sql_kill_blocking_query: KILL QUERY 11
sql_kill_blocking_connection: KILL 11
1 row in set (0.00 sec)

如果將條件換成聚簇索引的其他行,就不會阻塞

## 阻塞
mysql> update t1 set name='john' where id=1;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
 
## 不阻塞
mysql> update t1 set name='john' where id=3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

非唯一索引+RC

相對於前兩種來說,name列的約束降低了,不再是唯一索引,而是普通索引

mysql> alter table t1 drop index uk_n;
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> alter table t1 add index idx(name);
Query OK, 0 rows affected, 1 warning (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 1

mysql> desc t1;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int(11)     | NO   | PRI | NULL    |       |
| name  | varchar(10) | YES  | MUL | NULL    |       |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)


mysql> insert into t1 values(4,'jerry'),(7,'jerry');
Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql> select * from t1;
+----+-------+
| id | name  |
+----+-------+
|  3 | jack  |
|  2 | jerry |
|  4 | jerry |
|  7 | jerry |
|  1 | tom   |
+----+-------+
5 rows in set (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t1;
+----+-------+
| id | name  |
+----+-------+
|  3 | jack  |
|  2 | jerry |
|  4 | jerry |
|  7 | jerry |
|  1 | tom   |
+----+-------+
5 rows in set (0.00 sec)

這時的B終端加鎖情況

mysql> update t1 set id=10 where name='jerry';
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql> update t1 set name='lily' where id=7;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql> update t1 set name='lily' where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

如果name列為非唯一索引,那麼所有滿足條件的記錄都會被加鎖。同時,這些記錄對應的聚簇索引記錄也會被加鎖,本例會加6把鎖,3個主鍵鎖,3個輔助索引鎖

無索引+RC

mysql> alter table t1 drop index idx;
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

B終端的加鎖情況

## 加鎖
mysql> update t1 set id=5 where name='jerry';
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted

## 不滿足條件的會加鎖後馬上釋放鎖
mysql> update t1 set id=5 where name='jack';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

若 name列上沒有索引,SQL 會走聚簇索引的全掃描進行過濾,由於過濾是由 MySQL Server層面進行的。因此每條記錄,無論是否滿足條件,都會被加上 X 鎖。但是,為了效率考量,MySQL 做了優化,對於不滿足條件的記錄,會在判斷後放鎖,最終持有的,是滿足條件的記錄上的鎖,但是不滿足條件的記錄上的加鎖/放鎖動作不會省略

測試RR級別不同場景下鎖的處理

主鍵+RR

id 列是主鍵列,Repeatable Read 隔離級別,針對 delete from t1 where id = 10; 這條SQL,加鎖與組合一:[id 主鍵,Read Committed]一致

唯一索引+RR

與組合五類似,組合六的加鎖,與組合二:[id 唯一索引,Read Committed]一致。兩個 X 鎖,id 唯一索引滿足條件的記錄上一個,對應的聚簇索引上的記錄一個。
注:
id 為唯一索引,針對 id 的併發等值刪除操作,有可能會產生死鎖。

非唯一索引+RR

Repeatable Read 隔離級別,id 上有一個非唯一索引,執行 delete from t1 where id =10; 假設選擇 id 列上的索引進行條件過濾,最後的加鎖行為,如下圖:

修改隔離級別

[root@db ~]# vim /etc/my.cnf 
transaction_isolation=repeatable-read
autocommit=0
innodb_lock_wait_timeout=3600

[root@db ~]# /etc/init.d/mysqld restart
Shutting down MySQL.... SUCCESS! 
Starting MySQL... SUCCESS! 
mysql> select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| REPEATABLE-READ         |
+-------------------------+
1 row in set (0.01 sec)

建立表

mysql> create table t2(id int primary key ,name varchar(10));
Query OK, 0 rows affected (0.05 sec)

mysql> insert into t2 values(2,'a'),(5,'f'),(6,'c'),(11,'k'),(18,'o'),(21,'f'),(19,'f');
Query OK, 5 rows affected (0.01 sec)
Records: 5  Duplicates: 0  Warnings: 0

mysql> alter table t2 add index idx_n(name);
Query OK, 0 rows affected (7.00 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t2;
+----+------+
| id | name |
+----+------+
|  2 | a    |
|  6 | c    |
|  5 | f    |
| 19 | f    |
| 21 | f    |
| 11 | k    |
| 18 | o    |
+----+------+
7 rows in set (0.00 sec)

執行delete語句後,檢視鎖情況

mysql> delete from t2 where name='f';
Query OK, 3 rows affected (0.00 sec)
mysql> insert into t2 values('7','e');
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql> select *  from sys.innodb_lock_waits \G;
*************************** 1. row ***************************
                wait_started: 2020-07-09 00:17:02
                    wait_age: 00:00:06
               wait_age_secs: 6
                locked_table: `test`.`t2`
         locked_table_schema: test
           locked_table_name: t2
      locked_table_partition: NULL
   locked_table_subpartition: NULL
                locked_index: idx_n
                 locked_type: RECORD
              waiting_trx_id: 17750
         waiting_trx_started: 2020-07-09 00:15:05
             waiting_trx_age: 00:02:03
     waiting_trx_rows_locked: 2
   waiting_trx_rows_modified: 1
                 waiting_pid: 12
               waiting_query: insert into t2 values('7','e')
             waiting_lock_id: 140459079454160:147:5:3:140458986635232
           waiting_lock_mode: X,GAP,INSERT_INTENTION
             blocking_trx_id: 17745
                blocking_pid: 13
              blocking_query: NULL
            blocking_lock_id: 140459079453288:147:5:3:140458986628824
          blocking_lock_mode: X
        blocking_trx_started: 2020-07-09 00:13:46
            blocking_trx_age: 00:03:22
    blocking_trx_rows_locked: 7
  blocking_trx_rows_modified: 3
     sql_kill_blocking_query: KILL QUERY 13
sql_kill_blocking_connection: KILL 13
1 row in set (0.00 sec)