首页 > 数据库 >MySql —— 死锁场景

MySql —— 死锁场景

时间:2023-07-26 10:35:29浏览次数:54  
标签:事务 加锁 间隙 会话 插入 死锁 场景 MySql id

参考:

小林coding: https://xiaolincoding.com/mysql/lock/deadlock.html

出世&入世:https://blog.csdn.net/weixin_54828627/article/details/129379140

 

死锁的四个必要条件:

  • 互斥
  • 占有且等待
  • 不可强占用
  • 循环等待

一、批量更新/删除数据-同时加锁记录多

InnoDB是边扫描边加锁的。

因为是边扫描边加锁,这里就存在一个顺序问题,假如线程A对a b c d e五条数据边扫描边加X锁,而同时线程B对 e f g h a五条数据也边扫描边加X锁,明显的,这就会存在一个问题,在线程A对e加锁时,线程B已经对e加锁了,所以线程A会等待线程B释放锁,而线程B对a加锁时,线程A也对a加锁了,所以线程B就会等待线程A释放锁,最终结果是,互相循环等待造成死锁。

场景一:

TIP

网上很多资料说,update 没加锁索引会加表锁,这是不对的。

Innodb 源码里面在扫描记录的时候,都是针对索引项这个单位去加锁的, update 不带索引就是全表扫扫描,也就是表里的索引项都加锁(会对所有记录加上 next-key 锁(记录锁 + 间隙锁)),相当于锁了整张表,所以大家误以为加了表锁。

这是我遇到的场景:

有一个会话排队的队列表 queue,与这个案例有关的主要的字段有:

  • social_queue_id : 排队 id,主键
  • session_id:会话 id,一个会话一般只有一条记录(所以严格不算批量更新,但是全表扫描到的记录多)。但是该字段上没加索引 
  • queue_id:这次排队所在的队列

在关闭会话时,会把这个会话的排队信息从 queue 表移入 queue_his,也就是说先插入 queue_his ,再从 queue 删除

  1. delete queue where session_id = 2789
  2. delete queue where session_id = 2795
  3. delete queue where session_id = 2777
  4. ......

当多个会话同时关闭,都需要从 queue 移入 queue_his 时,就会同时执行上面那条 sql。由于是 session_id 上没有索引,那么就会全表扫描(扫描主键索引),对所有记录加上 next-key lock,两个事务同时对全表边扫描边加锁,就会发生同时持有部分数据的锁,同时等待对方那部分数据的锁。

解决方法:

  1. session_id 上建立二级索引
  2. 根据 session_id 查出所有满足条件的主键:social_queue_id ,再根据主键删除

场景二:

这也是我遇到的场景:

有一个客服与客户的聊天消息表 :message 表,与这个案例有关的主要的字段有:

  • message_id:消息 id ,主键
  • session_id:会话 id,一个会话中有多条消息,即一个 session_id 对应多个 message_id。session_id 上有二级索引
  • msg_state:消息状态,客服已读这个会话的时候,会根据 session_id 更改这个会话下所有未读消息的 msg_state

在客服关闭会话的时候,会把这个会话下所有的消息移入 消息历史表,也就是说会从 message 表删除,再插入 message_his 表

而关闭会话的时候,在移入历史表之前,会给客户发送关闭会话的提示语,并将提示语插入 message 表。客服点进这个会话后(如果这个客服已经保持在这个聊天窗口,收到消息后就会立马调用),会调用一个接口根据 session_id 更改这个会话下所有消息的 msg_state

  1. update message set msg_state = 'B' where session_id = 1078;
  2. delete message where session_id = 1078;

上面这两个操作同一个会话的 sql 是两个线程,也是两个事务,但是是几乎同时执行的。

那么就会发生上面说的,两个事务边扫描,边加锁,对扫描到符合条件的 session_id 二级索引行加 next-key lock(X锁及间隙加GAP锁),然后再根据二级索引里的主键信息去扫描聚簇索引对主键行依次加X锁;

两个事务都持有对方要加锁数据的部分锁,相互等待,发生了死锁。

 

二、先间隙锁再向间隙插入

场景一:

两事务同时持有同一个间隙的间隙锁

  • Time1 阶段:由于 id=25 这条记录不存在,所以事务 A 在主键索引(INDEX_NAME : PRIMARY)上加的是间隙锁,锁范围是(20, 30)
  • Time2 阶段:由于 id=36 这条记录不存在,所以事务 B 在主键索引(INDEX_NAME : PRIMARY)上加的也是是间隙锁,锁范围是(20, 30)

事务 A 和 事务 B 的间隙锁范围都是一样的,为什么不会冲突?

对于同一个间隙,两个事务的间隙锁和间隙锁之间是相互兼容的。

在MySQL官网上还有一段非常关键的描述:

Gap locks in InnoDB are “purely inhibitive”, which means that their only purpose is to prevent other transactions from Inserting to the gap. Gap locks can co-exist. A gap lock taken by one transaction does not prevent another transaction from taking a gap lock on the same gap. There is no difference between shared and exclusive gap locks. They do not conflict with each other, and they perform the same function.

间隙锁的意义只在于阻止区间被插入,因此是可以共存的。一个事务获取的间隙锁不会阻止另一个事务获取同一个间隙范围的间隙锁,共享(S型)和排他(X型)的间隙锁是没有区别的,他们相互不冲突,且功能相同。

两事务再同时向这个间隙插入数据

  • Time3 阶段:事务 A 的状态为等待状态(LOCK_STATUS: WAITING),因为被事务 B 生成的间隙锁(范围 (20, 30))阻塞,所以事务 A 的插入操作生成了一个插入意向锁(LOCK_MODE:INSERT_INTENTION)。
  • Time4 阶段:事务 B 的状态为等待状态(LOCK_STATUS: WAITING),因为被事务 A 生成的间隙锁(范围 (20, 30))阻塞,所以事务 B 的插入操作生成了一个插入意向锁(LOCK_MODE:INSERT_INTENTION)。

Insert 语句是怎么加锁的?

Insert 语句在正常执行时是不会生成锁结构的,它是靠聚簇索引记录自带的 trx_id 隐藏列来作为隐式锁来保护记录的。隐式锁是指,当事务需要加锁的时,如果这个锁不可能发生冲突,InnoDB会跳过加锁环节,这种机制称为隐式锁。

隐式锁就是在 Insert 过程中不加锁,只有在特殊情况下,才会将隐式锁转换为显示锁,这里我们列举两个场景。

  • 如果要插入的间隙被别的事务加了间隙锁,为了避免幻读,此时是不能插入记录的;会生成一个插入意向锁,并阻塞等待间隙锁释放。
  • 如果 Insert 的记录和已有记录存在唯一键冲突,此时也不能插入记录;插入就会失败,然后对于这条记录加上了 S 型的锁。
    • 如果主键索引重复,插入新记录的事务会给已存在的主键值重复的聚簇索引记录添加 S 型记录锁。
    • 如果唯一二级索引重复,插入新记录的事务都会给已存在的二级索引列值重复的二级索引记录添加 S 型 next-key 锁。

插入意向锁是什么?

插入意向锁的生成时机:

  • 每插入一条新记录,都需要看一下待插入的间隙是否已经被别的事务加上了间隙锁,如果已加间隙锁,此时会生成一个插入意向锁,然后锁的状态设置为等待状态PS:MySQL 加锁时,是先生成锁结构,然后设置锁的状态,如果锁状态是等待状态,并不是意味着事务成功获取到了锁,只有当锁状态为正常状态时,才代表事务成功获取到了锁),现象就是 Insert 语句会被阻塞。

在MySQL的官方文档中有以下重要描述:

An Insert intention lock is a type of gap lock set by Insert operations prior to row Insertion. This lock signals the intent to Insert in such a way that multiple transactions Inserting into the same index gap need not wait for each other if they are not Inserting at the same position within the gap. Suppose that there are index records with values of 4 and 7. Separate transactions that attempt to Insert values of 5 and 6, respectively, each lock the gap between 4 and 7 with Insert intention locks prior to obtaining the exclusive lock on the Inserted row, but do not block each other because the rows are nonconflicting.

场景二:

 业务主要逻辑就是新增订单、修改订单、查询订单等操作。然后因为订单是不能重复的,所以当时在新增订单的时候做了幂等性校验,做法就是在新增订单记录之前,先通过 select ... for update 语句查询订单是否存在,如果不存在才插入订单记录。

假设这时有两事务,一个事务要插入订单 1007 ,另外一个事务要插入订单 1008,因为需要对订单做幂等性校验,所以两个事务先要查询该订单是否存在,不存在才插入记录,过程如上。

因为 select...for update 和 update 一样,也是当前读&加独占锁(这里也是因为记录不存在退化为间隙锁,间隙锁之间是兼容的)。所以发生死锁的原因和场景一一样,就不继续描述了。

标签:事务,加锁,间隙,会话,插入,死锁,场景,MySql,id
From: https://www.cnblogs.com/suBlog/p/17580963.html

相关文章

  • 转:MySQL数据库给表添加索引
    MySQL数据库给表添加索引   ......
  • 万字长文浅析配置对MySQL服务器的影响
    有很多的服务器选项会影响这MySQL服务器的性能,比如内存中临时表的大小、排序缓冲区等。有些针对特定存储引擎(如InnoDB)的选项,也会对查询优化很有用。调整服务器的配置从某种程度来说是一个影响全局的行为,因为每个修改都可能对该服务器上的每个查询造成影响。不过有些选项是针对特......
  • 从另一电脑复制下来的MYSQL的数据文件(包括FRM IBD)快速恢复到另一MYSQL服务器过程
    从另一电脑复制下来的MYSQL的数据文件(包括FRMIBD)快速恢复到另一MYSQL服务器过程:1.安装mysql最好相同的版本,安装Navicateformysql,连接相应的服务器2.安装mysql-utilities,地址:https://downloads.mysql.com/archives/utilities/以恢复td_gov_company_abnormal.frm为例:3.C......
  • MySQL group by分组后,将每组所得到的id拼接起来
    背景需要将商品表中的sku按照spu_id分组后,并且得到每个spu下的sku_id,需要使用到group_concat函数selectspu_id,count(*),group_concat(idSEPARATOR',')asidsfromproduct_skuwherecategory='tv'groupbyspu_id;group_concat函数group_concat函数,实现分组查......
  • MySQL查询阻塞该如何解决
    MySQL是广泛使用的开源数据库管理系统,它提供了方便的查询功能。然而,在高并发访问的情况下,可能出现查询阻塞的情况。下面是一些解决此问题的方法。SHOWFULLPROCESSLIST;可以使用上述命令查看所有正在执行的SQL查询,并查看它们是否阻塞其他查询。如果有查询阻塞了其他查询,可以使......
  • Mysql主从复制
    介绍MySQL主从复制时一个异步的复制过程,底层时基于MySQL数据库自带的二进制日志功能。就是一台或者多台MySQL数据库(slave从库)从另一台MySQL(master主库)进行日志的复制然后再解析日志并应用到自身,最终实现从库的数据和主库的数据保持一致。MySQL主从复制时MySQL数据库自带功能,无......
  • C#委托的20种表达方式,每一种优缺点和应用场景
    C#委托有多种表达方式,每一种都有各自的优缺点和适用场景。以下为常见的20种表达方式:1.声明委托类型,并使用委托关键字进行定义:```c#delegatevoidMyDelegate(intvalue);```优点:简单明了,易于理解和使用。缺点:需要额外的代码定义委托。2.使用匿名方法:```c#MyDelegatemyDelegat......
  • MySQL 事务
    事务1.概念事务是一组命令的集合,强调整体性。以starttransaction或begin开始,以commit或callback结束。starttransactionupdate...1update...2commitbeginupdate...3update...4rollbackcommit表示提交本次事务,完成修改。(若失败,自动回滚回begin......
  • MySQL日志文件简记
    日志文件binlogbinlog主要记录了MySQL数据库执行了更改的所有操作,主要用来做主从复制,数据恢复记录模式:Statement模式:每一条回修改数据的sql都会被记录在日志中Row模式:每一行具体变更是俺都会被记录在binlog中混合模式刷盘时机选择0:由系统自行判断何时刷盘1:每次提交事务......
  • mysql 两表关联更新
    在实际情况下,其中一张表里的数据没有赋值到关联表中,可以使用下面的关联方式进行更新 把b表的name给a表的nameUPDATEtable_aaLEFTJOINtable_bbONp.id=a.uidSETa.name=b.nameWHEREa.name!='' ......