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

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

作者:转转技术团队

链接:https://juejin.cn/post/7387227689319563290

来源:稀土掘金

MySQL 锁回顾

共享锁

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

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

sql 复制代码
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启动后,可以正常看到具体的数值:

sql 复制代码
BEGIN;
SELECT salary FROM employees WHERE emp_no = 101 LOCK IN SHARE MODE;
--COMMIT;

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

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

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

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

sql 复制代码
BEGIN;
SELECT salary FROM employees WHERE emp_no = 101 FOR UPDATE;
COMMIT;

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

表锁

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

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

sql 复制代码
BEGIN;
SELECT salary FROM employees WHERE emp_no = 101 FOR UPDATE;
COMMIT;

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

sql 复制代码
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为自增主键,即为索引:

sql 复制代码
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。

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

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

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

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

原因

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

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

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

相关推荐
leidata30 分钟前
MySQL系列—10.Innodb行格式
数据库·mysql
阿维的博客日记1 小时前
聚簇索引和二级索引
数据库·聚簇索引·二级索引
璇嘟嘟1 小时前
springboot-创建连接池
数据库
计算机程序设计开发1 小时前
小说阅读网站登录注册搜索小说查看评论前后台管理计算机毕业设计/springboot/javaWEB/J2EE/MYSQL数据库/vue前后分离小程序
数据库·vue.js·spring boot·java-ee·课程设计·计算机毕业设计·数据库管理系统
赵利伟@1 小时前
springboot对数据库进行备份+对一个文件夹内的文件按时间排序,只保留最近的8个文件
数据库
水兵没月2 小时前
MongoDB根据字段内容长度查询语句
数据库·mongodb
shuiyihang09812 小时前
数据库课程 CMU15-445 2023 Fall Project-1 Buffer Pool Manager
网络·数据库·cmu 15-445
阿维的博客日记2 小时前
简要介绍联合索引
数据库·联合索引
夜夜亮晶晶2 小时前
MySQL——数据类型(一)
数据库·mysql
一心只为学3 小时前
SQL server 日常运维命令
运维·数据库·sqlserver