Skip to content

LATEST DETECTED DEADLOCK

xiaoboluo768 edited this page Jun 13, 2020 · 2 revisions
  • 第五段信息

    • 与外键错误一样,这部分只有当服务器产生死锁时才会出现,死锁信息同样在每次有新的死锁错误时被重写,percona toolkit中的pt-deadlock-logger工具可以用表保存这些信息以供后续分析死锁在等待关系图里是一个循环,就是一个锁定了行的数据结构又在等待别的锁,这个循环可以任意地大,innodb会立即检测到死锁,因为每当有事务等待行锁的时候,它都会去检查等待关系图里是否有循环,死锁的情况可能会比较复杂,但是,这一部分只显示了最近的两个死锁的情况,它们在各自的事务里执行的最后一条语句,以及它们在等待关系图里形成环锁的信息。在这个循环里你看不到其他事务,也可能看不到在事务里早先真正获得了锁的语句,尽管如此,通常还是可以通过查看这些输出结果来确定到底是什么引起了死锁。
    • 在innodb里实际上有两种死锁,第一种就是常常碰到的那种,它在等待关系图里是一个真正的循环,另外一种就是在一个等待关系图里,因代价昂贵而无法检测它是不是包含了循环,如果innodb要在关系图里检查超过100W个锁,或者在检查过程中,innodb要重做200个以上的事务,那它会放弃,并宣布这里有一个死锁,这些数值都是硬编码在innodb代码里的常量,无法配置(如果你NB可以修改代码然后重新编译)。第二种死锁报错你可以在输出里看到一条信息:TOO DEEP OR LONG SEARCH IN THE LOCK TABLE WAITS-FOR GRAPH
    • innodb不仅会打印出事务和事务持有和等待的锁,而且还有记录本身,不幸的是,它至于可能超过为输出结果预留的长度(只能打印1M的内容且只能保留最近一次的死锁信息),如果你无法看到完整的输出,此时可以在任意库下创建innodb_monitor或innodb_lock_monitor表,这样innodb status信息会完整且每15s一次被记录到错误日志中。如:create table innodb_monitor(a int)engine=innodb;,不需要记录到错误日志中时就删掉这个表即可。
  • 下面也搞一个示例来看看:

# 建表:
mysql> create table test_deadlock(id int unsigned not null primary key auto_increment,test int unsigned not null);
Query OK, 0 rows affected (0.02 sec)

# 插入测试数据:
mysql> insert into test_deadlock(test) values(1),(2),(3),(4),(5);
Query OK, 5 rows affected (0.00 sec)
Records: 5  Duplicates: 0  Warnings: 0

# 打开两个会话终端:
## 会话1执行下面的SQL:
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test_deadlock where id=1 for update;
+----+------+
| id | test |
+----+------+
|  1 |    1 |
+----+------+

1 row in set (0.00 sec)

## 接着会话2执行下面的SQL:
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test_deadlock where id=2 for update;
+----+------+
| id | test |
+----+------+
|  2 |    2 |
+----+------+

1 row in set (0.00 sec)

## 回到会话1执行下面的SQL,会发生等待:
mysql> select * from test_deadlock where id=2 for update;

## 回到会话2执行下面的SQL,产生死锁,会话2被回滚:
mysql> select * from test_deadlock where id=1 for update;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
  • 查看innodb status信息:
------------------------
LATEST DETECTED DEADLOCK
------------------------
160128  1:51:53  #这里显示了最近一次发生死锁的日期和时间
*** (1) TRANSACTION:
TRANSACTION D20847, ACTIVE 141 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 376, 2 row lock(s)
MySQL thread id 20027, OS thread handle 0x7f0a4c0f8700, query id 1818124 localhost root statistics
select * from test_deadlock where id=2 for update
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 441 page no 3 n bits 72 index `PRIMARY` of table `xiaoboluo`.`test_deadlock` trx id D20847 lock_mode X locks rec but not gap waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
0: len 4; hex 00000002; asc     ;;
1: len 6; hex 000000d20808; asc       ;;
2: len 7; hex ad000001ab011d; asc        ;;
3: len 4; hex 00000002; asc     ;;

*** (2) TRANSACTION:
TRANSACTION D20853, ACTIVE 119 sec starting index read
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1248, 2 row lock(s)
MySQL thread id 20081, OS thread handle 0x7f0a0f020700, query id 1818204 localhost root statistics
select * from test_deadlock where id=1 for update
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 441 page no 3 n bits 72 index `PRIMARY` of table `xiaoboluo`.`test_deadlock` trx id D20853 lock_mode X locks rec but not gap
Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
0: len 4; hex 00000002; asc     ;;
1: len 6; hex 000000d20808; asc       ;;
2: len 7; hex ad000001ab011d; asc        ;;
3: len 4; hex 00000002; asc     ;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 441 page no 3 n bits 72 index `PRIMARY` of table `xiaoboluo`.`test_deadlock` trx id D20853 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 4; hex 00000001; asc     ;;
1: len 6; hex 000000d20808; asc       ;;
2: len 7; hex ad000001ab0110; asc        ;;
3: len 4; hex 00000001; asc     ;;

*** WE ROLL BACK TRANSACTION (2)
  • 这部分内容比较多,下面分段逐一进行解释:
# 下面这部分显示的是死锁的第一个事务的信息:
*** (1) TRANSACTION:
TRANSACTION D20847, ACTIVE 141 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 376, 2 row lock(s)
MySQL thread id 20027, OS thread handle 0x7f0a4c0f8700, query id 1818124 localhost root statistics
select * from test_deadlock where id=2 for update
TRANSACTION D20847, ACTIVE 141 sec starting index read:这行表示事务D20847,ACTIVE 141 sec表示事务处于活跃状态141s,starting index read表示正在使用索引读取数据行
mysql tables in use 1, locked 1#这行表示事务D20847正在使用1个表,且涉及锁的表有1个

LOCK WAIT 3 lock struct(s), heap size 376, 2 row lock(s) #这行表示在等待3把锁,占用内存376字节,涉及2行记录,\
如果事务已经锁定了几行数据,这里将会有一行信息显示出锁定结构的数目(注意,这跟行锁是两回事)和堆大小,\
堆的大小指的是为了持有这些行锁而占用的内存大小,Innodb是用一种特殊的位图表来实现行锁的,从理论上讲,
它可将每一个锁定的行表示为一个比特,经测试显示,每个锁通常不超过4比特

#这行表示该事务的线程ID信息,操作系统句柄信息,连接来源、用户
MySQL thread id 20027, OS thread handle 0x7f0a4c0f8700, query id 1818124 localhost root statistics 

select * from test_deadlock where id=2 for update #这行表示事务涉及的SQL

# 下面这一部分显示的是当死锁发生时,第一个事务正在等待的锁等信息:
*** (1) WAITING FOR THIS LOCK TO BE GRANTED: #这行信息表示第一个事务正在等待锁被授予
RECORD LOCKS space id 441 page no 3 n bits 72 index `PRIMARY` of table `xiaoboluo`.`test_deadlock` trx id D20847 lock_mode X locks rec but not gap waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
0: len 4; hex 00000002; asc     ;;
1: len 6; hex 000000d20808; asc       ;;
2: len 7; hex ad000001ab011d; asc        ;;
3: len 4; hex 00000002; asc     ;;

#这行信息表示等待的锁是一个record lock,空间id是441,页编号为3,大概位置在页的72位处,锁发生在表xiaoboluo.test_deadlock的主键上,是一个X锁,但是不是gap lock。 waiting表示正在等待锁
RECORD LOCKS space id 441 page no 3 n bits 72 index `PRIMARY` of table `xiaoboluo`.`test_deadlock` trx id D20847 lock_mode X locks rec but not gap waiting

Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 #这行表示record lock的heap no 位置

# 这部分剩下的内容只对调试才有用。
0: len 4; hex 00000002; asc     ;;
1: len 6; hex 000000d20808; asc       ;;
2: len 7; hex ad000001ab011d; asc        ;;
3: len 4; hex 00000002; asc     ;;

# 下面这部分是事务二的状态:
*** (2) TRANSACTION:
TRANSACTION D20853, ACTIVE 119 sec starting index read #事务2处于活跃状态119s
mysql tables in use 1, locked 1 #正在使用1个表,涉及锁的表有1个
3 lock struct(s), heap size 1248, 2 row lock(s) #涉及3把锁,2行记录
MySQL thread id 20081, OS thread handle 0x7f0a0f020700, query id 1818204 localhost root statistics
select * from test_deadlock where id=1 for update #第二个事务的SQL

# 下面这部分是事务二的持有锁信息:
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 441 page no 3 n bits 72 index `PRIMARY` of table `xiaoboluo`.`test_deadlock` trx id D20853 lock_mode X locks rec but not gap
Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
0: len 4; hex 00000002; asc     ;;
1: len 6; hex 000000d20808; asc       ;;
2: len 7; hex ad000001ab011d; asc        ;;
3: len 4; hex 00000002; asc     ;;

RECORD LOCKS space id 441 page no 3 n bits 72 index `PRIMARY` of table `xiaoboluo`.`test_deadlock` trx id D20853 lock_mode X locks rec but not gap
#从这两行持有锁信息计息后面几行调试信息上看,就是事务1正在等待的锁。
Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0

# 下面这部分是事务二正在等待的锁,从下面的信息上看,等待的是同一个表,同一个索引,同一个page上的record lock X锁,但是heap no位置不同,即不同的行上的锁:
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 441 page no 3 n bits 72 index `PRIMARY` of table `xiaoboluo`.`test_deadlock` trx id D20853 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 4; hex 00000001; asc     ;;
1: len 6; hex 000000d20808; asc       ;;
2: len 7; hex ad000001ab0110; asc        ;;
3: len 4; hex 00000001; asc     ;;

*** WE ROLL BACK TRANSACTION (2) #这个表示事务2被回滚,因为两个事务的回滚开销一样,所以选择了后提交的事务进行回滚,\
如果两个事务回滚的开销不同(undo 数量不同),那么就回滚开销最小的那个事务。当一个事务持有了其他事务需要的锁,\
同时又想获得其他事务持有的锁时,等待关系图上就会产生循环,Innodb不会显示所有持有和等待的锁,但是,它显示了足够的信息来帮你确定,\
查询操作正在使用哪些索引,这对于你确定能否避免死锁有极大的价值。

# 如果能使两个查询对同一个索引朝同一个方向进行扫描,就能降低死锁的数目,因为,查询在同一个顺序上请求锁的时候不会创建循环,有时候,\
这是很容易做到的,如:要在一个事务里更新许多条记录,就可以在应用程序的内存里把它们按照主键进行排序,然后,\
再用同样的顺序更新到数据库里,这样就不会有死锁发生,但是在另一些时候,这个方法也是行不通的(如果有两个进程使用了不同的索引区间操作同一张表的时候)。

上一篇:LATEST FOREIGN KEY ERROR | 下一篇:TRANSACTIONS

Clone this wiki locally