MySQL锁与死锁分析

1.环境

MySQL 5.7.26

数据

表 a 中的字段 b_id 外键关联 表 b 中的 id

create table a
(
    id   int auto_increment
        primary key,
    name varchar(64) null,
    age  int         null,
    b_id int         null,
    constraint a_b_id_fk
        foreign key (b_id) references b (id)
);
[
  {
    "id": 2,
    "name": "hsh",
    "age": 24,
    "b_id": 3
  }
]
create table b
(
    id   int auto_increment
        primary key,
    name varchar(64) null,
    time bigint      null
);
[
  {
    "id": 3,
    "name": "h",
    "time": 330
  }
]

2.事务执行流程

通过主、外键更新数据来产生排它锁,阻塞和死锁。

步骤

事务1 id=56389

事务2 id=56390

1

update a

set name='hsh_1'

where id = 2;

2

update a

set age=19

where b_id = 3;

3

update a

set name='hsh_2'

where b_id = 3;

3.阻塞

当执行完第2步时,事务2阻塞,此时事务与锁的状态为:

select *
from information_schema.INNODB_TRX trx
         left join information_schema.INNODB_LOCKS locks on trx.trx_id = locks.lock_trx_id;
[
    {
      "trx_id": "56389",
      "trx_state": "RUNNING",
      "trx_started": "2022-08-20 16:07:43",
      "trx_requested_lock_id": null,
      "trx_wait_started": null,
      "trx_weight": 3,
      "trx_mysql_thread_id": 12,
      "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": "REPEATABLE READ",
      "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,
      "lock_id": "56389:227:3:9",
      "lock_trx_id": "56389",
      "lock_mode": "X",
      "lock_type": "RECORD",
      "lock_table": "`dps`.`a`",
      "lock_index": "PRIMARY",
      "lock_space": 227,
      "lock_page": 3,
      "lock_rec": 9,
      "lock_data": "2"
    },
    {
      "trx_id": "56390",
      "trx_state": "LOCK WAIT",
      "trx_started": "2022-08-20 16:07:55",
      "trx_requested_lock_id": "56390:227:3:9",
      "trx_wait_started": "2022-08-20 16:07:55",
      "trx_weight": 3,
      "trx_mysql_thread_id": 13,
      "trx_query": "/* ApplicationName=DataGrip 2022.2 */ update a set age=19 where b_id=3",
      "trx_operation_state": "starting index read",
      "trx_tables_in_use": 1,
      "trx_tables_locked": 1,
      "trx_lock_structs": 3,
      "trx_lock_memory_bytes": 1136,
      "trx_rows_locked": 2,
      "trx_rows_modified": 0,
      "trx_concurrency_tickets": 0,
      "trx_isolation_level": "REPEATABLE READ",
      "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,
      "lock_id": "56390:227:3:9",
      "lock_trx_id": "56390",
      "lock_mode": "X",
      "lock_type": "RECORD",
      "lock_table": "`dps`.`a`",
      "lock_index": "PRIMARY",
      "lock_space": 227,
      "lock_page": 3,
      "lock_rec": 9,
      "lock_data": "2"
    }
  ]
select * from information_schema.INNODB_LOCK_WAITS;
  [
    {
      "requesting_trx_id": "56390",
      "requested_lock_id": "56390:227:3:9",
      "blocking_trx_id": "56389",
      "blocking_lock_id": "56389:227:3:9"
    }
  ]

分析:

可以看出事务2当前处于锁等待状态,也就是阻塞状态。事务2当前正在请求的锁id为 56390:227:3:9。

事务1对应的锁ID为56389:227:3:9,这两个其实是同一把锁,都是添加在 表空间为227页号为3行记录是9的行上,是加在主键索引上。

4.死锁

再执行步骤3,产生死锁,事务2被kill掉。

[40001][1213] Deadlock found when trying to get lock; try restarting transaction。

死锁日志会在innodb status中记录。

innodb状态

show engine innodb status;

此语句会展示innodb监控信息,包含事务,文件IO,死锁,线程,内存和缓冲池等信息。

INNODB MONITOR OUTPUT
Per second averages calculated from the last 20 seconds
BACKGROUND THREAD
SEMAPHORES
LATEST DETECTED DEADLOCK
TRANSACTIONS
FILE I/O
INSERT BUFFER AND ADAPTIVE HASH INDEX
LOG
BUFFER POOL AND MEMORY
ROW OPERATIONS

下面只展示死锁相关信息:

LATEST DETECTED DEADLOCK
------------------------
2022-08-20 16:08:43 0x17e0
*** (1) TRANSACTION:
TRANSACTION 56390, ACTIVE 48 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s)
MySQL thread id 13, OS thread handle 3788, query id 1277 BOGON 192.168.0.101 root updating
/* ApplicationName=DataGrip 2022.2 */ update a set age=19 where b_id=3
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 227 page no 3 n bits 80 index PRIMARY of table `dps`.`a` trx id 56390 lock_mode X locks rec but not gap waiting
Record lock, heap no 9 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
 0: len 4; hex 80000002; asc     ;;
 1: len 6; hex 00000000dc45; asc      E;;
 2: len 7; hex 300000017018b8; asc 0   p  ;;
 3: len 5; hex 6873685f31; asc hsh_1;;
 4: len 4; hex 80000018; asc     ;;
 5: len 4; hex 80000003; asc     ;;
*** (2) TRANSACTION:
TRANSACTION 56389, ACTIVE 60 sec starting index read, thread declared inside InnoDB 5000
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 12, OS thread handle 6112, query id 1321 BOGON 192.168.0.101 root updating
/* ApplicationName=DataGrip 2022.2 */ update a set name='hsh_2' where b_id=3
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 227 page no 3 n bits 80 index PRIMARY of table `dps`.`a` trx id 56389 lock_mode X locks rec but not gap
Record lock, heap no 9 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
 0: len 4; hex 80000002; asc     ;;
 1: len 6; hex 00000000dc45; asc      E;;
 2: len 7; hex 300000017018b8; asc 0   p  ;;
 3: len 5; hex 6873685f31; asc hsh_1;;
 4: len 4; hex 80000018; asc     ;;
 5: len 4; hex 80000003; asc     ;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 227 page no 4 n bits 72 index a_b_id_fk of table `dps`.`a` trx id 56389 lock_mode X waiting
Record lock, heap no 6 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 4; hex 80000003; asc     ;;
 1: len 4; hex 80000002; asc     ;;
*** WE ROLL BACK TRANSACTION (1)

死锁分析:

当事务2执行更新时,时通过外键更新的,所以不仅会请求 227:3:9 这把锁,同时也会在 227:4:6 这个外键索引上加锁。

当事务1再通过外键更新时,就请求 227:4:6 这把外键锁,此时事务2持有这把锁,由于事务1持有事务2请求的锁,

所以事务2无法释放持有的外键锁,死锁就会产生。这是MySQL检测到了死锁,就会kill掉事务2。

5.外键锁示例

按如下语句执行事务:

步骤

事务1 id=56395

事务2 id=56396

1

update a

set name='hsh_2'

where b_id = 3;

2

update a

set age=19

where b_id = 3;

[
    {
      "trx_id": "56395",
      "trx_state": "RUNNING",
      "trx_started": "2022-08-20 17:05:16",
      "trx_requested_lock_id": null,
      "trx_wait_started": null,
      "trx_weight": 4,
      "trx_mysql_thread_id": 12,
      "trx_query": null,
      "trx_operation_state": null,
      "trx_tables_in_use": 0,
      "trx_tables_locked": 1,
      "trx_lock_structs": 3,
      "trx_lock_memory_bytes": 1136,
      "trx_rows_locked": 3,
      "trx_rows_modified": 1,
      "trx_concurrency_tickets": 0,
      "trx_isolation_level": "REPEATABLE READ",
      "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,
      "lock_id": "56395:227:4:6",
      "lock_trx_id": "56395",
      "lock_mode": "X",
      "lock_type": "RECORD",
      "lock_table": "`dps`.`a`",
      "lock_index": "a_b_id_fk",
      "lock_space": 227,
      "lock_page": 4,
      "lock_rec": 6,
      "lock_data": "3, 2"
    },
    {
      "trx_id": "56396",
      "trx_state": "LOCK WAIT",
      "trx_started": "2022-08-20 17:05:33",
      "trx_requested_lock_id": "56396:227:4:6",
      "trx_wait_started": "2022-08-20 17:05:33",
      "trx_weight": 2,
      "trx_mysql_thread_id": 13,
      "trx_query": "/* ApplicationName=DataGrip 2022.2 */ update a\nset age=19\nwhere b_id = 3",
      "trx_operation_state": "starting index read",
      "trx_tables_in_use": 1,
      "trx_tables_locked": 1,
      "trx_lock_structs": 2,
      "trx_lock_memory_bytes": 1136,
      "trx_rows_locked": 1,
      "trx_rows_modified": 0,
      "trx_concurrency_tickets": 0,
      "trx_isolation_level": "REPEATABLE READ",
      "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,
      "lock_id": "56396:227:4:6",
      "lock_trx_id": "56396",
      "lock_mode": "X",
      "lock_type": "RECORD",
      "lock_table": "`dps`.`a`",
      "lock_index": "a_b_id_fk",
      "lock_space": 227,
      "lock_page": 4,
      "lock_rec": 6,
      "lock_data": "3, 2"
    }
  ]

从"lock_index": "a_b_id_fk" 可以看出此时的锁是外键锁,

锁的记录为 227:4:6 上的外键锁。

事务与锁 表中的具体字段说明,参见

information_schema.INNODB_TRX表详解_CaptHua的博客-CSDN博客_information_schema.innodb_trx

INNODB_LOCKS与INNODB_LOCK_WAITS表_CaptHua的博客-CSDN博客

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