数据库死锁分析(行锁、间隙锁)

分享因遇到缝隙锁而导致的死锁案例。文章最后有一个知识总结,以供参考。

[En]

Share a deadlock case caused by a gap lock encountered. There is a summary of knowledge at the end of the article for reference.

日志出现:Deadlock found when trying to get lock; try restarting transaction

导致原因:并发导致的数据库间隙锁死锁(MySql数据库默认RR级别)

业务主要操作提炼:首先进来将t1表原来的记录状态更新掉,然后插入新的记录。

线程1

线程2

……

update t1 set status =0 where rule=1

insert t1 () values (),(),();

update t1 set status =0 where rule=2

insert t1 () values (),(),();

……

复现问题:

测试数据准备

— 准备测试表

CREATE TABLE IF NOT EXISTS test (

id int(11) NOT NULL AUTO_INCREMENT COMMENT ‘主键’,

col int(11) NULL COMMENT ‘普通字段’,

idx_col int(11) NULL COMMENT ‘索引字段’,

uni_col tinyint(4) NULL COMMENT ‘唯一键字段’,

PRIMARY KEY (id),

KEY idx_1 (idx_col) USING BTREE,

unique KEY uni_idx_1 (uni_col) USING BTREE

) ENGINE=InnoDB AUTO_INCREMENT=0 ;

— 插入测试数据

INSERT INTO test ( col,idx_col,uni_col) VALUES

(3,3,3),

(10,10,10),

(11,11,11),

(20,20,20),

(25,25,25),

(26,26,26),

(50,50,50)

;

— 清空数据表

DELETE FROM test ;

DROP table test;

— 查询测试表数据

SELECT * FROM test;

验证间隙锁的存在:

事务1

事务2

— 开始事务1

BEGIN;

— 开始事务2

BEGIN;

— 更新test,使用索引字段

UPDATE test set col=99 WHERE idx_col=20;

— 插入数据,因为有间隙锁,[11-25)这个区间全部被锁上了,插入被阻塞

INSERT INTO test ( idx_col) VALUES (11);

INSERT INTO test ( idx_col) VALUES (12);

INSERT INTO test ( idx_col) VALUES (20);

INSERT INTO test ( idx_col) VALUES (24);

缝隙外的那些可以顺利地插入。

[En]

Those outside the gap can be inserted smoothly.

INSERT INTO test ( idx_col) VALUES (10);
INSERT INTO test ( idx_col) VALUES (25);
INSERT INTO test ( idx_col) VALUES (26);

由于间隙锁导致的死锁案例:(本次报错复现)

事务1

事务2

— 开始事务1

BEGIN;

— 开始事务2

BEGIN;

— 更新test,使用索引字段,锁间隙[11,25)

UPDATE test set col=99 WHERE idx_col=20;

— 更新test,使用索引字段,锁间隙[20,26)

UPDATE test set col=99 WHERE idx_col=25;

— 使用了事务2的间隙锁,所以阻塞
INSERT INTO test ( idx_col) VALUES (21);

— 使用了事务1的间隙锁,阻塞,互相需要对方的锁,导致死锁
— Deadlock found when trying to get lock; try restarting transaction

INSERT INTO test ( idx_col) VALUES (12);

— 该事务被回滚,事务1提交成功。

where条件如果换成唯一键或者主键,没有间隙锁

事务1

事务2

— 开始事务1

BEGIN;

— 开始事务2

BEGIN;

— 更新test,使用唯一键或主键无间隙锁

UPDATE test set col=99 WHERE uni_col=20;

UPDATE test set col=99 WHERE uni_col=20 AND col=20;

— 更新test,使用唯一键或主键无间隙锁

UPDATE test set col=99 WHERE uni_col=25;


UPDATE test set col=99 WHERE uni_col=25 AND col=25;

— 无间隙锁,顺利执行
INSERT INTO test ( uni_col) VALUES (21);


INSERT INTO test ( uni_col,col) VALUES (21,21);

— 无间隙锁,顺利执行

INSERT INTO test ( uni_col) VALUES (12);


INSERT INTO test ( uni_col,col) VALUES (12,12);

如果没有索引,将导致完全表锁。

[En]

If there is no index, it will result in a full table lock.

事务1

事务2

— 开始事务1

BEGIN;

— 开始事务2

BEGIN;

— 更新test

UPDATE test set col=99 WHERE col=20;

— 以下语句全部阻塞
UPDATE test set col=99 WHERE col=25;


INSERT INTO test ( uni_col,col) VALUES (100,100);

结论:

对于update ,insert组合的这种业务操作,建议update操作使用主键或者唯一键作为where条件可以有效避免并发时候间隙锁的危害。

如果字段不是主键或唯一键,建议先查询,然后用主键或唯一键更新。

[En]

If the field is not a primary key or unique key, it is recommended to query it first and update it with a primary key or unique key.

或者修改数据库隔离级别为RC级别。

线程1

线程2

……

xx = select id from t1 where rule=1
update t1 set status =0 where id in(xx)

insert t1 () values (),(),();

xx = select id from t1 where rule=1
update t1 set status =0 where id in(xx)

insert t1 () values (),(),();

……

注意:以下写法无效

update t1 set status=0

WHERE id IN (

select id FROM

(SELECT id FROM t1 where rule=2) t

);

本文来自博客园,作者:wanglifeng,转载请注明原文链接:https://www.cnblogs.com/wanglifeng717/p/15993400.html

知识示例与参考如下:

数据库死锁分析(行锁、间隙锁)

在InnoDB中,主键可以被理解为聚簇索引,聚簇索引中的叶子结点就是相应的数据行,具有聚簇索引的表也被称为聚簇索引表,数据在存储的时候,是按照主键进行排序存储的。

我们都知道,数据库在select的时候,会选择索引列进行查找,索引列都是按照B+树(多叉搜索树)数据结构进行存储,找到主键之后,再回到聚簇索引表中进行查询,这叫回表查询。

id列是主键,RC或RR隔离级别

只有id=10记录上有行锁

数据库死锁分析(行锁、间隙锁)

id列是二级唯一索引,RC或RR隔离级别

数据库死锁分析(行锁、间隙锁)

id列是二级非唯一索引RC级别

数据库死锁分析(行锁、间隙锁)

id列是二级非唯一索引RR级别

在RR隔离级别下,为了防止幻读的发生,会使用Gap锁。

这里,你可以把Gap锁理解为,不允许在数据记录前面插入数据。首先,通过id索引定位到第一条满足查询条件的记录,加记录上的X锁,加GAP上的GAP锁,然后加主键聚簇索引上的记录X锁,然后返回;然后读取下一条,重复进行。

直至进行到第一条不满足条件的记录[11,f],此时,不需要加记录X锁,但是仍旧需要加GAP锁,

数据库死锁分析(行锁、间隙锁)

id上没有索引,RC级别

若id列上没有索引,SQL会走聚簇索引的全扫描进行过滤,由于过滤是由MySQL Server层面进行的。因此每条记录,无论是否满足条件,都会被加上X锁。

但是,为了效率考量,MySQL做了优化,对于不满足条件的记录,会在判断后放锁,最终持有的,是满足条件的记录上的锁,但是不满足条件的记录上的加锁/放锁动作不会省略。

同时,优化也违背了2PL的约束(同时加锁同时放锁)

数据库死锁分析(行锁、间隙锁)

id上没有索引,RR隔离级别

聚簇索引上的所有记录,都被加上了X锁。其次,聚簇索引每条记录间的间隙(GAP),也同时被加上了GAP锁。

MySQL是做了相关的优化的,就是所谓的semi-consistent read。semi-consistent read开启的情况下,对于不满足查询条件的记录,MySQL会提前放锁,同时也不会添加Gap锁。

数据库死锁分析(行锁、间隙锁)

实例:
delete from t1 where pubtime>1 and pubtime

Original: https://www.cnblogs.com/wanglifeng717/p/15993400.html
Author: 王李峰
Title: 数据库死锁分析(行锁、间隙锁)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/505582/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • JVM-堆

    堆 JAVA技术交流群:737698533 堆核心概述 此内存区域的唯一目的就是存放对象实例 一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域。 Java堆区在JV…

    数据库 2023年6月16日
    0109
  • Spring常见问题

    Spring常见问题 问渠那得清如许?为有源头活水来。 Spring 是个 java 企业级应用的开源开发框架。Spring 主要用来开发 Java 应用,但是有些扩展是针对构建 …

    数据库 2023年6月14日
    077
  • 【数据库】– MySQL SQL调优笔记(2)

    1.常规索引优化方式 1.1.单表优化 查询category_id为1且comments大于1的情况下,views最多的article id SELECT id, author_i…

    数据库 2023年5月24日
    080
  • MySQL45讲之备库并行复制策略

    前言 本文主要介绍 MySQL 备库的并行复制策略。 为什么备库需要并行复制 如果主数据库有大量的更新操作,因为主数据库可以并发写入,而从数据库只能在单个线程中执行,那么从数据库的…

    数据库 2023年5月24日
    082
  • openpyxl使用总结

    设置表头单元格的颜色 fill = PatternFill("solid", fgColor=’FF000000′) font = Font(color=’00…

    数据库 2023年6月9日
    081
  • DRF使用缓存

    在做项目的时候,有时很多数据都是经常被访问的而且数据是基本不变的,比如省市区的数据是经常被用户查询使用的,而且数据基本不变化,所以我们可以将省市区数据进行缓存处理,减少数据库的查询…

    数据库 2023年6月14日
    069
  • 你是否听说过 HashMap 在多线程环境下操作可能会导致程序死循环?

    作者:炸鸡可乐原文出处:www.pzblog.cn 一、问题描述 经常有些面试官会问, 是否了解过 HashMap 在多线程环境下使用时可能会发生死循环,导致服务器 cpu 100…

    数据库 2023年6月14日
    079
  • mysql范式

    mysql范式: mysql建表的规范格式 第一范式:保证每列的原子性(字段不能再分解) 第一种范式是最基本的范式。如果数据库表中的所有字段值都是不可分解的原子值,则数据库满足第一…

    数据库 2023年5月24日
    085
  • mysql拆分字符串做条件查询

    mysql拆分字符串作为查询条件 有个群友问一个问题 这表的ancestors列存放的是所有的祖先节点,以 ,分隔 例如我查询dept_id为103的所有祖先节点,现在我只有一个d…

    数据库 2023年5月24日
    075
  • ConcurrentHashMap深入剖析(基于JDK1.7)

    1 package cn.com.pep.concurrent; 2 3 import java.util.concurrent.ConcurrentMap; 4 import j…

    数据库 2023年6月14日
    0106
  • 关系型、非关系型数据库存储选型盘点大全

    工作中总是遇到数据存储相关的 Bug 工单,新需求开发设计中也多多少少会有数据模型设计和存储相关的问题。经过几次存储方案设计选型和讨论后发现需要有更全面的思考框架。 日常开发中常用…

    数据库 2023年6月14日
    0124
  • String vs StringBuffer vs StringBuilder

    String vs StringBuffer vs StringBuilder 本文翻译自:https://www.digitalocean.com/community/tutor…

    数据库 2023年6月11日
    096
  • Dev-tools使用遇到的小问题

    Dev-tools使用遇到的小问题 间隔长时间后重新打开某网页,提示Failed to load data:No resource with given identifier fo…

    数据库 2023年6月6日
    085
  • 在浏览器中Django项目的静态文件打不开的一个原因

    2022-09-27 问题描述: 编写Django代码时,设置了一个”static”文件夹,在里面放置了一张图片。在”setting&#8221…

    数据库 2023年6月14日
    0100
  • 2022蓝帽杯初赛wp(取证)

    战果 取证全解 misc出了1个 解其他题就像在坐牢 有那么一点思路,但不是完全有 手机取证_1 解压并打开阅读器,搜索627604C2-C586-48C1-AA16-FF33C3…

    数据库 2023年6月11日
    0100
  • MySQL索引:B+树索引

    MySQL索引:B+树索引 B+树索引是传统意义上的索引,这是目前关系型数据库系统中查找最为常用和最为有效的索引。B+树索引的构造类似于二叉树,根据键值快速找到数据 B树 B+树是…

    数据库 2023年5月24日
    083
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球