-- 准备表
drop table if exists test001;
create table test001
(
id int auto_increment primary key,
b varchar(100),
c int,
unique key (b)
);
-- 先插入1条基础数据,并提交事务
insert into test001(b,c) values ('uq_1',1);
① 会话1先执行:执行成功
-- step1:插入uq_2,不提交事务
## 执行成功
replace into test001(b,c) values ('uq_2',1);
-- step2:更新uq_1,不提交事务
## 执行被阻塞
replace into test001(b,c) values ('uq_1',2);
问题:唯一索引字段b的值完全不相同,会话1为什么会阻塞会话2语句的执行?
③ 会话1再执行:发生死锁**
-- step3:再次修改uq_1,不提交
replace into test001(b,c) values ('uq_1',3);
mysql> select * from information_schema.INNODB_LOCKS;
+-------------+-------------+-----------+-----------+-------------------+------------+------------+-----------+----------+-----------+
| lock_id | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data |
+-------------+-------------+-----------+-----------+-------------------+------------+------------+-----------+----------+-----------+
| 2262:25:4:8 | 2262 | X | RECORD | `mysql`.`test001` | b | 25 | 4 | 8 | 'uq_2' |
| 2261:25:4:8 | 2261 | X | RECORD | `mysql`.`test001` | b | 25 | 4 | 8 | 'uq_2' |
+-------------+-------------+-----------+-----------+-------------------+------------+------------+-----------+----------+-----------+
2 rows in set, 1 warning (0.00 sec)
mysql> select * from information_schema.INNODB_LOCK_WAITS;
+-------------------+-------------------+-----------------+------------------+
| requesting_trx_id | requested_lock_id | blocking_trx_id | blocking_lock_id |
+-------------------+-------------------+-----------------+------------------+
| 2262 | 2262:25:4:8 | 2261 | 2261:25:4:8 |
+-------------------+-------------------+-----------------+------------------+
1 row in set, 1 warning (0.00 sec)
(由于列比较多,所以改成竖排显示,(字段含义见 8.2))
mysql> select * from information_schema.innodb_trx\G;
*************************** 1. row ***************************
trx_id: 2262
trx_state: LOCK WAIT
trx_started: 2022-08-18 14:02:43
trx_requested_lock_id: 2262:25:4:8
trx_wait_started: 2022-08-18 14:02:43
trx_weight: 6
trx_mysql_thread_id: 13
trx_query: /* ApplicationName=DataGrip 2021.1 */ replace into test001(b,c) values ('uq_1',2)
trx_operation_state: updating or deleting
trx_tables_in_use: 1
trx_tables_locked: 1
trx_lock_structs: 4
trx_lock_memory_bytes: 1136
trx_rows_locked: 3
trx_rows_modified: 2
trx_concurrency_tickets: 0
trx_isolation_level: READ COMMITTED
trx_unique_checks: 1
trx_foreign_key_checks: 1
trx_last_foreign_key_error: NULL
trx_adaptive_hash_latched: 0
trx_adaptive_hash_timeout: 0
trx_is_read_only: 0
trx_autocommit_non_locking: 0
*************************** 2. row ***************************
trx_id: 2261
trx_state: RUNNING
trx_started: 2022-08-18 14:02:39
trx_requested_lock_id: NULL
trx_wait_started: NULL
trx_weight: 3
trx_mysql_thread_id: 8
trx_query: NULL
trx_operation_state: NULL
trx_tables_in_use: 0
trx_tables_locked: 1
trx_lock_structs: 2
trx_lock_memory_bytes: 1136
trx_rows_locked: 1
trx_rows_modified: 1
trx_concurrency_tickets: 0
trx_isolation_level: READ COMMITTED
trx_unique_checks: 1
trx_foreign_key_checks: 1
trx_last_foreign_key_error: NULL
trx_adaptive_hash_latched: 0
trx_adaptive_hash_timeout: 0
trx_is_read_only: 0
trx_autocommit_non_locking: 0
2 rows in set (0.00 sec)
- 会话1主要信息:【trx_id: 2261 trx_state: RUNNING trx_started: 2022-08-18 14:02:39】
- 会话2主要信息:【trx_id: 2262 trx_state: LOCK WAIT trx_started: 2022-08-18 14:02:43】
问题:会话2为什么要等待会话1释放锁?
那么问题来了,会话2更新的是’uq_1’,为什么会锁住’uq_2’?
进一步看information_schema.innodb_trx,从上面看到
- 会话2的语句,事务2262在等待锁, 锁定的行数是3【trx_rows_locked: 3 trx_rows_modified: 2】
- 会话1的语句,事务2261是运行状态, 锁定的行数是1【trx_rows_locked: 1 trx_rows_modified: 1】
问题:在间隙锁关闭的情况下,会话2的语句为什么锁定3行数据?**
REPALCE:
https://dev.mysql.com/doc/refman/5.7/en/innodb-locks-set.html
REPLACE is done like an INSERT if there is no collision on a unique key. Otherwise, an exclusive next-key lock is placed on the row to be replaced.
INSERT :
https://dev.mysql.com/doc/refman/5.7/en/innodb-locks-set.html
INSERT sets an exclusive lock on the inserted row. This lock is an index-record lock, not a next-key lock (that is, there is no gap lock) and does not prevent other sessions from inserting into the gap before the inserted row.
在已提交读事务隔离级别下,官方文档有如下描述:
https://dev.mysql.com/doc/refman/5.7/en/innodb-transaction-isolation-levels.html
For locking reads (SELECT with FOR UPDATE or LOCK IN SHARE MODE), UPDATE statements, and DELETE statements, InnoDB locks only index records, not the gaps before them, and thus permits the free insertion of new records next to locked records.
Gap locking is only used for foreign-key constraint checking and duplicate-key checking.
咨询DBA,也反应关闭间隙锁的命令【innodb_locks_unsafe_for_binlog】不会影响外键和唯一索引对gap加锁,DBA回答如下:
再插入一条基础数据
-- 插入uq_2,并提交事务
replace into test001(b,c) values ('uq_2',1);
-- step1:插入uq_3,不提交
## 执行成功
replace into test001(b,c) values ('uq_3',1);
② 会话2执行:
-- step2:替换uq_1,不提交``## 执行成功``replace` `into` `test001(b,c) ``values` `(``'uq_1'``,2);
附加:事务2262和2261的详细加锁过程,可以看到都是由意向排他锁升级为排他锁,2262一直在等待锁。
mysql>show engine innodb status;
------------
TRANSACTIONS
------------
Trx id counter 2263
Purge done for trx's n:o < 2261 undo n:o < 0 state: running but idle
History list length 1
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 283411180461568, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 2262, ACTIVE 22 sec updating or deleting
mysql tables in use 1, locked 1
LOCK WAIT 4 lock struct(s), heap size 1136, 3 row lock(s), undo log entries 2
MySQL thread id 13, OS thread handle 42312, query id 6680 localhost 127.0.0.1 root update
/* ApplicationName=DataGrip 2021.1 */ replace into test001(b,c) values ('uq_1',2)
------- TRX HAS BEEN WAITING 22 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 25 page no 4 n bits 80 index b of table `mysql`.`test001` trx id 2262 lock_mode X waiting
Record lock, heap no 8 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 4; hex 75715f32; asc uq_2;;
1: len 4; hex 80000043; asc C;;
------------------
TABLE LOCK table `mysql`.`test001` trx id 2262 lock mode IX
RECORD LOCKS space id 25 page no 4 n bits 80 index b of table `mysql`.`test001` trx id 2262 lock_mode X
Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
0: len 4; hex 75715f31; asc uq_1;;
1: len 4; hex 80000001; asc ;;
RECORD LOCKS space id 25 page no 3 n bits 80 index PRIMARY of table `mysql`.`test001` trx id 2262 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 5; compact format; info bits 32
0: len 4; hex 80000001; asc ;;
1: len 6; hex 0000000008d6; asc ;;
2: len 7; hex 230000015303d7; asc # S ;;
3: len 4; hex 75715f31; asc uq_1;;
4: len 4; hex 80000001; asc ;;
RECORD LOCKS space id 25 page no 4 n bits 80 index b of table `mysql`.`test001` trx id 2262 lock_mode X waiting
Record lock, heap no 8 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 4; hex 75715f32; asc uq_2;;
1: len 4; hex 80000043; asc C;;
---TRANSACTION 2261, ACTIVE 26 sec
2 lock struct(s), heap size 1136, 1 row lock(s), undo log entries 1
MySQL thread id 8, OS thread handle 41748, query id 6670 localhost 127.0.0.1 root
TABLE LOCK table `mysql`.`test001` trx id 2261 lock mode IX
RECORD LOCKS space id 25 page no 4 n bits 80 index b of table `mysql`.`test001` trx id 2261 lock_mode X locks rec but not gap
Record lock, heap no 8 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 4; hex 75715f32; asc uq_2;;
1: len 4; hex 80000043; asc C;;
① 会话1:
-- step1:插入uq_2,不提交
INSERT into test001(b,c) values ('uq_2',1) ON DUPLICATE KEY UPDATE c = VALUES(c);
② 会话2:
-- step2:更新uq_1,不提交
INSERT into test001(b,c) values ('uq_1',2) ON DUPLICATE KEY UPDATE c = VALUES(c);
上面分析了事务1产生锁等待的原因,接着分析死锁产生的原因就很容易了。
开启主动死锁检测,默认开启:
innodb_deadlock_detect=on
mysql> show engine innodb status;
| InnoDB | |
=====================================
2022-08-18 14:03:14 0xb2b8 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 9 seconds
-----------------
BACKGROUND THREAD
-----------------
srv_master_thread loops: 255 srv_active, 0 srv_shutdown, 31140 srv_idle
srv_master_thread log flush and writes: 31395
----------
SEMAPHORES
----------
OS WAIT ARRAY INFO: reservation count 945
OS WAIT ARRAY INFO: signal count 967
RW-shared spins 0, rounds 1260, OS waits 541
RW-excl spins 0, rounds 448, OS waits 2
RW-sx spins 0, rounds 0, OS waits 0
Spin rounds per wait: 1260.00 RW-shared, 448.00 RW-excl, 0.00 RW-sx
------------------------
LATEST DETECTED DEADLOCK
------------------------
2022-08-18 14:03:10 0xa314
*** (1) TRANSACTION:
TRANSACTION 2262, ACTIVE 27 sec updating or deleting
mysql tables in use 1, locked 1
LOCK WAIT 4 lock struct(s), heap size 1136, 3 row lock(s), undo log entries 2
MySQL thread id 13, OS thread handle 42312, query id 6680 localhost 127.0.0.1 root update
/* ApplicationName=DataGrip 2021.1 */ replace into test001(b,c) values ('uq_1',2)
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 25 page no 4 n bits 80 index b of table `mysql`.`test001` trx id 2262 lock_mode X waiting
Record lock, heap no 8 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 4; hex 75715f32; asc uq_2;;
1: len 4; hex 80000043; asc C;;
*** (2) TRANSACTION:
TRANSACTION 2261, ACTIVE 31 sec inserting
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 2
MySQL thread id 8, OS thread handle 41748, query id 6694 localhost 127.0.0.1 root update
/* ApplicationName=DataGrip 2021.1 */ replace into test001(b,c) values ('uq_1',3)
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 25 page no 4 n bits 80 index b of table `mysql`.`test001` trx id 2261 lock_mode X locks rec but not gap
Record lock, heap no 8 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 4; hex 75715f32; asc uq_2;;
1: len 4; hex 80000043; asc C;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 25 page no 4 n bits 80 index b of table `mysql`.`test001` trx id 2261 lock_mode X waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
0: len 4; hex 75715f31; asc uq_1;;
1: len 4; hex 80000001; asc ;;
*** WE ROLL BACK TRANSACTION (2)
------------
TRANSACTIONS
------------
Trx id counter 2267
Purge done for trx's n:o < 2267 undo n:o < 0 state: running but idle
History list length 3
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 283411180461568, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 283411180459824, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 2262, ACTIVE 31 sec
5 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 2
MySQL thread id 13, OS thread handle 42312, query id 6705 localhost 127.0.0.1 root
TABLE LOCK table `mysql`.`test001` trx id 2262 lock mode IX
RECORD LOCKS space id 25 page no 4 n bits 80 index b of table `mysql`.`test001` trx id 2262 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
0: len 4; hex 75715f31; asc uq_1;;
1: len 4; hex 80000001; asc ;;
RECORD LOCKS space id 25 page no 3 n bits 80 index PRIMARY of table `mysql`.`test001` trx id 2262 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 5; compact format; info bits 32
0: len 4; hex 80000001; asc ;;
1: len 6; hex 0000000008d6; asc ;;
2: len 7; hex 230000015303d7; asc # S ;;
3: len 4; hex 75715f31; asc uq_1;;
4: len 4; hex 80000001; asc ;;
RECORD LOCKS space id 25 page no 4 n bits 80 index b of table `mysql`.`test001` trx id 2262 lock_mode X
RECORD LOCKS space id 25 page no 4 n bits 80 index b of table `mysql`.`test001` trx id 2262 lock_mode X locks gap before rec
Record lock, heap no 8 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 4; hex 75715f31; asc uq_1;;
1: len 4; hex 80000044; asc D;;
--------
FILE I/O
--------
I/O thread 0 state: wait Windows aio (insert buffer thread)
I/O thread 1 state: wait Windows aio (log thread)
I/O thread 2 state: wait Windows aio (read thread)
I/O thread 3 state: wait Windows aio (read thread)
I/O thread 4 state: wait Windows aio (read thread)
I/O thread 5 state: wait Windows aio (read thread)
I/O thread 6 state: wait Windows aio (write thread)
I/O thread 7 state: wait Windows aio (write thread)
I/O thread 8 state: wait Windows aio (write thread)
I/O thread 9 state: wait Windows aio (write thread)
Pending normal aio reads: [0, 0, 0, 0] , aio writes: [0, 0, 0, 0] ,
ibuf aio reads:, log i/o's:, sync i/o's:
Pending flushes (fsync) log: 0; buffer pool: 0
284 OS file reads, 3790 OS file writes, 2443 OS fsyncs
0.00 reads/s, 0 avg bytes/read, 1.89 writes/s, 1.33 fsyncs/s
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 0, seg size 2, 0 merges
merged operations:
insert 0, delete mark 0, delete 0
discarded operations:
insert 0, delete mark 0, delete 0
Hash table size 34679, node heap has 0 buffer(s)
Hash table size 34679, node heap has 0 buffer(s)
Hash table size 34679, node heap has 0 buffer(s)
Hash table size 34679, node heap has 0 buffer(s)
Hash table size 34679, node heap has 1 buffer(s)
Hash table size 34679, node heap has 0 buffer(s)
Hash table size 34679, node heap has 0 buffer(s)
Hash table size 34679, node heap has 0 buffer(s)
1.56 hash searches/s, 2.22 non-hash searches/s
---
LOG
---
Log sequence number 3095833
Log flushed up to 3095833
Pages flushed up to 3095833
Last checkpoint at 3095824
0 pending log flushes, 0 pending chkp writes
1492 log i/o's done, 0.78 log i/o's/second
----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 137297920
Dictionary memory allocated 153584
Buffer pool size 8192
Free buffers 7759
Database pages 432
Old database pages 0
Modified db pages 0
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 0, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 250, created 182, written 1994
0.00 reads/s, 0.00 creates/s, 1.00 writes/s
Buffer pool hit rate 1000 / 1000, young-making rate 0 / 1000 not 0 / 1000
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 432, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]
--------------
ROW OPERATIONS
--------------
0 queries inside InnoDB, 0 queries in queue
0 read views open inside InnoDB
Process ID=37652, Main thread ID=40856, state: sleeping
Number of rows inserted 4283, updated 86, deleted 17, read 8172
0.00 inserts/s, 0.11 updates/s, 0.00 deletes/s, 0.00 reads/s
----------------------------
END OF INNODB MONITOR OUTPUT
============================
-- 查看数据库版本
select version();
-- 查看默认值
show variables like '%engine%';
-- 查看事务隔离级别
show global variables like 'tx_isolation';
-- 查看是否开启自动提交
show variables like 'autocommit';
-- 查看是否开启间隙锁
show variables like 'innodb_locks_unsafe_for_binlog';
-- 查看锁等待的对应关系
select * from information_schema.INNODB_LOCK_WAITS;
-- 查看innodb当前的事务和锁
select * from information_schema.innodb_trx;
mysql5.6版本后,可以通过下面的命令来进行锁的状态监控
set global innodb_status_output=on;
set global innodb_status_output_locks=on;
-- 开启锁的状态监控后,查看详细的innodeb信息
show engine innodb status;
-- 1、查看是否开启间隙锁:show variables like 'innodb_locks_unsafe_for_binlog';
mysql> show variables like 'innodb_locks_unsafe_for_binlog';
+--------------------------------+-------+
| Variable_name | Value |
+--------------------------------+-------+
| innodb_locks_unsafe_for_binlog | OFF |
+--------------------------------+-------+
-- 注意:默认值为OFF(0),即启用gap lock。
-- 这个参数最主要的作用就是控制innodb是否对gap加锁。
-- 但是,这一设置变更并不影响外键和唯一索引(含主键)对gap进行加锁的需要。
-- 2、关闭间隙锁(gap lock)方法:
-- 在my.cnf里面的[mysqld]添加:
innodb_locks_unsafe_for_binlog = 1
-- 重启MySQL后生效。
Field | Extra |
---|---|
trx_id | 事务ID |
trx_state | 事务状态 |
trx_started | 事务开始时间 |
trx_requested_lock_id | innodb_locks.lock_id |
trx_wait_started | 事务开始等待的时间 |
trx_weight | 事务权重 |
trx_mysql_thread_id | 事务线程ID |
trx_query | 具体SQL语句 |
trx_operation_state | 事务当前操作状态 |
trx_tables_in_use | 事务中有多少个表被使用 |
trx_tables_locked | 事务拥有多少个锁 |
trx_lock_structs | |
trx_lock_memory_bytes | 事务锁住的内存大小(B) |
trx_rows_locked | 事务锁住的行数 |
trx_rows_modified | 事务更改的行数 |
trx_concurrency_tickets | 事务并发票数 |
trx_isolation_level | 事务隔离级别 |
trx_unique_checks | 是否唯一性检查 |
trx_foreign_key_checks | 是否外键检查 |
trx_last_foreign_key_error | 最后的外键错误 |
trx_adaptive_hash_latched | |
trx_adaptive_hash_timeout |
Field | Extra |
---|---|
lock_id | 锁ID |
lock_trx_id | 拥有锁的事务ID |
lock_mode | 锁模式 |
lock_type | 锁类型 |
lock_table | 被锁的表 |
lock_index | 被锁的索引 |
lock_space | 被锁的表空间号 |
lock_page | 被锁的页号 |
lock_rec | 被锁的记录号 |
lock_data | 被锁的数据 |
Field | Extra |
---|---|
requesting_trx_id | 请求锁的事务ID |
requested_lock_id | 请求锁的锁ID |
blocking_trx_id | 当前拥有锁的事务ID |
blocking_lock_id | 当前拥有锁的锁ID |