Innodb死锁案例

innodb 死锁例子

频繁死锁开启打印死锁设置会将死锁的信息输出到错误日志中:

 set global innodb_print_all_deadlocks=ON|OFF;

创建两个表,分别为动物表,鸟类表

CREATE TABLE Animals (name VARCHAR(10) PRIMARY KEY, value INT) ENGINE = InnoDB;
CREATE TABLE Birds (name VARCHAR(10) PRIMARY KEY, value INT) ENGINE = InnoDB;

分别插入数据

INSERT INTO Animals (name,value) VALUES ("Aardvark",10);
INSERT INTO Birds (name,value) VALUES ("Buzzard",20);

事务A,对Animals 动物表进行共享读操作

begin;

SELECT value FROM Animals WHERE name='Aardvark' FOR SHARE;

事务B,对Birds 鸟类表进行共享读操作

begin;

SELECT value FROM Birds WHERE name='Buzzard' FOR SHARE;

查看表和数据行的锁信息

SELECT ENGINE_LOCK_ID as Lock_Id,  
              ENGINE_TRANSACTION_ID as Trx_Id, 
              OBJECT_NAME as `Table`, 
              INDEX_NAME as `Index`, 
              LOCK_DATA as Data, 
              LOCK_MODE as Mode, 
              LOCK_STATUS as Status, 
              LOCK_TYPE as Type 
        FROM performance_schema.data_locks;

动物表和鸟类表,表级别是IS(意向共享锁),数据行是S锁,共享锁

Lock_Id Trx_Id Table Index Data Mode Status Type
281472817657424:1071:281472829299648 562947794368080 Animals NULL NULL IS GRANTED TABLE
281472817657424:9:4:2:281472829296688 562947794368080 Animals PRIMARY ‘Aardvark’ S,REC_NOT_GAP GRANTED RECORD
281472817662272:1072:281472829336288 562947794372928 Birds NULL NULL IS GRANTED TABLE
281472817662272:10:4:2:281472829333328 562947794372928 Birds PRIMARY ‘Buzzard’ S,REC_NOT_GAP GRANTED RECORD

事务B,对动物进行更新操作,更新操作是X锁(排他锁),事务B需要等待事务A的共享读锁释放,此时事务B处于阻塞等待状态

UPDATE Animals SET value=30 WHERE name='Aardvark';

查看事务B的阻塞信息

SELECT 
              ENGINE_LOCK_ID as Lock_Id, 
              ENGINE_TRANSACTION_ID as Trx_id, 
              OBJECT_NAME as `Table`, 
              INDEX_NAME as `Index`, 
              LOCK_DATA as Data, 
              LOCK_MODE as Mode, 
              LOCK_STATUS as Status, 
              LOCK_TYPE as Type 
        FROM performance_schema.data_locks;

此时动物表级别,由IS锁(意向共享锁)升级为IX锁(意向排他锁),数据行也由S锁,变为X锁,且处于阻塞等待事务A的共享锁释放

Lock_Id Trx_Id Table Index Data Mode Status Type
281472817662272:1071:281472829336376 2489 Animals NULL NULL IX GRANTED TABLE
281472817662272:1072:281472829336288 2489 Birds NULL NULL IS GRANTED TABLE
281472817662272:10:4:2:281472829333328 2489 Birds PRIMARY ‘Buzzard’ S,REC_NOT_GAP GRANTED RECORD
281472817662272:9:4:2:281472829333672 2489 Animals PRIMARY ‘Aardvark’ X,REC_NOT_GAP WAITING RECORD
281472817657424:1071:281472829299648 562947794368080 Animals NULL NULL IS GRANTED TABLE
281472817657424:9:4:2:281472829296688 562947794368080 Animals PRIMARY ‘Aardvark’ S,REC_NOT_GAP GRANTED RECORD

事务A,对鸟类表进行更新操作,死锁产生

UPDATE Birds SET value=40 WHERE name='Buzzard';

查看事务A的阻塞信息

SELECT 
              ENGINE_LOCK_ID as Lock_Id, 
              ENGINE_TRANSACTION_ID as Trx_id, 
              OBJECT_NAME as `Table`, 
              INDEX_NAME as `Index`, 
              LOCK_DATA as Data, 
              LOCK_MODE as Mode, 
              LOCK_STATUS as Status, 
              LOCK_TYPE as Type 
        FROM performance_schema.data_locks;
Lock_Id Trx_Id Table Index Data Mode Status Type
281472817662272:1071:281472829336376 2489 Animals NULL NULL IX GRANTED TABLE
281472817662272:1072:281472829336288 2489 Birds NULL NULL IS GRANTED TABLE
281472817662272:10:4:2:281472829333328 2489 Birds PRIMARY ‘Buzzard’ S,REC_NOT_GAP GRANTED RECORD
281472817662272:9:4:2:281472829333672 2489 Animals PRIMARY ‘Aardvark’ X,REC_NOT_GAP GRANTED RECORD

注意 前面事务B等待事务A的共享锁释放,大多数时候我们可能认为,
此时 事务A 又将 事务B 的数据由 “S锁” 变为 “X锁”, 鸟类表级锁也由IS锁变为了IX锁,
事务A进入阻塞等待状态,等待事务B释放S锁(共享读锁), 两张表都由IS锁,都变为了IX 锁,数据行的锁都变为了X锁,死锁发生时,动物表级别是IX锁,数据行是X锁,
但是通过上面查看锁的信息,我们发现鸟类表依然是IS锁,数据行是S锁,这就很奇怪了!!死锁产生了,经过试验,此时不管对动物表还是鸟类表死锁的数据进行 更新/删除 操作
都会发生阻塞等待。(为什么鸟类表级锁没有变为IX 锁,数据行没有变为X锁原因有待查证)

查看死锁信息

SELECT * FROM INFORMATION_SCHEMA.INNODB_METRICS WHERE NAME="lock_deadlocks";
NAME SUBSYSTEM COUNT MAX_COUNT MIN_COUNT AVG_COUNT COUNT_RESET MAX_COUNT_RESET MIN_COUNT_RESET AVG_COUNT_RESET TIME_ENABLED TIME_DISABLED TIME_ELAPSED TIME_RESET STATUS TYPE COMMENT
lock_deadlocks lock 1 1 NULL 0.0007617351474613312 1 1 NULL NULL 2023-04-28 16:30:19 NULL 1313 NULL enabled counter Number of deadlocks

重点当死锁产生后,因为死锁会一直锁定数据行,即使进行回滚操作也无法解锁,数据行的锁是X锁(排他锁),后续对该数据进行的任何 更新/删除 操作都会被阻塞,直到等待到默认锁超时时间结束为止,MySQL默认锁超时时间是50s

查看默认锁超时时间如下:

show variables like 'innodb_lock_wait_timeout';
Variable_name Value
innodb_lock_wait_timeout 50

查看最新的死锁信息

SHOW ENGINE INNODB STATUS;
=====================================
2023-04-28 15:34:12 281471894847360 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 16 seconds
-----------------
BACKGROUND THREAD
-----------------
srv_master_thread loops: 30 srv_active, 0 srv_shutdown, 98134 srv_idle
srv_master_thread log flush and writes: 0
----------
SEMAPHORES
----------
OS WAIT ARRAY INFO: reservation count 217
OS WAIT ARRAY INFO: signal count 208
RW-shared spins 0, rounds 0, OS waits 0
RW-excl spins 0, rounds 0, OS waits 0
RW-sx spins 0, rounds 0, OS waits 0
Spin rounds per wait: 0.00 RW-shared, 0.00 RW-excl, 0.00 RW-sx
------------------------
LATEST DETECTED DEADLOCK
------------------------
2023-04-28 15:32:59 281472351068032
*** (1) TRANSACTION:
TRANSACTION 2483, ACTIVE 529 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 4 lock struct(s), heap size 1128, 2 row lock(s)
MySQL thread id 38, OS thread handle 281471453474688, query id 10679 10.211.55.2 root updating
UPDATE Animals SET value=30 WHERE name='Aardvark'

*** (1) HOLDS THE LOCK(S):
RECORD LOCKS space id 10 page no 4 n bits 72 index PRIMARY of table `test`.`Birds` trx id 2483 lock mode S locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
0: len 7; hex 42757a7a617264; asc Buzzard;;
1: len 6; hex 0000000009b2; asc       ;;
2: len 7; hex 81000000b90110; asc        ;;
3: len 4; hex 80000014; asc     ;;


*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 9 page no 4 n bits 72 index PRIMARY of table `test`.`Animals` trx id 2483 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
0: len 8; hex 416172647661726b; asc Aardvark;;
1: len 6; hex 0000000009b0; asc       ;;
2: len 7; hex 81000000b80110; asc        ;;
3: len 4; hex 8000000a; asc     ;;


*** (2) TRANSACTION:
TRANSACTION 2484, ACTIVE 718 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 4 lock struct(s), heap size 1128, 2 row lock(s)
MySQL thread id 34, OS thread handle 281471898013568, query id 10688 10.211.55.2 root updating
UPDATE Birds SET value=40 WHERE name='Buzzard'

*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 9 page no 4 n bits 72 index PRIMARY of table `test`.`Animals` trx id 2484 lock mode S locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
0: len 8; hex 416172647661726b; asc Aardvark;;
1: len 6; hex 0000000009b0; asc       ;;
2: len 7; hex 81000000b80110; asc        ;;
3: len 4; hex 8000000a; asc     ;;


*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 10 page no 4 n bits 72 index PRIMARY of table `test`.`Birds` trx id 2484 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
0: len 7; hex 42757a7a617264; asc Buzzard;;
1: len 6; hex 0000000009b2; asc       ;;
2: len 7; hex 81000000b90110; asc        ;;
3: len 4; hex 80000014; asc     ;;

*** WE ROLL BACK TRANSACTION (2)
------------
TRANSACTIONS
------------
Trx id counter 2485
Purge done for trx's n:o < 2480 undo n:o < 0 state: running but idle
History list length 0
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 562947794373736, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
---TRANSACTION 562947794372120, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
---TRANSACTION 562947794371312, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
---TRANSACTION 562947794368888, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
---TRANSACTION 562947794368080, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
---TRANSACTION 562947794370504, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
---TRANSACTION 562947794369696, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
---TRANSACTION 562947794367272, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
---TRANSACTION 562947794366464, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
---TRANSACTION 562947794365656, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
---TRANSACTION 562947794364848, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
---TRANSACTION 562947794364040, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
---TRANSACTION 2483, ACTIVE 602 sec
4 lock struct(s), heap size 1128, 2 row lock(s), undo log entries 1
MySQL thread id 38, OS thread handle 281471453474688, query id 10708 10.211.55.2 root
--------
FILE I/O
--------
I/O thread 0 state: waiting for completed aio requests ((null))
I/O thread 1 state: waiting for completed aio requests (insert buffer thread)
I/O thread 2 state: waiting for completed aio requests (read thread)
I/O thread 3 state: waiting for completed aio requests (read thread)
I/O thread 4 state: waiting for completed aio requests (read thread)
I/O thread 5 state: waiting for completed aio requests (read thread)
I/O thread 6 state: waiting for completed aio requests (write thread)
I/O thread 7 state: waiting for completed aio requests (write thread)
I/O thread 8 state: waiting for completed aio requests (write thread)
Pending normal aio reads: [0, 0, 0, 0] , aio writes: [0, 0, 0, 0] ,
ibuf aio reads:
Pending flushes (fsync) log: 0; buffer pool: 0
915 OS file reads, 1739 OS file writes, 1073 OS fsyncs
0.00 reads/s, 0 avg bytes/read, 0.00 writes/s, 0.00 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 2 buffer(s)
Hash table size 34679, node heap has 0 buffer(s)
Hash table size 34679, node heap has 4 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)
0.00 hash searches/s, 0.00 non-hash searches/s
---
LOG
---
Log sequence number          19827319
Log buffer assigned up to    19827319
Log buffer completed up to   19827319
Log written up to            19827319
Log flushed up to            19827319
Added dirty pages up to      19827319
Pages flushed up to          19827319
Last checkpoint at           19827319
Log minimum file id is       5
Log maximum file id is       6
392 log i/o's done, 0.00 log i/o's/second
----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 0
Dictionary memory allocated 722057
Buffer pool size   8192
Free buffers       7059
Database pages     1126
Old database pages 395
Modified db pages  0
Pending reads      0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 194, not young 47
0.00 youngs/s, 0.00 non-youngs/s
Pages read 892, created 235, written 983
0.00 reads/s, 0.00 creates/s, 0.00 writes/s
No buffer pool page gets since the last printout
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 1126, 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=46343, Main thread ID=281472325889920 , state=sleeping
Number of rows inserted 11, updated 5, deleted 0, read 47
0.00 inserts/s, 0.00 updates/s, 0.00 deletes/s, 0.00 reads/s
Number of system rows inserted 213, updated 449, deleted 48, read 8710
0.00 inserts/s, 0.00 updates/s, 0.00 deletes/s, 0.00 reads/s
----------------------------
END OF INNODB MONITOR OUTPUT
============================

查看错误日志

SELECT @@log_error;

Innodb 存储引擎可以检测到表锁和行锁,使用Lock Table 这样的语句检测不到,通过设置 innodb_lock_wait_timeout (死锁超时等待)时间,回滚事务
高并发场景,死锁检测会影响性能,设置 innodb_lock_wait_timeout 死锁超时时间性能会更好,发生死锁时到了超时时间直接回滚。
事务中发生死锁也很正常,也没什么,发生死锁无非是两个事务都还处于执行阶段,只要其中一个事务提交,也就不会继续持有数据行的锁,既然没有持有数据行的锁了,其他事务后续提交就行,只是在事务没有提交期间,有新的会话进来对锁定的数据进行修改,删除时会发生阻塞,并发性降低。

死锁的解决方案:

  1. 重启MySQL服务(很粗暴,不推荐)
  2. 提交死锁中的任一事务
  3. 通过 SHOW ENGINE INNODB STATUS; 查看有没有死锁
  4. 通过开启打印死锁日志 innodb_print_all_deadlocks 对发生死锁的原因进行分析
  5. 发生死锁后,事务会被回滚,重新尝试事务操作
  6. 尽可能让事务更新的行少,持续的时间比较短
  7. 使用 select for update 或 select for share 锁定读操作时,使用较低的隔离级别 READ COMMITTED
  8. 修改多张表或同一张表的不同数据时,按照修改顺序进行组织,形成事务队列,可以将多条SQL操作,使用存储过程操作
  9. 精心设计索引,让查询的结果集更小,锁定的行更少
  10. 使用表级序列化事务,对表加锁,事务依次执行,避免死锁的同时却也降低了并发性
SET autocommit=0;
LOCK TABLES t1 WRITE, t2 READ, ...;
... do something with tables t1 and t2 here ...
COMMIT;
UNLOCK TABLES;

你可能感兴趣的:(数据库,sql,mysql)