服了!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的锁超时时间呢?是不是锁超时时间设置的太短了呢?

相关推荐
瓜牛_gn27 分钟前
mysql特性
数据库·mysql
奶糖趣多多1 小时前
Redis知识点
数据库·redis·缓存
CoderIsArt3 小时前
Redis的三种模式:主从模式,哨兵与集群模式
数据库·redis·缓存
师太,答应老衲吧5 小时前
SQL实战训练之,力扣:2020. 无流量的帐户数(递归)
数据库·sql·leetcode
Channing Lewis6 小时前
salesforce case可以新建一个roll up 字段,统计出这个case下的email数量吗
数据库·salesforce
毕业设计制作和分享7 小时前
ssm《数据库系统原理》课程平台的设计与实现+vue
前端·数据库·vue.js·oracle·mybatis
ketil277 小时前
Redis - String 字符串
数据库·redis·缓存
Hsu_kk8 小时前
MySQL 批量删除海量数据的几种方法
数据库·mysql
编程学无止境8 小时前
第02章 MySQL环境搭建
数据库·mysql
knight-n8 小时前
MYSQL库的操作
数据库·mysql