InnoDB鎖機制
- 2020 年 12 月 4 日
- 筆記
測試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)