MySQL InnoDB 锁的二三事

近日, 在一个小型项目中, 遇到了一个触及我知识盲区的bug.

项目用的是MySQL 5.7.25, 其中有一张表 config_data, 包含四个字段, id, name, value, expireAt. 其中id为主键, name建有唯一索引, 表的用途大概就是存放一些有时效性的配置. 以上就是这次故事的背景.

(不要问我为什么要用这么奇怪的方式处理需要过时的配置, 项目太简陋以至只有一台虚拟主机和一个数据库, 别的什么都没了, 包括Redis)

这张表的使用场景大致为, 假设需要使用某配置 a, 先尝试从表中查找 a, 若找到, 判断是否过期, 过期或者值不存在则从外部获取配置的值并存入表中, 以便下次使用. 伪代码流程如下:

config = query('select value, expireAt from config_data where name = "a" lock in share mode;');

if (!config || config.expireAt < now) {    // &#x4E0D;&#x5B58;&#x5728;&#x6216;&#x5DF2;&#x8FC7;&#x671F;
    beginTransaction();
    config = query('select value, expireAt from config_data where name = "a" for update;');
    if (config && config.expireAt > now) {
        rollback();
        return config.value;
    }
    value = getConfigValueFrom3rdPartyServer();    // &#x4ECE;&#x5916;&#x90E8;&#x670D;&#x52A1;&#x5668;&#x83B7;&#x53D6;&#x914D;&#x7F6E;&#x503C;
    execute('insert config_data (name, value, expireAt) value ("a", value, newExpireTime) on duplicate key update value=value, expireAt=newExpireTime;');    // &#x63D2;&#x5165;&#x6216;&#x66F4;&#x65B0;&#x914D;&#x7F6E;&#x503C;&#x4EE5;&#x53CA;&#x8FC7;&#x671F;&#x65F6;&#x95F4;
    commit();
    return value;
}

return config.value;

由于配置的值需要从外部服务器通过接口调用获取, 执行代价较大, 更重要的是, 第三方服务器的接口有每日调用次数限制, 因此必须控制出现并发更新配置值时 (即同一时间多个请求到来时配置项过期了) 只有一个进程发起请求获取配置值并更新数据库, 其余进程需等待更新完成并使用更新后的数据.

Again, 只有虚拟主机+DB, 故只好借用数据库方式加锁. 基本思路就是, 开始时使用共享锁 (S Lock) 查找配置值 (数据库使用了默认的autocommit, 语句执行完后共享锁自动释放), 如果需要更新, 开启事务, 使用排他锁 (X Lock) 锁住待更新行, 从外部服务器获取配置值 (不考虑获取失败情况, 配置值都获取不了只能直接往外抛异常了) , 使用 insert ... on duplicate key update 方式插入或更新数据库, 提交事务, done~

假设有两个进程A, B同时获取配置值, A, B均能同时获得共享锁并查询到已过期的配置, 然后尝试获取排他锁, 但只会有一个进程能成功获取排他锁, 这里假设是A, 则B在第5行时会被block住, 在A更新完成并提交事务后, B才能从第5行继续并获取到最新的配置值. 假如在A更新完成前, 第三个进程C又需要获取这个配置值, 则会在第1行尝试获取共享锁时由于排他锁已被A获得而被block住. 同样, 待A提交事务后C就能获得共享锁并拿到最新的值.

粗看逻辑没有问题, 并发的问题貌似完全可以由MySQL的行锁 (Record Lock) 解决. Perfect~ 于是就简单试了下功能, 扔代码上主机, 项目就上线运行了.

就这样过了两三天, 项目体量实在太迷你了, 每天最多也就1~2k的访问量, 因此服务器配置也是低得令人发指. 期间偶尔收到反馈说接口会报500错误, 我一概以”服务器配置太低”或者”网络问题”为由搪塞过去 (甩锅小能手~) , 倒也无惊无险地过来了. 直到那一次, 收到某个需求要小改一下前端界面, 调试的时候偶遇了这个神秘的500, 好奇看了一眼报错内容……

Deadlock found when trying to get lock; try restarting transaction

WTF? Deadlock???

一顿操作排查之后, 基本可以确定问题就是出在上面这段查找配置值的代码上. 当配置值过期后需要更新时, 如果同时有多个进程尝试执行上面的代码更新配置值时, 就会被检测出死锁. 具体表现为, 其中某个进程成功更新了数据库, 其余进程全部会抛出死锁异常, 几乎100%必现 (必现的bug就是好bug~).

按一般对死锁的理解, 常见的场景是两个进程按相反顺序加锁访问两个资源, 然后卡在互相等对方释放第一个资源造成的. 然而, 上面的代码明显和这个场景完全不沾边啊?…… 百思不得其解, 只能用尽各种模拟方法尝试找到原因. 还好最后终于确认了重现的步骤:

首先惯例假设有两个进程A和B.

开始事务 开始事务 select … for update 查找name=a的行并获得结果 select … for update 查找name=a的行 (被阻塞) insert … on duplicate key update … 更新数据成功 (deadlock found, gg) 事务被强行中断并回滚 提交事务, 完成更新

然后我就 (黑人问号.jpg) . Why???? 我不就是更新了行数据, 你都被阻塞了, 等我更新完再去拿结果不就好了?

而且, 即使我将 insert ... on duplicate key update ... 替换成 insert ... , 也照样能造成B死锁, 只是A也因唯一索引冲突插入失败而已, 也就是说, 死锁和更新无关 (也许吧).

这真的超出我理解范围了. 调出死锁分析看看 (执行 show engine InnoDB status; 然后查看Status字段的 LATEST DETECTED DEADLOCK 部分)


LATEST DETECTED DEADLOCK

Original: https://www.cnblogs.com/reginald-lee/p/16697879.html
Author: Reginald-Yoeng-Lee
Title: MySQL InnoDB 锁的二三事

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

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

(0)

大家都在看

  • jsp规范

    jsp&#x89C4;&#x8303; 一。jsp介绍:来自于javaee规范里面的一种jsp规范制定了如何开发jsp文件代替响应对象将处理结果写入到响应体的开发流…

    数据库 2023年6月11日
    0106
  • JavaScript进阶内容——BOM详解

    JavaScript进阶内容——BOM详解 在上一篇文章中我们学习了DOM,接下来让我们先通过和DOM的对比来简单了解一下BOM 首先我们先来复习一下DOM: 文档对象模型 DOM…

    数据库 2023年6月14日
    0182
  • python-tkinter 自定义tkinter风格的提示框

    博客园的密码终于找回了 前言 偶尔使用python要绘制个简单输入提示框或者复选框窗体,使用tkinter的话绘制窗体也是很麻烦的,想着能不能把它自定义一个简单可复用的提示框。然后…

    数据库 2023年6月11日
    098
  • PHP设计模式—享元模式

    定义: 享元模式(Flyweight):运用共享技术有效地支持大量细粒度的对象。 结构: Flyweight:享元抽象类,所有具体享元类的接口,通过这个接口,Flyweight 可…

    数据库 2023年6月14日
    0106
  • Java处理.tif或.tiff图片

    前言 Java将图片读取到内存用的是ImageIO,默认可以处理的图片格式如下: undefined ImageIO.getWriterFileSuffixes() //此方法返回…

    数据库 2023年6月9日
    0100
  • SQL的约束

    概念:约束是作用于表中字段上的规则,用于限制存储表中的数据; 常见的约束分类:约束 描述 关键字 非空约束 限制该字段的数据不能为null not null 唯一约束 保证该字段的…

    数据库 2023年6月16日
    0113
  • k8s vs k3s: 差异解析

    2. K3s的优势 小型 K3s 的最大优势是它的尺寸最小(小于 100 MB),这有助于它以最少的设置在小型硬件中启动 Kubernetes 集群。 快速部署 curl -sfL…

    数据库 2023年6月14日
    0133
  • Try-with-resources are not supported at language level ‘5’

    Try-with-resources are not supported at language level ‘5’没有指定maven版本导致 的 指定ma…

    数据库 2023年6月16日
    098
  • Mybatis-Plus 实现乐观锁

    是指在读取一行数据时,记下它的版本号、最近修改的时间戳或校验和。然后,你可以在修改记录之前检查版本有没有发生变化。 适用场景 适用于读多写少的场景,乐观锁相信事务之间的数据竞争概率…

    数据库 2023年6月6日
    0108
  • MySQL45讲之查询慢或者阻塞

    前言 本文介绍了表锁定和执行速度慢的实例,以及表锁定时的故障排除方法。 [En] This paper introduces examples of table locking a…

    数据库 2023年5月24日
    0141
  • Are You OK?主键、聚集索引、辅助索引

    每张表都一定存在主键吗? 关于这个问题,各位小伙伴们不妨先自己想一想,再往下寻找答案。 首先公布结论: 对于 InnoDB 存储引擎来说,每张表都一定有个主键(Primary Ke…

    数据库 2023年6月6日
    099
  • win10彻底永久关闭自动更新的方法【已验证有效】

    [知识整理/来源网络] 原文链接:win10彻底永久关闭自动更新的方法【已验证有效】_电脑知识-电脑配置网 (dnpz.net) win10的自动更新可谓是非常顽固,很多用户在网上…

    数据库 2023年6月9日
    0133
  • 史上最全Mysql规范

    1 整体规约 1)【强制】数据库所有对象必须要有注释,包括:表、字段、索引等,并且要保持最新; 1)【强制】默认使用utf8字符集,无乱码风险,除一些需要存储特殊符号的字段,可以采…

    数据库 2023年5月24日
    0114
  • Java学习-第一部分-第三阶段-第三节:MySQL基础

    零基础学MySQL 笔记目录:(https://www.cnblogs.com/wenjie2000/p/16378441.html) 一个问题 淘宝网,京东、微信,抖音都有各自的…

    数据库 2023年6月11日
    0115
  • Python–软件目录结构

    目的不必多说:提高项目可读性、可维护性 软件目录结构示例: 那么问题来了,当类似于如上的目录结构时,我怎么在game.py中去调用setting.py或者main.py中的函数呢?…

    数据库 2023年6月9日
    0111
  • 翻译 | 解读首部 Kubernetes 纪录片

    Honeypot.io 自诩为欧洲最大的技术人才招聘平台,同时提供开发者视频网站,又被称其为 ” 开发者的 Netflix“。2022 年 1 月,该公司与…

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