记一次MySQL索引不当引发死锁问题

news2024/9/23 9:14:20

一、前言

在并发量很低的情况下,mysql 的响应时延一切正常,一旦并发量上去了,mysql就会出现死锁的情况,你有没有遇到过?到底是是什么原因导致的呢,让我们一起看看真实的案例。

二、遇到的问题

先介绍一下我们的库表结构,数据库表中的数据为500w

create table t_award
(
    id            bigint(30)            not null primary key,
     award_no     varchar(30)           not null comment '奖券',
    award_pwd     varchar(100)          not null comment '奖券密码',
    pool_id    int(20)    default 0  not null comment 'poolId',
    is_redeemed      tinyint(1) default 0  not null comment '0.兑奖 1.未兑奖',
    status        tinyint(1)            not null comment '0 正常',
    deleted      tinyint(1) default 0  not null comment '逻辑删除 0.未删除 1.删除',
    identifier      varchar(100)          null,
    identifier_type    varchar(20)           null comment '身份类型',
    constraint award_no
        unique (award_no),
    constraint uniq_ins_identifier
        unique (pool_id, identifier, identifier_type)
)
    engine = InnoDB
    charset = utf8;

create index identifier
    on t_award (identifier);

create index idx_pool
    on t_award (pool_id);
   
create index idx_ins_stat
    on t_award (pool_id, identifier,status, is_redeemed);
唯一索引:unique (award_no) 和unique (pool_id, identifier, identifier_type)
普通索引:identifier,pool_id ,index(pool_id, identifier,status, is_redeemed)

根据业务场景,需要从抽奖池中获取一个没有兑换过奖的奖券,执行的sql为

select id from t_award where pool_id=? and identifier is null and status=0 and is_redeemed=0 limit 1;

三、问题1: 死锁

1、现象
从压测的第30s开始,QPS一下从1000骤降到100,紧接着就是十几了,响应时延TP95从10+ms上升到1s从mysql的监控上看,有一堆像这样的sql语句排队等待更新

update t_award 
set identifier=?, identifierType=? 
where pool_id=? and identifier is null and status=0 and is_redeemed=0 limit 1;

紧接着出现了死锁的情况

trascation 1

WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 70 page no 699223 n bits 144 index PRIMARY of table `test`.`t_ward` trx id 79626302 lock_mode X locks rec but not gap waiting Record lock, heap no 74 PHYSICAL RECORD: n_fields 37; compact format; info bits 0 0: len 8;
update t_award 
set identifier=?, identifierType=? 
where pool_id=19021 and identifier is null and status=0 and is_redeemed=0 limit 1

trascation2 79626303 HOLDS THE LOCK(S)

2、原因
锁等待原因:mysql对每个update增加排它锁,更新完成之后,释放锁,其他更新操作执行,mysql对于更新操作是串行的。
在大并发量的前提下,如果update语句慢,会造成排队现象。
这个时候我们不禁想问,为什么update语句这么慢呢?看一下我们创建的索引
index idx_ins_staton t_award (pool_id, identifier,status, is_redeemed);
从执行计划中得知并没有走我们创建的索引,是什么原因呢?
在这里插入图片描述
索引失效了
什么原因导致索引失效呢?
(pool_id, identifier,status, is_redeemed) 索引中identifier 该字段允许为null,这会导致identifier后边的字段失效,从而导致整个联合索引会失效,看来是索引的问题
那为什么会死锁呢?还记得pool_id是普通索引吗,在执行以下sql的时候
where pool_id=19021 and identifier is null and status=0 and is_redeemed=0 limit 1
pool_id加间隙锁,当互相持间隙锁的时候,就造成了死锁,看下图。
在这里插入图片描述
当出现死锁的时候,mysql会回滚其中一个事务,其他的会正常执行,如果偶尔出现一次死锁是可以接受的,但如果大面积的出现死锁,整个系统的性能就会下降
3、解决方法
从上边分析的原因得知,造成死锁的原因是有大量并发的更新导致,如果想要解决死锁问题,那我们就要控制并发数量。
那如何控制并发量呢?
在单位时间内减少请求的数量,可以采用在程序中加锁的方式。但这种方式会导致系统的性能下降。
再次分析导致死锁的现象,我们发现在死锁出现之前有大量的锁等待,如果在单位时间内能减少锁等待的update语句数量,是不是可能会出现转机?
紧接着我们把优化的方向放到了 update语句上,怎样才让update语句执行的更快呢?归根到底,还是索引问题既然分析清楚了索引失效的原因,那就好解决了,调整一下索引创建顺序是不是就可以了。在创建索引的时候,把identifier放到了最后,调整后的索引为 (pool_id, status, is_redeemed, identifier);。
最终的结果是:我们QPS瞬间就到3000+,瞬间就起飞了。

四、问题2 依然是死锁问题

在解决了死锁的问题之后,我们又再一次面临了死锁问题,上次索引顺序已经调整过来了,这次又是什么原因呢?
1、现象
这次是偶发?什么,偶发?程序员最担心的就是偶发,那就意味着很难复现,很难定位问题。
因为这个问题,还发生了一段小插曲
这次的版本迭代目标是是补写单元测试和优化redis操作,不涉及到核心功能的修改,想着快速做一下压测就行了,但因为这个事情还弄的QA非常不开心,这是我俩的对话。QA:修改啥了,上个版本还没问题的(唉声叹气!!) 我:就只做了单元测试和redis的优化。
QA:那为啥之前还好好的,现在不行了?我:那看看是啥具体原因,要不你把上个版本也压测一下,看看结果呢结果,你们猜怎么着,也是同样的问题,那就证明一个事,这个问题跟咱们这次改的真没关系。但这也不是QA的问题,还是咱的问题,毕竟之前的代码也是咱写的啊。
这次的问题根本不知道什么原因引起的。
2、原因
无论多难,问题还是要解决的。
既然和上次的表现一样,按照上次的经验,怀疑可能还是跟索引有问题,于是拿着出现问题的pool_id做了一次执行计划
意想不到的事情竟然出现了
这次依然没走计划中的索引,竟然走了index idx_pool on t_award (pool_id)这个索引,是什么原因呢?
mysql为了查询效率,会对索引做优化,有时候会选错索引。
3、解决方法
既然mysql选错索引了,那我们可以强制mysql走某个索引 force index()不就可以了吗?
事情真的这么简单吗?
如果真的是这样,mysql为什么会对索引做优化了呢?
所以还是不要修改mysql优化索引的机制,有可能会出现意想不到的情况,还是看看自己的索引创建的有没有问题
这是创建的索引

(pool_id, identifier,status, is_redeemed)

pool_id

(pool_id, identifier, identifier_type)

仔细一看,还真是,pool_id这个索引存在的必要性在哪里呢? 按照最左匹配规则,另外两个索引是可以覆盖到pool_id的。经过测试验证之后,就drop 掉了pool_id这个索引。
这下mysql索引匹配正常了,问题解决了

五、Mysql索引机制

索引的用处在于能够快速找到你需要的东西,比如你在图书馆找本书,图书管理员告诉你在几号书架第几个,这就是索引。
索引和数据有时候不一定是放在一起的。图书管理员和图书有时候并不在一起。

六、索引的类型

先看一个例子

create table stx
(
   id bigint primary key auto_increment not null,
   a  int   not null default 0,
   b  varchar2(12) null,
   index (b),
   index(a,b)
) engine = InnoDB;

select * from stx where id=1;
select * from stx wehre b=‘a’;
select id from stx wehre a=1
这三个sql语句分别用到了唯一索引(聚簇索引),普通索引和覆盖索引
1、唯一索引
在这里插入图片描述
sql1 直接通过id=1找到索引,定位到叶子节点,不需要回表就可以查询到数据
2、普通索引
在这里插入图片描述
sql2查找数据的流程为 通过普通索引b='a’找到所在位置;
通过a值获取到主键id=1;
通过id=1回表获取到整行数据sql3 是联合索引但和普通索引的结构是一样的,唯一不同的是sql3不需要回表,为什么呢?
因为id的值可以直接拿到,性能更快一些。

七、索引的数据结构

常见的索引结构有Hash、有序数组,B+树
1、Hash结构
在这里插入图片描述
Hash结构最大的优点是快速查找,时间复杂度为O(1), 如果Hash值冲突会存入到链表,如果链表过大就会影响查询效率,链表是挨个遍历查询。
Hash是典型的KV结构
2、有序数组
在这里插入图片描述
有序数组在查找和插入上的效率非常高,比如按照区间查询 between 3 AND 5. 但有序数组不适合动态增加的场景,因为动态增加会涉及到页分裂,从而导致随机磁盘的IO。有序数组适合类似归档的静态数据库。
3、二叉树
二叉树的特点是左节点小于父节点,父节点小于右节点。
在这里插入图片描述
如果要找到leaf3的路径是 root->index2->leaf3,时间复杂度为 O(log(N))

八、Mysql锁机制详解

1、行锁 record lock
对一行数据或者多行数据加锁称为行锁,请看下图
在这里插入图片描述
开启两个命令窗口,执行sql,结果对比
在这里插入图片描述
从实验中看出,对id=1加了行锁。
2、间隙锁 gap lock
执行以下sql

set autocommit =0;

update t_award set award_pwd='xxx' where id>1 and id<5;

结果是:3 rows affected in 15 ms

执行以下sql

update t_award set award_pwd='aaa' where id=1;

update t_award set award_pwd='aaa' where id=6;

update t_award set award_pwd='aaa' where id=5;

结果是:
在这里插入图片描述
我们看到id=1和id=6成功了,但id=5没成功,为什么呢?
因为mysql的锁定区间是(1,5] 左开右闭原则。
间隙锁仅适用于可重复读级别,因为可重复读级别有幻读的问题产生,mysql为了防止幻读的问题出现才有了间隙锁。
幻读是:同一个事务,在同一个时刻读取的数据不一样。
3、行锁+间隙锁=next Key lock
看一个例子

CREATE TABLE `t` (
                     `id` int(11) NOT NULL,
                     `c` int(11) DEFAULT NULL,
                     `d` int(11) DEFAULT NULL,
                     PRIMARY KEY (`id`),
                     KEY `c` (`c`)
) ENGINE=InnoDB;

insert into t values(0,0,0),(5,5,5),
                    (10,10,10),(15,15,15),(20,20,20),(25,25,25);

在这里插入图片描述
在这里插入图片描述
第一个sql加锁的范围是(0,10),所以阻塞了insert的插入。
注意 mysql加锁的粒度是next key lock

九、锁的退化机制

1、mysql 加锁的初始粒度是next key lock,遵循左开右闭原则
2、等值查询,如果是唯一索引,退化成行锁
3、等值查询,如果是非唯一索引,向右查找到不等于的等值的第一个停止查询,则退化成间隙锁
4、唯一索引的范围查询,会查找到不满足条件的第一个值为止。

十、总结

本文通过具体遇到的问题,抽丝剥茧的方式介绍了引起死锁的原因,从而介绍了mysql的索引机制和类型。重点需要弄懂 mysql 的加锁机制,方便在日后的工作中使用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2157158.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LabVIEW提高开发效率技巧----利用第三方库和工具

LabVIEW开发不仅依赖于自身强大的图形化编程能力&#xff0c;还得益于其庞大的用户社区和丰富的第三方库。这些工具和库能够帮助开发者快速解决问题&#xff0c;提升开发效率&#xff0c;避免从头开始编写代码。 1. LabVIEW工具网络&#xff08;NI Tools Network&#xff09; …

MateBook 16s 2023在Deepin下开启性能模式,调节风扇转速到最大,全网首发!

方法 在Deepin下按住Fnp快捷键&#xff0c;开启性能模式。 验证 首先去debian下载acpi-call-dkms https://packages.debian.org/sid/all/acpi-call-dkms/download 然后使用root用户执行&#xff1a; apt install --simulate ./acpi-call-dkms_1.2.2-2.1_all.deb apt inst…

LeetCode 面试经典150题 191.位1的个数

Java中的算术右移和逻辑右移的区别 题目&#xff1a;编写一个函数&#xff0c;获取一个正整数的二进制形式并返回其二进制表达式中设置位的个数&#xff08;也被称为汉明重量&#xff09;。 设置位的个数即二进制中1的个数。 思路&#xff1a;方法一&#xff1a;因为正数的原…

基于阿里云免费部署Qwen1-8B-chat模型并进行lora参数微调从0到1上手操作

文章目录 一、申请资源二、创建实例三、克隆微调数据四、部署Qwen1-8B-chat模型1、环境配置2、模型下载3、本地模型部署 五、模型微调1、拉取Qwen仓库源码2、微调配置3、合并微调参数4、本地部署微调模型 一、申请资源 阿里云账号申请PAI资源详细教程我已于部署ChatGLM3时写过…

Golang | Leetcode Golang题解之第430题扁平化多级双向链表

题目&#xff1a; 题解&#xff1a; func dfs(node *Node) (last *Node) {cur : nodefor cur ! nil {next : cur.Next// 如果有子节点&#xff0c;那么首先处理子节点if cur.Child ! nil {childLast : dfs(cur.Child)next cur.Next// 将 node 与 child 相连cur.Next cur.Chi…

遗传算法与深度学习实战(14)——进化策略详解与实现

遗传算法与深度学习实战&#xff08;14&#xff09;——进化策略详解与实现 0. 前言1. 进化策略1.1 进化策略原理1.2 将进化策略应用于函数逼近 2. 实现进化策略小结系列链接 0. 前言 进化策略 (Evolutionary Strategies, ES) 是进化计算和遗传方法的扩展&#xff0c;增加了控…

【Python学习手册(第四版)】学习笔记24-高级模块话题

个人总结难免疏漏&#xff0c;请多包涵。更多内容请查看原文。本文以及学习笔记系列仅用于个人学习、研究交流。 本来计划中秋发布几篇文章&#xff0c;结果阳了&#xff0c;发烧、头疼、咽疼&#xff0c;修养了近一周&#xff0c;还没好完。希望大家都能有个好身体&#xff0…

proteus仿真软件简体中文版网盘资源下载(附教程)

对于电子通信专业的小伙伴来说&#xff0c;今天文章的标题应该不会陌生。Proteus是一款具有广泛应用的仿真软件&#xff0c;它的功能非常强大&#xff0c;适用于所有单片机的仿真工作&#xff0c;能够从原理图、调试、到与电路的协同仿真一条龙全部搞定&#xff0c;受到所有用户…

自己开发了一个电脑上滚动背单词的软件

在这个快节奏的时代&#xff0c;我们每天都在忙碌中度过&#xff0c;手机虽然方便&#xff0c;但往往难以找到一整块时间来专心背单词。然而&#xff0c;你是否意识到&#xff0c;每天坐在电脑前的时间远比使用手机的时间要长&#xff1f;现在我们来介绍一个新型的学习软件灵思…

Fyne ( go跨平台GUI )中文文档-容器和布局 (四)

本文档注意参考官网(developer.fyne.io/) 编写, 只保留基本用法 go代码展示为Go 1.16 及更高版本, ide为goland2021.2 这是一个系列文章&#xff1a; Fyne ( go跨平台GUI )中文文档-入门(一)-CSDN博客 Fyne ( go跨平台GUI )中文文档-Fyne总览(二)-CSDN博客 Fyne ( go跨平台GUI…

XSS—xss-labs靶场通关

level 1 JS弹窗函数alert() <script>alert()</script> level 2 闭合绕过 "> <script>alert()</script> <" level 3 onfocus事件在元素获得焦点时触发&#xff0c;最常与 <input>、<select> 和 <a> 标签一起使用…

科研绘图系列:R语言多个AUC曲线图(multiple AUC curves)

文章目录 介绍加载R包导入数据数据预处理画图输出结果组图系统信息介绍 多个ROC曲线在同一张图上可以直观地展示和比较不同模型或方法的性能。这种图通常被称为ROC曲线图,它通过比较不同模型的ROC曲线下的面积(AUC)大小来比较模型的优劣。AUC值越大,模型的诊断或预测效果越…

生成自签名证书和私钥

可以使用 OpenSSL 来生成自签名证书&#xff08;linux上执行&#xff09;&#xff1a; openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes生成 key.pem&#xff08;私钥&#xff09;和 cert.pem&#xff08;证书&#xff09; 当执行这个 …

某集群管理系统存在任意文件读取漏洞

你为什么要拼命努力&#xff1f;父母的白发&#xff0c;想去的地方很远&#xff0c;想要的东西很贵&#xff0c;喜欢的人很优秀&#xff0c;周围人的嘲笑&#xff0c;以及&#xff0c;天生傲骨。 漏洞描述 利用漏洞&#xff0c;攻击者可以读取 Windows 或 Linux 服务器上的任…

ER论文阅读-Decoupled Multimodal Distilling for Emotion Recognition

基本介绍&#xff1a;CVPR, 2023, CCF-A 原文链接&#xff1a;https://openaccess.thecvf.com/content/CVPR2023/papers/Li_Decoupled_Multimodal_Distilling_for_Emotion_Recognition_CVPR_2023_paper.pdf Abstract 多模态情感识别&#xff08;MER&#xff09;旨在通过语言、…

基于STM32残疾人辅助行走系统

要么是家人陪伴&#xff0c;要么是类似导盲犬的动物辅助&#xff0c;家人还有事要做&#xff0c;不一定实时在场&#xff0c;而动物辅助也可能会出现新的问题&#xff0c;威胁残疾人身体安全。因此利用现代计算机技术、传感器检测设备和物联网技术设计这一款辅助残疾人行走的智…

.NET常见的5种项目架构模式

前言 项目架构模式在软件开发中扮演着至关重要的角色&#xff0c;它们为开发者提供了一套组织和管理代码的指导原则&#xff0c;以提高软件的可维护性、可扩展性、可重用性和可测试性。 假如你有其他的项目架构模式推荐&#xff0c;欢迎在文末留言&#x1f91e;&#xff01;&a…

基于微信小程序的家教信息管理系统的设计与实现(论文+源码)_kaic

摘 要 随着互联网时代的来临&#xff0c;使得传统的家教模式已不复存在&#xff0c;亟需一种方便、快捷的在线教学平台。因此&#xff0c;利用Java语言作为支撑和MySQL数据库存储数据&#xff0c;结合微信小程序的便利性&#xff0c;为用户开发出了一个更加人性化、方便的家庭…

Centos 9 Steam扩容硬盘

要将 sda 的剩余空间扩展给 cs-root&#xff0c;可以按照以下步骤进行操作。假设你已经有剩余的未分配空间在 sda 上。 步骤 1&#xff1a;查看当前磁盘分区情况 首先&#xff0c;确保你有未分配的空间在 sda 上。 lsblk步骤 2&#xff1a;创建新的分区 使用 fdisk 或 par…

Apache Arrow IPC 消息格式

Apache Arrow 的 IPC&#xff08;Inter-Process Communication&#xff0c;进程间通信&#xff09;消息格式是一种用于在不同进程间高效传输数据的序列化格式&#xff0c;它允许不同系统或语言环境中的应用程序以统一的方式交换数据&#xff0c;而无需关心数据的具体存储细节。…