MySQL:删除同一行时发生死锁

2024-01-21

最近删除记录时遇到死锁(注意隔离级别是可重复读取, MySQL 5.7)

这是重现步骤

1 创建一个新表

CREATE TABLE `t` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `p_name` (`name`)
) ENGINE=InnoDB CHARSET=utf8;

2 准备3条记录

insert into t (name) value ('A'), ('C'), ('D');

3

+====================================+============================================================+
|             Session A              |                         Session B                          |
+====================================+============================================================+
| begin;                             |                                                            |
+------------------------------------+------------------------------------------------------------+
|                                    | begin;                                                     |
+------------------------------------+------------------------------------------------------------+
| delete from t where name = 'C';    |                                                            |
+------------------------------------+------------------------------------------------------------+
|                                    | delete from t where name = 'C';  --Blocked!                |
+------------------------------------+------------------------------------------------------------+
| insert into t (name) values ('B'); |                                                            |
+------------------------------------+------------------------------------------------------------+
|                                    | ERROR 1213 (40001): Deadlock found when trying to get lock |
+------------------------------------+------------------------------------------------------------+

的结果显示引擎 innodb 状态如下所示(最新检测到的死锁部分)

LATEST DETECTED DEADLOCK
------------------------
*** (1) TRANSACTION:
TRANSACTION 3631, ACTIVE 21 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 13, OS thread handle 123145439432704, query id 306 localhost root updating
delete from t where name = 'C'
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 69 page no 4 n bits 72 index p_name of table `jacky`.`t` trx id 3631 lock_mode X waiting
Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
 0: len 1; hex 43; asc C;;
 1: len 8; hex 8000000000000018; asc         ;;

*** (2) TRANSACTION:
TRANSACTION 3630, ACTIVE 29 sec inserting
mysql tables in use 1, locked 1
5 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 2
MySQL thread id 14, OS thread handle 123145439711232, query id 307 localhost root update
insert into t (name) values ('B')
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 69 page no 4 n bits 72 index p_name of table `jacky`.`t` trx id 3630 lock_mode X
Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
 0: len 1; hex 43; asc C;;
 1: len 8; hex 8000000000000018; asc         ;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 69 page no 4 n bits 72 index p_name of table `jacky`.`t` trx id 3630 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
 0: len 1; hex 43; asc C;;
 1: len 8; hex 8000000000000018; asc         ;;

如Innodb状态所示,会话B正在等待next-key lockC,并且会话A持有记录锁C并等待间隙锁定C;


众所周知

DELETE FROM ... WHERE ... 对搜索遇到的每个记录设置独占的下一键锁定

下一个键锁是索引记录上的记录锁和索引记录之前的间隙上的间隙锁的组合。

Q1:我猜测会话B是否首先获得间隙锁(next-key的一部分),然后等待记录锁。因此,会话A中的后者插入被会话B阻塞(由于间隙锁),最终导致死锁。正确的?

Q2: 作为C从索引中清除,会话 B 持有的间隙锁是否应该为 ('A', 'D')?如果是这样,为什么会话 A 正在等待范围(,'C')上的插入意图锁?

Q3: 为什么会话B有1 row lock(s),并且会话 A 有4 row lock(s)?


Q4:改变索引时p_name对于唯一索引,我们仍然会由于间隙锁而陷入死锁,这很奇怪。和官方的表现不一样doc https://dev.mysql.com/doc/refman/8.0/en/innodb-locks-set.html其中指出仅需要记录锁定。

DELETE FROM ... WHERE ... 对搜索遇到的每个记录设置独占的下一键锁定。然而,对于使用唯一索引锁定行来搜索唯一行的语句,只需要索引记录锁.


不过使用主键的时候就可以了id执行删除(步骤如下所示)。这是 MySQL 中的错误吗?

1 准备数据

delete from t;
insert into t (id, name) value (1, 'A'), (3, 'C'), (5, 'D');

2

+-------------------------------------------+--------------------------------------+
|                 Session A                 |              Session B               |
+-------------------------------------------+--------------------------------------+
| begin;                                    |                                      |
|                                           | begin;                               |
| delete from t where id = 3;               |                                      |
|                                           | delete from t where id = 3; Blocked! |
| insert into t (id, name) values (2, 'B'); |                                      |
|                                           |                                      |
| commit;                                   |                                      |
+-------------------------------------------+--------------------------------------+

从事务 3631 的“WAITING FOR THIS LOCK TO BE GRANTED”部分,我们可以看到:

RECORD LOCKS space id 69 page no 4 n bits 72 index p_name of table `jacky`.`t` trx id 3631 lock_mode X waiting
Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
  1. 3631 正在等待记录锁。对应的索引内容为{"name":"C","id":24}。
  2. 索引名称是表 t 中的 p_name。
  3. 锁的模式是“lock_mode X”

从事务 3630 的“WAITING FOR THIS LOCK TO BE GRANTED”部分,我们可以看到:

*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 69 page no 4 n bits 72 index p_name of table `jacky`.`t` trx id 3630 lock_mode X
Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
 0: len 1; hex 43; asc C;;
 1: len 8; hex 8000000000000018; asc         ;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 69 page no 4 n bits 72 index p_name of table `jacky`.`t` trx id 3630 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
 0: len 1; hex 43; asc C;;
 1: len 8; hex 8000000000000018; asc         ;;
  1. 3630 正在等待记录锁。对应的索引内容为{"name":"C","id":24}。等待锁的模式是“lock_mode X 锁间隙”
  2. 3630 正在持有记录锁。对应的索引内容为{"name":"C","id":24}。持有锁的模式是“lock_mode X locks”
  3. 索引名称是表 t 中的 p_name。
  4. 这个死锁是由于执行“insert into t(name)values('B')”引起的

根据您的重现步骤,会话 A 将发送delete from t where name = 'C';首先,这将锁定:

  1. ('A', 'C'] 和 ('C', 'D'):下一个键锁定 'C' 和 'D' 之前的间隙锁定;

从...删除... https://dev.mysql.com/doc/refman/8.0/en/innodb-locks-set.html对每个设置独占的下一键锁定 记录搜索遭遇。但是,只有索引记录锁是 对于使用唯一索引进行搜索锁定行的语句是必需的 对于唯一的行。

  1. 为'C'对应的主索引id添加记录锁。这里的 id 值应该是“26”。

然后会话 B 将开始并且delete from t where name = 'C';将再次被执行。然而。对于会话 B,由于会话 A 尚未提交,“C”已被会话 A 锁定。但是,如果执行删除 sql,会话 B 将尝试按以下顺序添加锁定:

  1. 'C'之前的间隙锁:成功,因为innodb可以在同一位置添加多个间隙锁。
  2. 记录锁'C':Blocked,因为会话 A 已持有该锁。会话 B 必须等待会话 A 释放它。
  3. 'D' 之前的间隙锁定:

最后,会话A发送insert into t (name) values ('B');。餐桌用t,有2个索引,分别是id and name. id是一个自增主整型键,对于name,这条sql会尝试添加一个插入意向锁。然而,会话 B 持有一个间隙锁,因此会话 A 必须等待会话 B 释放该间隙锁。现在我们可以看看这个死锁是如何发生的。 Innodb会根据成本选择一个会话进行回滚。这里会话B将被回滚。

对于Q1,答案是肯定的。 实际上,对于第二季度,在会话提交之前,删除的记录不会从索引中清除。 对于 Q3,行锁数量等于trx_rows_locked,在 mysql 网站中,其:

TRX_ROWS_LOCKED https://dev.mysql.com/doc/refman/5.7/en/information-schema-innodb-trx-table.html

The 大概的数量或行数被本次交易锁定。价值 可能包括物理上存在但不存在的删除标记行 对交易可见。

由此article https://developpaper.com/understanding-mysql-lock-details-based-on-updating-sql-statements/,我们可以知道:

  1. 对于非聚集唯一索引过滤,由于需要回表,所以锁定过滤行数为唯一索引加上 返回的行数。

  2. 对于非聚集非唯一索引过滤,会涉及到间隙锁,因此会锁定更多的记录。

因此,在会话 A 中删除后,trx_rows_locked(间隙锁 + 下一个键锁 + 返回表)为 3。尝试插入后,最终 trx_rows_locked 值应为 3 + 1(插入键锁)。


以下是新的更新问题: 我之前没有注意到删除主键和唯一辅助键。

经过一番调查,我发现:

  1. 当删除一个primary key,已被删除且尚未提交,新的删除操作符只需要record lock而不是下一键锁。
  2. 当删除一个secondary unique key,已被删除且尚未提交,新的删除操作符将需要next-key lock.

你可以使用set GLOBAL innodb_status_output_locks=ON; show engine innodb status查看正在运行的事务的详细锁定状态。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

MySQL:删除同一行时发生死锁 的相关文章

  • 在docker中使用MySQL数据库设置aspnetcore

    我正在尝试设置一个 docker compose 文件 其中包含 asp net core mysql 数据库和 phpmyadmin 的容器 设置我的 mysql 服务器没有问题 我可以使用 phpmyadmin 访问它 我的 asp n
  • PHP 选择后立即删除

    我有一个 PHP 服务器脚本 它从 MySQL 数据库中选择一些数据 一旦我将 mysql query 和 mysql fetch assoc 的结果存储在我自己的局部变量中 我就想删除我刚刚选择的行 这种方法的问题在于 PHP 似乎对我的
  • 在 MacOSX10.6 上运行 python 服务器时 MySQLdb 错误

    运行我的服务器 python manage py runserver 产生以下错误 django core exceptions ImproperlyConfigured 加载 MySQLdb 模块时出错 没有名为 MySQLdb 的模块
  • 如何在 phpmyadmin 中创建 MySQL 触发器

    我想在 MySQL 中创建一个触发器 我运行以下命令 mysql gt delimiter mysql gt CREATE TRIGGER before insert money BEFORE INSERT ON money gt FOR
  • 显示表 FULLTEXT 索引列

    我希望运行一个查询 该查询将返回表中全文索引的列列表 该表采用 MyISAM 格式 我将使用 php 来构建查询 理想情况下 我会运行查询 它会返回信息 以便我可以构造一个以逗号分隔的列字符串 例如 名 姓 电子邮箱 这在 MySQL 中可
  • 如何在 PostgreSQL 中将数据库从一台服务器移动到另一台服务器?

    我正在尝试将数据库从旧服务器移动到新服务器 任何帮助 将不胜感激 Just pipe http www postgresql org docs current interactive migration html从旧服务器转储到新服务器 p
  • 查询中列的顺序重要吗?

    当从 MySQL 表中选择列时 与表中的顺序相比 选择列的顺序是否会影响性能 不考虑可能覆盖列的索引 例如 您有一个包含行 uid name bday 的表 并且有以下查询 SELECT uid name bday FROM table M
  • 如何使用 MySQL 的 LOAD DATA LOCAL INFILE 在导入 CSV 时将字符串日期更改为 MySQL 日期格式

    我正在使用 MySQL 的 LOAD DATA LOCAL INFILE SQL 语句将数据从 CSV 文件加载到现有数据库表中 下面是一个 SQL 语句示例 LOAD DATA LOCAL INFILE file csv INTO TAB
  • Hibernate 对集合的查询过滤器

    我想执行以下查询 from Item i where i categoryItems catalogId catId 然而 这会产生以下异常 非法尝试取消引用集合 所以我用谷歌搜索 找到了这个 Hibernate 论坛帖子https for
  • 当sql连接中存在两个同名列时,如何从一个表列中获取值

    当我连接两个具有相同名称列的表时 我目前面临着尝试获取值的问题 例如 table1 date和table2 date 每个表中的日期不同 我将如何获取 日期 本例中的表1 我目前正在跑步 while row mysqliquery gt f
  • 截断 Mysql 表 Cron 作业?

    我在如何使用 cron 作业截断 Mysql 表时遇到了一些麻烦 无论我尝试什么 我似乎都无法让数据库清除表格 感谢您的帮助 mysql uderp example pexample hlocalhost Dexample e TRUNCA
  • 为什么这会返回资源 id #2? [复制]

    这个问题在这里已经有答案了 可能的重复 我如何从 PHP 中的 MySql 响应中 回显 资源 id 6 https stackoverflow com questions 4290108 how do i echo a resource
  • SQL服务器事务

    我需要了解sql server事务 我浏览了谷歌上的一些文章 但我什么也没理解 谁能帮我 您可以通过写入显式启动事务BEGIN TRANSACTION 您可以通过运行来结束事务COMMIT TRANSACTION 之前COMMIT运行时 受
  • Mysql 检索所有有限制的行

    我想检索特定用户的所有行 限制为 0 x 所以我只是想问是否有任何方法可以检索 mysql 中的所有行 而不调用返回 x 的 count id 的方法 而不重载现有函数 该函数在查询中根本没有限制 与我们的 string Relace 功能
  • 如何在不运行 PostgreSQL 服务器的情况下初始化 PostgreSQL 数据库

    在初始化脚本中 我想初始化 PostgreSQL 目录 但在此阶段不需要 也不希望 正在运行的 PostgreSQL 服务器 如果我只是创建集群 作为用户postgres initdb D 但是 我还需要创建 PostgreSQL 角色 创
  • 为什么我的 if 语句没有按我预期的方式工作?

    我正在尝试实现以下目标 我向我的 SQL 数据库询问使用SELECT FROM subjects 这样做之后我要求使用数组mysqli fetch assoc 在那之前一切都很好 现在的问题是 当我尝试在每个循环中修改 genero 的值
  • 如何在Sequelize中设置查询超时?

    我想看看如何在 Sequelize 中设置查询的超时时间 我查看了 Sequelize 文档以获取一些信息 但我找不到我要找的东西 我发现的最接近的是 pools acquire 选项 但我不想设置传入连接的超时 而是设置正在进行的查询的超
  • MySQL PHP邮政编码比较具体距离

    我试图找出比较一个邮政编码 用户提供的 和一大堆其他邮政编码 现在大约有 200 个邮政编码 之间的距离的最有效方法 相对于加载时间 但它会随着时间的推移而增加 我不需要任何精确的东西 只是在球场上 我下载了整个美国的邮政编码 csv 文件
  • Laravel leftJoin 仅右表的最后一条记录

    我是 Laravel 的新手 我有两张桌子 1 产品 2 价格 products id product int p key name varchar prices id price int p key id product int
  • MySQL 查询计算上个月

    我想计算上个月的订单总额 我收到了从当前日期获取当月数据的查询 SELECT SUM goods total AS Total Amount FROM orders WHERE order placed date gt date sub c

随机推荐

  • 在执行 ShellExecute 之前等待?

    我有一个希望很快的问题 是否可以稍微延迟 ShellExecute 的执行 我有一个带有自动更新程序的应用程序 下载所有必需的文件等后 它将当前文件重命名为 OLD 并将新文件重命名为以前的文件 够简单的 但随后我需要删除那些 OLD 文件
  • Mockito中的Java枚举列表thenReturn

    有没有办法在mockito的thenReturn函数中枚举列表中的项目 以便我返回列表中的每个项目 到目前为止我已经这样做了 List
  • 如何更改 Flutter DevTools 默认浏览器?

    我可以更改 Flutter DevTools 使用的默认浏览器吗 它与 Firefox 或其他浏览器兼容吗 就我而言 我不太喜欢 Chrome 与 Firefox 和其他浏览器相比 我有点慢且笨重 到目前为止我还没有找到任何解决方案 甚至通
  • Aurelia 取消订阅事件聚合器

    我在用Aurelia Framework with Typescript并在event aggregator我能够发布和订阅频道 问题是我无法取消订阅频道 注意 所有形式的 subscribe 方法都会返回一个 dispose 函数 您可以
  • 在windbg中调试.Net字符串值

    我有一个 Net 应用程序转储 它捕获了一个异常 我正在使用 Windbg 进行分析 并对其中一种方法的 String 参数的值感兴趣 我已经隔离了 String 对象 我的windbg工作是 0 000 gt loadby sos msc
  • Outlook 2007 加载项部署为 DLL

    我开发了第一个 Outlook 插件 我可以看到调试加载项会自动打开 Outlook 我注意到 Outlook 所涉及的问题20 sec当我的加载项附加时打开 作为一键式新菜单 我认为这可能是由于我正在调试我的项目造成的 我将加载项发布到本
  • 使用不同版本运行 Compass/SASS

    我正在尝试找到一个适用于两个不同罗盘项目的不错的解决方案 一种是基于使用 Blueprint 的 Compass 旧版本 另一种是基于 susy grid 较新版本 目前 我必须重新安装正确的版本watch过程 是否可以运行compile有
  • 消除 matlab 图 pdf 输出周围的空白

    我想在 LaTeX 文档中使用我的 matlab 绘图的 PDF 版本 我使用带有 PDF 选项的 saveas 命令保存数字 但 pdf 文件中的绘图周围有巨大的空白 这是正常的吗 我怎样才能摆脱它 当然 这是自动的 因为我有 很多 情节
  • 将值放入 array_walk_recursive() 之外的数组中[重复]

    这个问题在这里已经有答案了 我想递归地查找名为 image 的键的嵌套 JSON 对象 并将它们的值 URL 字符串 推送到函数外部的另一个数组中 从其他示例和 SO 问题中 我知道我需要传递对范围外数组变量的引用 但我对 PHP 不太熟悉
  • 如何使用 NSDateFormatter 将字符串日期转换为 NSDate

    我有以下日期作为 NSString Thu May 29 14 22 40 UTC 2014 我尝试使用以下代码将其转换为 NSDate NSDateFormatter fmt NSDateFormatter alloc init fmt
  • 仅获取我的 Android 应用程序附带的区域设置列表

    我知道有多种方法可以获取设备支持的所有区域设置的列表 有人能够获取您已包含在应用程序中的区域设置列表吗 使用以下代码我知道我可以获得设备支持的列表 String languages getAssets getLocales or Strin
  • 利用 Google 帐户凭据在 Android 中发送电子邮件

    在 Android 中是否可以使用手机关联的 GMail 凭据以编程方式发送电子邮件 当我明确提供用户名和密码时 我可以发送电子邮件 但我希望利用已经与手机关联的 Google 帐户 请注意 我不需要实际访问用户名或密码 我只是想间接利用这
  • Spark Cassandra 连接器使用 IN 子句进行过滤

    我在 java 的 Spark cassandra 连接器过滤方面面临一些问题 Cassandra 允许使用 IN 子句按分区键的最后一列进行过滤 例如 create table cf text a varchar b varchar c
  • Elasticsearch - 文档中片段的位置

    我正在执行如下所示的短语查询 它返回给我按相关性排序的突出显示的片段 当然 我希望用户单击某个片段 然后我会将文档滚动到相应的位置 但是 我在 Elasticsearch 中看不到任何方法来找出片段在原始文档中的位置 有任何想法吗 GET
  • 如何在Rails中缓存任意对象(基于时间)?

    我读了官方指南 它说有page cache action cache and fragment cache 但它们不是我想要的 我只是想缓存一个对象 而不是整个页面或视图片段 就像这样的伪代码 def show cache ads expi
  • Boto3:等待 S3 流式上传完成

    我在用着S3 Client upload fileobj with a BytesIO流作为输入 将文件从流上传到 S3 我的函数不应该在上传完成之前返回 所以我需要一种方法来等待它 从文档中看 没有明显的方法来等待传输完成 但有一些提示c
  • 我的 Android 设备没有出现在 adb 设备列表中

    我有一台 HP Slate 21 它使用 USB 电缆连接到我的 Windows 盒子 设备上已启用 USB 调试 After 添加适当的 SingleAdbInterface and CompositeAdbInterface https
  • IIS7 显示 iisstart.html 而不是默认文档

    我有一个在 IIS7 上运行的 Sitefinity 4 1 网站 我在默认文档列表的顶部设置 Default aspx 但是当我访问http www http www org 它显示 IIS7 欢迎页面 当我访问时http www htt
  • 捕获引擎执行异常

    我有一个包含托管代码和本机代码的应用程序 应用程序当前有一个未处理的异常过滤器 通过 SetUnhandledExceptionFilter 设置 它捕获任何关键错误 生成小型转储 记录各种应用程序参数 然后退出程序 未处理的异常处理程序不
  • MySQL:删除同一行时发生死锁

    最近删除记录时遇到死锁 注意隔离级别是可重复读取 MySQL 5 7 这是重现步骤 1 创建一个新表 CREATE TABLE t id bigint 20 NOT NULL AUTO INCREMENT name varchar 32 N