您的位置:首页 > 游戏 > 游戏 > 服了!DELETE 同一行记录也会造成死锁---图文解析

服了!DELETE 同一行记录也会造成死锁---图文解析

2024/9/24 21:29:38 来源:https://blog.csdn.net/zhaozhiqiang1981/article/details/141729236  浏览:    关键词:服了!DELETE 同一行记录也会造成死锁---图文解析

服了!DELETE 同一行记录也会造成死锁!!

作者:转转技术团队
链接:https://juejin.cn/post/7387227689319563290
来源:稀土掘金

MySQL 锁回顾

img

共享锁

使用共享锁(Shared Lock)时,它允许持有该锁的事务读取一行数据,但不允许对其进行更新或删除。这种锁的主要目的是防止其他事务修改被锁定的数据。

举个例子:共享锁(Shared Lock) 就像是大家一起看电视,但只能看,不能改变频道。这意味着多个事务可以同时读取同一份数据,但不能修改它。共享锁适用于只读操作,比如查看账户余额或者商品库存。

CREATE TABLE employees (emp_no INT NOT NULL,birth_date DATE NOT NULL,first_name VARCHAR(14) NOT NULL,last_name VARCHAR(16) NOT NULL,gender ENUM('M','F') NOT NULL,hire_date DATE NOT NULL,middle_name VARCHAR(14),PRIMARY KEY (emp_no)
);INSERT INTO `employees` VALUES (101, '1990-01-15', 'John', 'Doe', 'M', '2020-05-10', 'Michael', 2000.00);
INSERT INTO `employees` VALUES (102, '1988-03-20', 'Jane', 'Smith', 'F', '2019-12-01', 'Elizabeth', 1500.00);
INSERT INTO `employees` VALUES (103, '1995-07-05', 'Robert', 'Johnson', 'M', '2021-02-18', 'William', 1800.00);-- 在事务1中获取共享锁
START TRANSACTION;
SELECT salary FROM employees WHERE emp_no = 101 LOCK IN SHARE MODE;-- 此时其他事务可以读取 salary,但不能修改或删除-- 在事务2中尝试修改 salary(会被阻塞)
UPDATE employees SET salary = 6000 WHERE emp_no = 101;
-- 这里会等待,直到事务1释放共享锁-- 在事务1中继续操作或提交
COMMIT;

共享锁,启动两个事务,事务1启动后,可以正常看到具体的数值:

BEGIN;
SELECT salary FROM employees WHERE emp_no = 101 LOCK IN SHARE MODE;
--COMMIT;

在这里插入图片描述

新启动一个事务查询同一行,发现可以正常查询:

在这里插入图片描述

排他锁(Exclusive Lock) 就像是你独占一台电视,可以随便换频道,甚至关掉。其他人想看电视,需要你交出电视控制权才可以,否则看都不能看。排他锁用于数据修改操作,比如更新账户余额、添加新订单或者删除记录。只有一个事务能持有排他锁,其他事务必须等待它释放锁后才能操作。

事务1,可以看到查询结果,但没有提交,所以事务无法结束。

在这里插入图片描述

事务2,无法看到查询结果,因为事务1独占排他锁(事务2模拟的时候不要执行COMMIT):

BEGIN;
SELECT salary FROM employees WHERE emp_no = 101 FOR UPDATE;
COMMIT;

在这里插入图片描述

提交事务1后,事务2的查询结果才能看到:

在这里插入图片描述

在这里插入图片描述

表锁

意向锁:意向锁分为意向共享锁和意向排它锁,只要搞清楚一点,这两个锁瞬间就懂了,这一点就是:标准锁和排他锁不可以同时添加!简单说就是,当多个事务都进行了共享锁请求,系统会查看每一个事务请求共享锁的数据是不是已经有了排他锁,如果有,就不再添加共享锁了。如果没有,注意是事务中请求的表中的所有数据都没有添加排他锁,就会被标记,这个标记就叫做意向共享锁,实际执行的时候这些被标记的意向共享锁就会转化为共享锁。

演示:事务A为ID为101的数据添加了排他锁(注意执行的内容是选中内容,没有执行COMMIT!)

BEGIN;
SELECT salary FROM employees WHERE emp_no = 101 FOR UPDATE;
COMMIT;

在这里插入图片描述

事务B为id为101和102的数据请求共享锁,因为事务A中已经为101请求了排它锁,所以事务B中的共享锁请求失败,被阻塞,需要等待事务A提交之后才会执行。

BEGIN;
SELECT salary FROM employees WHERE emp_no = 101 or emp_no = 102 LOCK IN SHARE MODE;
COMMIT;

在这里插入图片描述

事务A提交之后,事务B的数据才会被查询出来,按理说如果添加了共享锁,是可以直接查询出结果的:

在这里插入图片描述

自增锁:就是mysql中的自增,表中如果id字段是自增字段,为了保证其自增id的连续性,其中一个事务插入数据的时候,另一个事务插入数据需要等待前一个事务提交完成才可以继续。这个没什么好演示的。

行锁

记录锁:和排它锁类似,锁会加载包含索引的数据行中,简单说就是通过索引来记录的排它锁,通过索引的辅助,可以避免添加锁需要扫描全表。

演示,注意记录锁需要索引,例子中的mytable表中的id为自增主键,即为索引:

CREATE TABLE mytable (id INT AUTO_INCREMENT PRIMARY KEY,value VARCHAR(20)
);INSERT INTO mytable (value) VALUES ('A');
INSERT INTO mytable (value) VALUES ('B');
INSERT INTO mytable (value) VALUES ('C');

事务A执行了查询id=1的数据,value=A,此时没有commit;事务没有提交。

在这里插入图片描述

事务B对id=1的value进行更新操作,把原来的值’A’更新为’X’,执行并commit提交

在这里插入图片描述

此时由于事务A拿着记录锁,事务B更新操作被阻塞,所以数据库中的数据依然是’A’

在这里插入图片描述

提交事务A

在这里插入图片描述

因为事务A已经提交,记录锁被释放,数据库中的数据被更新了。

在这里插入图片描述

间隙锁

主要目的是同一个事务中同样的查询不能有不同的结果:

演示:事务A查询id小于5的数据,其实数据库中只有三条数据

在这里插入图片描述

事务B插入第四条数据,但事务A已经占有了间隙锁,事务B执行被阻塞。如果此时不阻塞,则事务A如果多次相同查询会造成幻读。

在这里插入图片描述

所以我们会看到,数据库依然只有三条数据:

在这里插入图片描述

事务A提交,释放间隙锁

在这里插入图片描述

数据库数据更新为4条

在这里插入图片描述

临键锁

记录锁是为了保持数据一致性,会锁定自身,保证查到的数据在同一个事务里保持一致。间隙锁是为了避免幻读,保证一个事务中多次查询同样的数据结果是一样的,临键锁就是记录锁+间隙锁。

DELETE 流程

在深入分析问题原因之前先对 DELETE 操作的基本流程进行复习。众所周知,MySQL 以页作为数据的基本存储单位,每个页内包含两个主要的链表:正常记录链表和垃圾链表。每条记录都有一个记录头,记录头中包括一个关键属性——deleted_flag。

img

执行 DELETE 操作期间,系统首先将正常记录的记录头中的 delete_flag 标记设置为 1。这一步骤也被称为 delete mark,是数据删除流程的一部分。

img

在事务成功提交之后,由 purge 线程 负责对已标记为删除的数据执行逻辑删除操作。这一过程包括将记录从正常记录链表中移除,并将它们添加到垃圾链表中,以便后续的清理工作。

img

针对不同状态下的记录,MySQL 在加锁时采取不同的策略,特别是在处理唯一索引上记录的加锁情况。以下是具体的加锁规则:

  • 正常记录: 对于未被标记为删除的记录,MySQL 会施加记录锁,以确保事务的隔离性和数据的一致性。
  • delete mark: 当记录已被标记为删除(即 delete_flag 被设置为1),但尚未由 purge 线程清理时,MySQL 会对这些记录施加临键锁,以避免在清理前发生数据冲突。
  • 已删除记录: 对于已经被 purge 线程逻辑删除的记录,MySQL 会施加间隙锁,这允许在已删除记录的索引位置插入新记录,同时保持索引的完整性和顺序性。

原因

在这里插入图片描述

会对这些记录施加临键锁,以避免在清理前发生数据冲突。

  • 已删除记录: 对于已经被 purge 线程逻辑删除的记录,MySQL 会施加间隙锁,这允许在已删除记录的索引位置插入新记录,同时保持索引的完整性和顺序性。

## 原因

最终原作者采用了分布式锁的方案解决了问题,我个人认为是否查看了mysql的锁超时时间呢?是不是锁超时时间设置的太短了呢?

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com