死锁示例

两个小工具,MySQL死锁分析

多个事务并发执行update出现的数据库死锁问题排除

可以通过SHOW ENGINE INNODB STATUS;来查看死锁日志

死锁日志:

** (1)  TRANSACTION :
TRANSACTION  6648945293, ACTIVE 0 sec starting  index  read
mysql tables  in  use 3, locked 3
LOCK WAIT 5 lock struct(s), heap  size  1184, 4 row lock(s)
MySQL thread id 7953966, OS thread handle 0x7f5b58350700, query id 8422437535 10.129.128.237 promcenter Searching  rows  for  update
update  pc_coupon_0200
         set
         used_time =  null ,
         gmt_modified =  '2017-08-31 00:00:00.841' ,
         status =  'NOT_USED' ,
         trade_no =  null
         where
         code =  '4ab5bf23-d09e-4947-8e83-4e6619c1f750'
         and  user_id = 29096550200
*** (1) WAITING  FOR  THIS LOCK  TO  BE GRANTED:
RECORD LOCKS  space  id 285 page  no  33715 n bits 152  index  ` PRIMARY of  table  `promcenter`.`pc_coupon_0200` trx id 6648945293 lock_mode X locks rec but  not  gap waiting
Record lock, heap  no  79 PHYSICAL RECORD: n_fields 22; compact format; info bits 0
  0: len 8; hex 8000000000192b58;  asc        +X;;
  1: len 6; hex 000188c2bcfa;  asc        ;;
  2: len 7; hex 1e00001dd70680;  asc         ;;
  3: len 8; hex 80000000000009e2;  asc          ;;
  4: len 28; hex e696b0e4babae4b893e4baab35e58583e697a0e997a8e6a79be588b8;  asc              5               ;;
  5: len 8; hex 80000006c64a1f38;  asc       J 8;;
  6: SQL  NULL ;
  7: SQL  NULL ;
  8: SQL  NULL ;
  9: len 30; hex 30626430373532632d636638662d346264352d383961302d373064313266;  asc  0bd0752c-cf8f-4bd5-89a0-70d12f; (total 36 bytes);
  10: SQL  NULL ;
  11: len 4; hex 55534544;  asc  USED;;
  12: len 5; hex 999d6e0a7b;  asc    n {;;
  13: len 5; hex 999d795e39;  asc    y^9;;
  14: len 5; hex 999d6e0a7b;  asc    n {;;
  15: len 5; hex 999d8d7efb;  asc     ~ ;;
  16: len 5; hex 999d6e0a7b;  asc    n {;;
  17: len 5; hex 999d795e39;  asc    y^9;;
  18: len 30; hex 66646635363862312d663036632d346232382d383935382d636463316433;  asc  fdf568b1-f06c-4b28-8958-cdc1d3; (total 36 bytes);
  19: len 7; hex 74726964656e74;  asc  trident;;
  20: len 27; hex 75736572696e766974653a32393039363535303230303a32353330;  asc  userinvite:29096550200:2530;;
  21: len 3; hex 414c4c;  asc  ALL ;;
*** (2)  TRANSACTION :
TRANSACTION  6648945294, ACTIVE 0 sec starting  index  read
mysql tables  in  use 3, locked 3
4 lock struct(s), heap  size  1184, 3 row lock(s)
MySQL thread id 7953138, OS thread handle 0x7f5b3e8b7700, query id 8422437534 10.129.129.119 promcenter Searching  rows  for  update
update  pc_coupon_0200
         set
         used_time =  null ,
         gmt_modified =  '2017-08-31 00:00:00.841' ,
         status =  'NOT_USED' ,
         trade_no =  null
         where
         code =  '0bd0752c-cf8f-4bd5-89a0-70d12fd26dd3'
         and  user_id = 29096550200
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS  space  id 285 page  no  33715 n bits 152  index  ` PRIMARY of  table  `promcenter`.`pc_coupon_0200` trx id 6648945294 lock_mode X locks rec but  not  gap
Record lock, heap  no  79 PHYSICAL RECORD: n_fields 22; compact format; info bits 0
  0: len 8; hex 8000000000192b58;  asc        +X;;
  1: len 6; hex 000188c2bcfa;  asc        ;;
  2: len 7; hex 1e00001dd70680;  asc         ;;
  3: len 8; hex 80000000000009e2;  asc          ;;
  4: len 28; hex e696b0e4babae4b893e4baab35e58583e697a0e997a8e6a79be588b8;  asc              5               ;;
  5: len 8; hex 80000006c64a1f38;  asc       J 8;;
  6: SQL  NULL ;
  7: SQL  NULL ;
  8: SQL  NULL ;
  9: len 30; hex 30626430373532632d636638662d346264352d383961302d373064313266;  asc  0bd0752c-cf8f-4bd5-89a0-70d12f; (total 36 bytes);
  10: SQL  NULL ;
  11: len 4; hex 55534544;  asc  USED;;
  12: len 5; hex 999d6e0a7b;  asc    n {;;
  13: len 5; hex 999d795e39;  asc    y^9;;
  14: len 5; hex 999d6e0a7b;  asc    n {;;
  15: len 5; hex 999d8d7efb;  asc     ~ ;;
  16: len 5; hex 999d6e0a7b;  asc    n {;;
  17: len 5; hex 999d795e39;  asc    y^9;;
  18: len 30; hex 66646635363862312d663036632d346232382d383935382d636463316433;  asc  fdf568b1-f06c-4b28-8958-cdc1d3; (total 36 bytes);
  19: len 7; hex 74726964656e74;  asc  trident;;
  20: len 27; hex 75736572696e766974653a32393039363535303230303a32353330;  asc  userinvite:29096550200:2530;;
  21: len 3; hex 414c4c;  asc  ALL ;;
*** (2) WAITING  FOR  THIS LOCK  TO  BE GRANTED:
RECORD LOCKS  space  id 285 page  no  33489 n bits 792  index  `ix_user_id`  of  table  `promcenter`.`pc_coupon_0200` trx id 6648945294 lock_mode X locks rec but  not  gap waiting
Record lock, heap  no  342 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
  0: len 8; hex 80000006c64a1f38;  asc       J 8;;
  1: len 8; hex 8000000000192b58;  asc        +X;;
*** WE ROLL BACK  TRANSACTION  (2)
 

mysql 事务具有acid属性,分别是代表原子性,隔离性,一致性,持久性。

根据原子性可以知道事务操作是不可再分的,每个事务要么全部成功要么全部失败,通过隔离性可以知道:事务之间不会相互影响。

但是本次为什么还出现了并发事务出现了死锁问题呢?

 

根据死锁日志可以提取几个有用信息:

 
trx1(事务1)
trx2(事务2)
id66489452936648945294
特征活跃0秒,正在index读活跃0秒,正在index读
原因Searching rows for updateSearching rows for update
sql

update pc_coupon_0200
set
used_time = null,
gmt_modified = '2017-08-31 00:00:00.841',
status = 'NOT_USED',
trade_no = null
where
code = '4ab5bf23-d09e-4947-8e83-4e6619c1f750'
and user_id = 29096550200

update pc_coupon_0200
set
used_time = null,
gmt_modified = '2017-08-31 00:00:00.841',
status = 'NOT_USED',
trade_no = null
where
code = '0bd0752c-cf8f-4bd5-89a0-70d12fd26dd3'
and user_id = 29096550200

锁定行数43

正在等待的锁特性/

正在持有锁特性

等待:{聚集索引id:285,页码:33715,锁类型:排他锁,非GAP(间隙)锁,

事务号:6648945293,索引:主键索引}

 

持有:idx_user_id的锁

持有:{锁id:285,页码:33715,锁类型:共享锁,非GAP(间隙)锁,

事务号:6648945293,索引:主键索引}

等待:

{聚集索引id:285,页码:33489,锁类型:排他锁,非GAP(间隙)锁,

事务号:6648945293,索引:idx_user_id}

是否回滚否  是

首先mysql默认的隔离级别是可重复读,事务未提交之前总是读到相同的记录,该隔离级别就是为了避免读已提交出现的幻读现象,采用的是GAP间隙锁实现。

根据上表可以得到信息,两个事务都未提交,或者说行锁锁定的记录之外没有其他事务提交的与之有关的记录,所以都未用到gap锁,有点绕。。。

根据日志可以发现update语句其实就是select xxx for update,这个语句会持有排他锁(共享锁是in share mode)。

事务1等待排他锁,事务2持有事务1的共享锁,并且等待排他锁。这样就能死锁了??为什么事务1没有持有事务2的共享锁

mysql官方有个bug帖子,如下:

https://bugs.mysql.com/bug.php?id=77209

建议:

Do not use index merge when single index is good enough
Try to avoid using index merge in UPDATE to not provoke deadlocks

 

所以在写sql的时候能用一个索引尽量不要使用两个混合索引去更新,可以先根据索引查询出结果,再执行更新。

 

另:

解决方案

  1. 使用组合索引(a+b)
  2. 关闭index merge优化器

mysql innodb行级锁的前提条件是建立索引,行级锁并不是直接锁记录,而是锁索引。

a先根据code锁住了index然后锁住了primary key,然后根据user_id取锁定对应的index

b同时根据user_id锁住了index然后取锁定primary key,再去锁code的index和primary key

当a根据code锁定住primary key的时候,b锁定了user_id的index

这时候a没办法锁定user_id,b没办法锁定primary key->导致死锁

字符串与整数之间的强制类型转换,行锁升级为表锁

环境准备:事务隔离级别RR

set session autocommit=0;

create table t (
id int(20) primary key AUTO_INCREMENT,
cell varchar(20) unique
)engine=innodb;

事务数据:
Session A:
start transaction;

insert into t(cell)values(44444444444);  [1]
update t set cell=123 where cell=44444444444; [3] 

Session B:
start transaction;

insert into t(cell) values(55555555555); [2] 
update t set cell=456 where cell=55555555555; [4]

[1][2][3][4]为执行时序

现象:

insert into t(cell)values(44444444444);  [1]
事务A插入数据,最先执行

结果:插入成功

insert into t(cell) values(55555555555); [2]
事务B插入数据,第二执行

结果:插入成果

update t set cell=123 where cell=44444444444; [3]
事务A修改[1]中插入的数据,第三执行

结果:阻塞,等待执行结果

update t set cell=456 where cell=55555555555; [4]
事务B修改[2]中插入的数据,最后执行

结果:
(1)事务B死锁,事务B被回滚;
(2)事务A中,[3]语句阻塞结束,执行成功;

结果分析:

两个事务,各自修改自己插入的数据,却产生了死锁,确实诡异。

分析见原文。

结论:

整数强制转换为字符串,导致update因为没有命中索引,导致对每一行都要加锁。并发时,互相等待对方insert时的行锁而导致dead lock。

 

 

 

 

转载于:https://www.cnblogs.com/fanguangdexiaoyuer/p/11252698.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值