Mysql索引不当引发死锁问题

1. 前言

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

2.遇到的问题

先介绍一下我们的库表结构,数据库表中的数据为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;

2.1 问题1: 死锁

2.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.1.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会回滚其中一个事务,其他的会正常执行,如果偶尔出现一次死锁是可以接受的,但如果大面积的出现死锁,整个系统的性能就会下降

2.1.3解决方法

从上边分析的原因得知,造成死锁的原因是有大量并发的更新导致,如果想要解决死锁问题,那我们就要控制并发数量。

那如何控制并发量呢?

在单位时间内减少请求的数量,可以采用在程序中加锁的方式。但这种方式会导致系统的性能下降。

再次分析导致死锁的现象,我们发现在死锁出现之前有大量的锁等待,如果在单位时间内能减少锁等待的update语句数量,是不是可能会出现转机?

紧接着我们把优化的方向放到了 update语句上,怎样才让update语句执行的更快呢?

归根到底,还是索引问题

既然分析清楚了索引失效的原因,那就好解决了,调整一下索引创建顺序是不是就可以了。

在创建索引的时候,把identifier放到了最后,调整后的索引为 (pool_id, status, is_redeemed, identifier);

最终的结果是:我们QPS瞬间就到3000+,瞬间就起飞了。

2.2 问题2 依然是死锁问题

在解决了死锁的问题之后,我们又再一次面临了死锁问题,上次索引顺序已经调整过来了,这次又是什么原因呢?

2.2.1现象

这次是偶发? 什么,偶发? 程序员最担心的就是偶发,那就意味着很难复现,很难定位问题。

因为这个问题,还发生了一段小插曲

这次的版本迭代目标是是补写单元测试和优化redis操作,不涉及到核心功能的修改,想着快速做一下压测就行了,但因为这个事情还弄的QA非常不开心,这是我俩的对话。

QA:修改啥了,上个版本还没问题的(唉声叹气!!) 我:就只做了单元测试和redis的优化。 QA:那为啥之前还好好的,现在不行了? 我:那看看是啥具体原因,要不你把上个版本也压测一下,看看结果呢

结果,你们猜怎么着,也是同样的问题,那就证明一个事,这个问题跟咱们这次改的真没关系。

但这也不是QA的问题,还是咱的问题,毕竟之前的代码也是咱写的啊。

这次的问题根本不知道什么原因引起的。

2.2.2 原因

无论多难,问题还是要解决的。

既然和上次的表现一样,按照上次的经验,怀疑可能还是跟索引有问题,于是拿着出现问题的pool_id做了一次执行计划

意想不到的事情竟然出现了

这次依然没走计划中的索引,竟然走了index idx_pool on t_award (pool_id)这个索引,是什么原因呢?

mysql为了查询效率,会对索引做优化,有时候会选错索引。

2.2.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索引匹配正常了,问题解决了。

3. Mysql索引机制

索引的用处在于能够快速找到你需要的东西,比如你在图书馆找本书,图书管理员告诉你在几号书架第几个,这就是索引。

索引和数据有时候不一定是放在一起的。图书管理员和图书有时候并不在一起。

3.1 索引的类型

先看一个例子

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;

  1. select * from stx where id=1;
  2. select * from stx wehre b='a';
  3. select id from stx wehre a=1

这三个sql语句分别用到了唯一索引(聚簇索引),普通索引和覆盖索引

唯一索引

image.png

sql1 直接通过id=1找到索引,定位到叶子节点,不需要回表就可以查询到数据

普通索引

image.png

sql2查找数据的流程为 通过普通索引b='a'找到所在位置;

通过a值获取到主键id=1;

通过id=1回表获取到整行数据

sql3 是联合索引但和普通索引的结构是一样的,唯一不同的是sql3不需要回表,为什么呢?

因为id的值可以直接拿到,性能更快一些。

3.2 索引的数据结构

常见的索引结构有Hash、有序数组,B+树

Hash结构

Hash结构最大的优点是快速查找,时间复杂度为O(1), 如果Hash值冲突会存入到链表,如果链表过大就会影响查询效率,链表是挨个遍历查询。

Hash是典型的KV结构

有序数组

image.png

有序数组在查找和插入上的效率非常高,比如按照区间查询 between 3 AND 5. 但有序数组不适合动态增加的场景,因为动态增加会涉及到页分裂,从而导致随机磁盘的IO。 有序数组适合类似归档的静态数据库。

二叉树

二叉树的特点是左节点小于父节点,父节点小于右节点。

image.png

如果要找到leaf3的路径是 root->index2->leaf3,时间复杂度为 O(log(N))

4. Mysql锁机制详解

4.1 行锁 record lock

对一行数据或者多行数据加锁称为行锁,请看下图

image.png

开启两个命令窗口,执行sql,结果对比

image.png

从实验中看出,对id=1加了行锁。

4.2 间隙锁 gap lock

  1. 执行以下sql

set autocommit =0;

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

结果是:3 rows affected in 15 ms

  1. 执行

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;

结果是:

image.png

我们看到id=1和id=6成功了,但id=5没成功,为什么呢?

因为mysql的锁定区间是(1,5] 左开右闭原则。

间隙锁仅适用于可重复读级别,因为可重复读级别有幻读的问题产生,mysql为了防止幻读的问题出现才有了间隙锁。

幻读是:同一个事务,在同一个时刻读取的数据不一样。

4.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);

image.png

image.png

第一个sql加锁的范围是(0,10),所以阻塞了insert的插入。

注意 mysql加锁的粒度是next key lock

4.4 锁的退化机制

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

总结

本文通过具体遇到的问题,抽丝剥茧的方式介绍了引起死锁的原因,从而介绍了mysql的索引机制和类型。

重点需要弄懂mysql的加锁机制,方便在日后的工作中使用。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

二进制、十进制转换进阶--小数点后的转换

上一篇文章详细介绍了整数的二进制,八进制,十进制,十六进制之间的转换 详情可前往:二进制、八进制、十进制、十六进制的相互转换-CSDN博客 这篇介绍含有小数点之间的转换 一:二进制转十进制 二进制 101.11 可以分为两部分 101 和 0.11 整数部分 101 转换的方式是从右到左,…

【文心智能体】通过低代码工作流编排创建应用《挑战奥运问答拿奖牌》

欢迎来到《小5讲堂》 这是《文心智能体平台》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解。 温馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01; 目录 背景整体界面大模型链提示词模型 工具链HTTP请求工具 逻辑…

游戏开发设计模式之策略模式

目录 策略模式在游戏开发中的具体应用案例有哪些&#xff1f; 如何在Unity中实现策略模式以优化角色行为和AI策略&#xff1f; 策略模式与其他设计模式&#xff08;如观察者模式、状态模式&#xff09;在游戏开发中的比较优势是什么&#xff1f; 策略模式的优势 观察者模式…

【Qt笔记】QCommandLinkButton控件详解

目录 引言 一、概述 二、特性与属性 1. 属性 2. 样式 三、基本用法 1. 引入必要的头文件 2. 创建和配置 QCommandLinkButton 3. 布局管理 四、高级用法 1. 自定义绘制 2. 动态内容更新 五、代码解析示例 注意 总结 引言 QCommandLinkButton 是 Qt 框架中 QtWi…

android关于binder的简单通信过程

文章目录 简述aidl文件服务端的实现客户端的实现验证过程 简述 主要实现的是两个应用之间跨进程通信的过程&#xff0c;client端调用server端的具体实现&#xff0c;然后server端给client回调数据&#xff0c;详细如下所示 aidl文件 以下的文件需要在服务端与客户端都配置一…

外包干了两年,快要废了。。。

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 简单的说下&#xff0c;我大学的一个同学&#xff0c;毕业后我自己去了自研的公司&#xff0c;他去了外包&#xff0c;快两年了我薪资、技术各个方面都有了很大的…

Linux top 命令详解

top命令是Linux和Unix系统中一个非常强大的实时系统监控工具&#xff0c;它可以显示系统中各个进程的实时动态管理视图&#xff0c;类似于Windows的任务管理器。在需要诊断系统性能问题或监控资源使用情况时是非常有用的。 使用top命令 在命令行中输入top并回车&#xff0c;即…

Dubbo ZooKeeper Spring Boot整合

依赖配置 1. Dubbo 起步依赖 Dubbo 是一款高性能的 Java RPC 框架&#xff0c;用于快速开发高性能的服务。 <dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-spring-boot-starter</artifactId><version>${dubbo.ver…

非阻塞轮询

目录 前言1.options 参数2. 非阻塞轮询3. 模拟非阻塞轮询4. 非阻塞轮询 执行其它任务 前言 继上一篇文章 详谈进程等待 讲到 waitpid 系统调用&#xff0c;在该系统调用接口中还有一个 options 参数&#xff0c;本篇文章介绍 watipid 系统调用中的options 参数 以及 什么是非…

谈到这个痛点,写C的和不写C的码农都沉默了

声明&#xff1a;此篇为 ai123.cn 原创文章&#xff0c;转载请标明出处链接&#xff1a;https://ai123.cn/2246.html 作为一名在计算机软件行业工作的C工程师&#xff0c;我深知在高要求的内存管理环境中工作有多么艰难。内存分配与优化、避免内存泄漏&#xff0c;都是日常挑战…

工业相机测长仪的组成部分

关键字:工业相机测长仪,高精度测长仪,视觉测量系统,蓝鹏测控测长仪,工业测长仪, 本文介绍了蓝鹏测控公司机器视觉业务 测长仪的核心产品及技术特点&#xff0c;主要涵盖相机部分、相机防护系统、补光系统和软件部分。 &#xff08;一&#xff09;相机部分 我司的机器视觉业务…

SpringBoot 项目——抽奖系统

本项目主要实现的功能是&#xff1a;主要服务于管理员用户&#xff0c;其可圈选奖品&#xff0c;人员来创建抽奖活动&#xff0c;并进行在线抽奖&#xff0c;并可通过短信或邮件的方式通知中奖者&#xff0c;同时普通用户可查看已结束的抽奖活动的中奖结果&#xff1b; 一、项…

JAVA电子器件制造行业生产管理系统计算机毕设计算机毕业设计

项目开发意义 目前小型企业基本上是采用人工完成生产及物料的车间计划,由于企业运作是以订单驱动而非计划生产,人工手段无法及时随新订单的到来更新计划,造成计划偏离实际;各个生产单位(车间)各自为战,分别提出物料、设备、专用工具的需求,在整个企业层面上很难较精确地控制物料…

C++核心编程02——引用

摘录于B站黑马程序员提供的笔记。 1. 引用的基本使用 作用&#xff1a; 给变量起别名 语法&#xff1a; 数据类型 &别名 原名 实例&#xff1a; #include <iostream> using namespace std;int main() {// 引用基本语法// 数据类型 &别名 原名int a 10;in…

2024年8月28日(docker网络)

跨主机的容器网络连接 A>mysql B>java容器 将A -p3306:3306 端口映射就可以实现 一、docker网络 1、本地网络 bridge 所有容器连接到桥就可以使用外网,使用nat让容器可以访问外网,使用ip a s指令查看桥,所有容器连接到此桥,ip地址都是172.17.0.0/16网段,桥是启动…

ESP8266通过WiFiManager实现Web配网

背景 一个项目中使用到了一款压力传感器,需要通过单片机实现数据的采集并发送到远程的服务器上,单片机采用的时ESP8266,通过WiFiManager实现局域网配置,以及远端服务器IP地址和服务端口的配置。发布此文章记录一下使用WiFiManager实现配网的方法。 程序流程图 示例代码 …

【机器学习-监督学习】双线性模型

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈Python机器学习 ⌋ ⌋ ⌋ 机器学习是一门人工智能的分支学科&#xff0c;通过算法和模型让计算机从数据中学习&#xff0c;进行模型训练和优化&#xff0c;做出预测、分类和决策支持。Python成为机器学习的首选语言&#xff0c;…

[HZNUCTF 2023 preliminary]flask

[HZNUCTF 2023 preliminary]flask 点开之后页面如图所示&#xff1a; 猜测是SSTI模板注入&#xff0c;先输入{7*7},发现模板是倒序输入的&#xff1a; 然后我们输入}}‘7’*7{{返回777777&#xff0c;这是jinja2模板&#xff1a; 我们需要让用户输入一个字符串&#xff0c;对其…

VS2022 QT环境显示中文乱码问题

1.问题描述 在VS2022中搭配QT6.2环境&#xff0c;在文本处设置中文&#xff0c;运行程序文本处显示乱码&#xff0c;未成功显示想要的中文。 2.VS2015解决方案 如果是VS2015的话&#xff0c;直接文件->高级保存选项可以设置编码格式。 修改编码格式如图所示&#xff1a;…

C语言迷宫制造

目录 开头程序程序的流程图程序的效果我推荐要制造的迷宫下一篇博客要讲的东西 开头 大家好&#xff0c;我叫这是我58。 程序 #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #include <stdlib.h> #include <string.h> void printmaze(const cha…