mysql-死锁

死锁案例大全

注2:RR隔离级别才有gap lock,Read Commited下没有;
事务提交了之后锁才会释放,而不是一条sql之执行完了才释放

死锁日志如何读
在这里插入图片描述

记录锁(Record Lock) 单行记录锁,锁住索引记录,如果没有则锁定隐式主键。
间隙锁(Gap Lock)	锁定一个范围,不包含当前行。
临键锁(Next-Key Lock) 默认行查询使用,`记录锁+间隙锁合体`,锁定一个范围,包含当前行。解决幻读(Phantom),如果是主键且唯一,会降级为记录锁。
意向锁(Intention Locks) 为了支持多粒度(表锁与行锁)的锁并存,引入意向锁,是表级锁。
插入意向锁(Insert Intention Locks) 为了插入时保证正确,范围锁的一种,与Gap锁不兼容。

这里是引用
首先我们要知道对于MySQL有两种常规锁模式
LOCK_S(读锁,共享锁)
LOCK_X(写锁,排它锁)
最容易理解的锁模式,读加共享锁,写加排它锁.
有如下几种锁的属性
LOCK_REC_NOT_GAP (锁记录)
LOCK_GAP (锁记录前的GAP)
LOCK_ORDINARY (同时锁记录+记录前的GAP 。传说中的Next Key锁)
LOCK_INSERT_INTENTION(插入意向锁,其实是特殊的GAP锁)
锁的属性可以与锁模式任意组合。例如.
lock->type_mode 可以是Lock_X 或者Lock_S
locks gap before rec 表示为gap锁:lock->type_mode & LOCK_GAP
locks rec but not gap 表示为记录锁,非gap锁:lock->type_mode & LOCK_REC_NOT_GAP
insert intention 表示为插入意向锁:lock->type_mode & LOCK_INSERT_INTENTION
waiting 表示锁等待:lock->type_mode & LOCK_WAIT

在这里插入图片描述

  1. 间隙锁和任何锁都兼容——间隙锁必然可以加上
  2. 记录锁和带记录的锁冲突(r锁、next-key锁)——记录冲突了就加不上了
  3. next-key和带记录的锁冲突(nextkey、r锁)——分间隙和记录两步枷锁分析
  4. 插入意向锁和带间隙的锁冲突

标题死锁日志的关键词含义

记录锁(LOCK_REC_NOT_GAP): 	lock_mode X locks rec but not gap
间隙锁(LOCK_GAP):	 lock_mode X locks gap before rec
Next-key 锁(LOCK_ORNIDARY): 	lock_mode X
插入意向锁(LOCK_INSERT_INTENTION): 	lock_mode X locks gap before rec insert intention

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

锁选择

update锁什么
一条update语句到底加了多少锁
1)、如果更新条件没有走索引,例如执行”update from t1 set v2=0 where v2=5;” ,此时会进行全表扫描,扫表的时候,要阻止其他任何的更新操作,所以上升为表锁

2)、如果更新条件为索引字段,但是并非唯一索引(包括主键索引),不论表中是否存在该记录,除了会对该记录所在范围加锁(间隙)+ 该记录+还会向右遍历到不满足条件的范围进行加锁(间隙)。
例如执行“update from t1 set v2=0 where v1=9;”

那么此时更新会使用Next-Key Lock。使用Next-Key Lock的原因:

a)、首先要保证在符合条件的记录上加上排他锁,会锁定当前非唯一索引和对应的主键索引的值;

b)、还要保证锁定的区间不能插入新的数据。

3)、如果更新条件为唯一索引,则使用Record Lock(记录锁)。

InnoDB根据唯一索引,找到相应记录,将主键索引值和唯一索引值加上记录锁。但不使用Gap Lock(间隙锁)。MySQL InnoDB 锁表与锁行

**

前面我们说的 GAP LOCK 其实是锁的属性,另外我们知道 InnoDB 常规锁模式有:S 和 X,即共享锁和排他锁。锁模式和锁属性是可以随意组合的

insert锁什么

  1. 在t1插入记录时,是不加锁的。这个时候事务t1还未提交的情况下,事务t2尝试插入的时候,发现有这条记录,t2尝试获取S锁,会判定记录上的事务id是否活跃,如果活跃的话,说明事务未结束,会帮t1把它的隐式锁提升为显式锁(X锁);

  2. 一个已提交但是未purge掉的记录会造成后续插入获取S共享锁,两个事务同时获取S锁,然后尝试获取插入意向锁,造成死锁

  3. insert的加锁策略是,分为两个阶段:

第一阶段,先进行唯一性约束检查,申请LOCK_S 锁
(场景:a:一个已提交但是未purge掉的记录会造成后续插入获取S共享锁
b:已存在的一条记录)
第二阶段,当获取到LOCK_S锁insert成功之后
插入的位置有Gap锁: LOCK_INSERT_INTENTION(插入意向锁),为了防止其他insert 唯一键冲突。
新数据插入:LOCK_X(排他锁) + LOCK_REC_NOT_GAP(记录锁)

**

delete锁什么
MySQL DELETE 删除语句加锁分析
1.删除存在的记录:next-key锁||记录X锁(lock_mode X locks rec but not gap)
1.1在非唯一索引的情况下,删除一条存在的记录是有gap锁,锁住记录本身和记录之前的gap(next-key锁)
1.2在唯一索引和主键的情况下删除一条存在的记录,因为都是唯一值,进行删除的时候,是不会有gap存在——精准(加记录X锁(lock_mode X locks rec but not gap)
2.删除不存在的记录:加 gap 锁(locks gap before rec)
2.1非唯一索引,唯一索引和主键在删除一条不存在、的记录,均会在这个区间加gap锁
3.删除标记为删除的记录:加 Next-key 锁(lock_mode X)
3.1通过非唯一索引和唯一索引去删除一条标记为删除的记录的时候,都会请求该记录的行锁,同时锁住记录之前的gap

RC 情况下是没有gap锁的,除了遇到唯一键冲突的情况,如插入唯一键冲突。

select for update锁什么

select for update锁什么
select for update的详细案例
每个next-key lock都是前开后闭的区间。也就是说select * from t for update;会形成7个next-key lock,分别是:(-∞,0]、(0,5]、(5,10]、(10,15]、(15,20]、(20, 25]、(25, +supremum]。
锁:左间隙+行+右间隙

区别

forupdate、update、delete枷锁案例分析
一个范围匹配矛盾的案例vs上面这个 todo验证

  1. update、select for update、delete都要用锁后面的间隙;即左间隙+中间记录+右间隙
  2. 范围匹配的时候加锁单位是next-key锁,不会有什么优化退化;

todo验证问题:

  1. 是否右边加的锁是间隙,而不是next-key;
  2. 范围匹配加的锁的范围是??唯一&非唯一索引范围匹配的区别??
  3. 普通查询会不会被其他锁阻塞????
  4. 普通索引加锁的时候,还会给主键索引加锁,那么主键加的顺序是??毕竟普通索引下的主见索引的顺序可不是从小到大???
  5. 申请队列中的锁会互相阻塞吗 ——如果申请的是相互包含的锁

待验证问题
待验证问题2

锁的优化建议

  1. 合理设计索引,让 InnoDB 在索引键上面加锁的时候尽可能准确,重复度低的排在前面,可以尽可能的缩小锁定gap范围,避免造成不必要的锁定而影响其他 Query 的执行。
    例如update加间隙锁,即x锁之后,其他就不能查询了)
  2. 尽可能减少基于范围的数据检索过滤条件(因为不是根据非唯一索引的条件的sql,枷锁时候会产生gap锁),避免因为间隙锁带来的负面影响而锁定了不该锁定的记录。(降低加锁的级别,非唯一索引条件的修改操作update会产生间隙锁gap,而这是导致大多死锁的主要原因)
    尽量控制事务的大小,减少锁定的资源量和锁定时间长度。
  3. 在业务环境允许的情况下,尽量使用较低级别的事务隔离,以减少 MySQL 因为实现事务隔离级别所带来的附加成本
  4. 对索引加锁顺序的不一致很可能会导致死锁, 所以如果可以, 尽量以相同的顺序来访问索引记录和表. 在程序以批量方式处理数据的时候, 如果事先对数据排序, 保证每个线程按固定的顺序来处理记录, 也可以大大降低出现死锁的可能.
  5. 避免大事务, 尽量将大事务拆成多个小事务来处理. 因为大事务占用资源多, 耗时长, 与其他事务冲突的概率也会变高
  6. 避免在同一时间点运行多个对同一表进行读写的脚本, 特别注意加锁且操作数据量比较大的语句.
  7. 设置锁等待超时参数:innodb_lock_wait_timeout,在并发访问比较高的情况下,如果大量事务因无法立即获得所需的锁而挂起,会占用大量计算机资源,造成严重性能问题,甚至拖跨数据库。我们通过设置合适的锁等待超时阈值,可以避免这种情况发生。
  8. 用乐观锁进行控制

1)不同的应用访问同一组表时,应尽量约定以相同的顺序访问各表。对一个表而言,应尽量以固定的顺序存取表中的行。这点真的很重要,它可以明显的减少死锁的发生。

举例:好比有a,b两张表,如果事务1先a后b,事务2先b后a,那就可能存在相互等待产生死锁。那如果事务1和事务2都先a后b,那事务1先拿到a的锁,事务2再去拿a的锁,如果锁冲突那就会等待 事务1释放锁,那自然事务2就不会拿到b的锁,那就不会堵塞事务1拿到b的锁,这样就避免死锁了。

2)在主键等值更新的时候,尽量先查询看数据库中有没有满足条件的数据,如果不存在就不用更新,存在才更新。为什么要这么做呢,因为如果去更新一条数据库不存在的数据,一样会产生间隙锁。

举例:如果表中只有id=1和id=5的数据,那么如果你更新id=3的sql,因为这条记录表中不存在,那就会产生一个(1,5)的间隙锁,但其实这个锁就是多余的,因为你去更新一个数据都不存在的数据没有任何意义。

3)尽量使用主键更新数据,因为主键是唯一索引,在等值查询能查到数据的情况下只会产生记录锁,不会产生间隙锁,这样产生死锁的概率就减少了。当然如果是范围查询,一样会产生间隙锁。

4)避免长事务,小事务发送锁冲突的几率也小。这点应该很好理解。

5)在允许幻读和不可重复度的情况下,尽量使用RC的隔离级别,避免gap lock造成的死锁,因为产生死锁经常都跟间隙锁有关,间隙锁的存在本身也是在RR隔离级别来解决幻读的一种措施。

死锁案例:

死锁案例1
死锁案例2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值