【学习笔记】MySQL死锁及热点行问题

news/2024/5/20 13:00:36 标签: mysql, 死锁, 热点行, MySQL优化

目录


本文记录下关于MySQL优化的学习和一点点思考。

案例

一个并发比较大的下单接口;
包括

  • step1 扣减商品库存
  • step2 生成订单数据
  • step3 记录操作记录

伪代码如下,底层使用的是MySQL数据库,单体服务(你问我为什么单体,案例需要啦)。

提问:暂时不考虑分布式锁、缓存、异步等使用场景,下面的代码执行步骤有没有值得优化的点?

  @Transactional(rollbackFor = Exception.class)
  public void createOrder(Entity param){
       ...校验等
      //step1 扣减商品库存
      //update products set stock = stock where sku_id = XXX
      deductStock(param);
      //step2 生成订单数据
      //insert into order XXXXXX
      createOrder(param);
      //step3 记录操作记录
      //insert into operation_records XXX
      recordOperation(param);
      ... 
  }

上述代码做了哪些事,开启了事务

更新了一个个表:库存表库存分

插入了两条数据:用户订单表,用户操作记录

对于当前的业务,上述代码的DB执行流程是没有问题的,

但是朋友,我们是要有梦想滴,CRUD BOY不是我们的终点,我们的目标是星辰大海。

来,键盘给你,优化下。

优化思路

正常业务情况下,上述代码没毛病,开启事务,库存更新语句会加上行锁,插入语句可能会加上插入意向锁等。

我们重点来关注下 更新语句的行锁,并发高的情况下,同一种库存商品数据是根据sku_id来更新,存在激烈的行锁竞争抢占的问题。

来回忆一下,MySQL事务开启后,是什么时候开始给库存加行锁的?

事务开启就加行锁? 错。
是在执行更新语句的时候加锁的,上述的step1

那么解锁是什么时候呢?

当然是事务提交的时候。

那么对于案例中的语句来说,products表的sku_id = XXX这行数据的行锁,将从step1开始锁定,直到createOrder()方法执行完毕才会解锁。

对于其他的创建订单请求,热点商品,相同的sku_id,同时执行这行代码的时候,只能等待。

这样分析可以发现,持有锁的时间太长了。

没错,是时间太长了,换个场景,或许我会欣喜若狂,彷佛得到了认可,

但此时此刻,这并不是我想要的。

那么,是否可以将更新库存的语句放到最后呢,step1 变成step3,这样对一一次创建订单的请求,加锁的时长会大大减少,并发度会提升。

  @Transactional(rollbackFor = Exception.class)
  public void createOrder(Entity param){
       ...校验等
      //step1 生成订单数据
      //insert into order XXXXXX
      createOrder(param);
      //step2 记录操作记录
      //insert into operation_records XXX
      recordOperation(param);
      //step3 扣减商品库存
      //update products set stock = stock where sku_id = XXX
      deductStock(param);
      ... 
  }

这样的优化虽不起眼,但是架不住积少成多,从细节处着手,才能仰望星空。

这样才是泰裤辣!

死锁的一些记录笔记

继续讨论下,上述代码没有问题了,但是

职业生涯中,我并没有真正的面对过MySQL的死锁,很幸运也很不幸,无需面对老大难的风险,但也没有获得实操的经验。

死锁的产生及竞态条件不再赘述。

最接近死锁的一次,还是刚工作的时候在一家保险公司,我还是nobody,当然了现在也还是nobody。

大约记得夜里的批处理更新账户信息,以及客户投保时,都需要保险账户总表账户明细表更新,批处理和客户投保的请求互相竞争对方持有的锁,导致死锁,疯狂报警,处理方案则是DBA大半夜爬起来 kill掉其中一个session,解除死锁

很明显上述处理的方案是不可持续的。

完全避免死锁不太现实,我们需要做的是尽可能减少死锁发生的概率。

MySQL给我们提供了两种方案

  • 互相等待锁释放,直至超时,通过innodb_lock_wait_timeout来设置
  • 死锁检测,innodb_deadlock_detect设置为on (8.0之后版本提供)

第一种策略,我们固然可以设置很短的超时时间,但是对于慢SQL来说很容易误伤。

所以一般会有第二种方案,超时检测。
MySQL会自动查询是否存在连接相互等待获取对方连接的情况,如果存在,会回滚其中一个事务来打破死锁

但是高并发情况下,比如上述的案例接口,死锁检测会影响到MySQL执行速度和效率,我们就只能通过第一这种方案来等待超时释放。

热点行问题

正如我们上面讨论的,案例中的某个热门产品的库存对于MySQL来说就是热点行,会存在频繁的锁竞争死锁等问题,会导致性能下降,MySQL本身也没有更好的方案来避免。

我们考虑下从业务层来解决这个问题。

  • redis预扣减

使用redis缓存热点商品,借助redis的高性能、高并发特性,承担大部分的请求压力。

但是redis会增加复杂度,可能会带来数据不一致的情况,例如超卖和少卖。

  • 库存拆分

往往库存只有一条数据,作为共享资源,为了保证安全,MySQL会对这条数据加锁,请求只能排一条长队等待锁的释放,如果我们把这条数据拆分成多条,10条数据,20条数据,性能的提升是可以预见的,大量请求的锁竞争可被分散在多条数据上,请求的耗时根据拆分的力度逐渐降低。

拆分缺点也很明显,对于拆分后的数据,某条库存用完了怎么办,怎么判断库存超卖不超卖,以及库存数据的增加维护等等等,系统复杂度会提升。

  • 请求合并

对于反馈需要及时的电商场景,请求合并并不一定适合,但是对于实时性没那么强的业务场景,比如说银行或者保险的热点账户,多个账户变动请求同时变更同一个账户,我们可以在1s、5s或者更长一段时间内将这段时间的多个请求在内存中计算合并后,再去更新数据库,可以大大降低数据库的压力。

  • 更新转插入

这种方式也是针对异步场景 UPDATE语句转INSERT语句,原来更新一条数据,改为插入单独的一个明细表,通过异步的方式将明细表进行汇总计算,再合并到原数据中。

这么一想设计思想是否和MySQL redo log的使用方式比较接近了,先顺序写磁盘,然后异步写入MySQL数据表中。
很多设计思想都是一致的,无非是实现的区别

理论上我们上述说的方案,也都可以通过MySQL进行改造,比如请求合并,改为在MySQL层对相同的SQL语句(这里的相同指更新逻辑基本一致)进行合并。


http://www.niftyadmin.cn/n/5165607.html

相关文章

【python 学习】代码插桩调试

Python 代码插桩方法 文章目录 Python 代码插桩方法什么是插桩技术?使用插桩调试python插桩的一种实现方法 什么是插桩技术? 插桩技术是指在保持原有程序逻辑完整性的基础上,在程序中加入探针,通过探针来收集代码在执行过程中的信…

Flink 基础 -- 应用开发(Table API SQL) Table API

Table API是用于流和批处理的统一关系API。表API查询可以在批处理或流输入上运行,而无需修改。Table API是SQL语言的超集,专为与Apache Flink一起工作而设计。Table API是Scala、Java和Python的语言集成API。与将查询指定为SQL中常见的字符串值不同&…

百度上海智能研发中心一面

Prometheus告警机制原理 介绍hashmap和concurrentHashmap concurrentHashmap和hashmap如果线程1在遍历 另一个线程对这个map进行修改操作 会发生什么现象 对线程安全的理解 通过什么方法解决线程安全 除了上锁 CAS等还有其他手段 不用锁的话 (集合的类设计成一…

【ubuntu】ubuntu系统查看服务命令

查看正在运行的服务 sudo service --status-all [] 代表服务是在启动运行的状态 [-] 代表服务是在关闭停止的状态

还在从零开始搭建项目?这款基于SpringBoot 3的快速开发脚手架值得一试!

关注我Github的小伙伴应该了解,之前我开源了一款快速开发脚手架mall-tiny,该脚手架完整继承了mall项目的技术栈,拥有完整的权限管理功能。最近抽空把该项目支持了Spring Boot 3,今天再和大家聊聊这个脚手架,同时聊聊升…

Git-Lab私域共享代码

1.导读 本教程主要讲解了GitLab在项目的环境搭建和基本的使用,可以帮助大家在企业中能够自主搭建GitLab服务,并且可以GitLab中的组、权限、项目自主操作。 - GitLab简介 - GitLab环境搭建 - GitLab基本使用(组、权限、用户、项目) 2.GitLab简介 Gi…

免费的把人声和背景音乐分离的软件或者在线网站

搜索引擎里的那些不是要付费的就是效果不好的,小编亲测分享几种方法: 一、在线免费分离人声和背景音乐网站 https://vocalremover.org/zh/ 亲测好用,但不知道能用到什么时候,且用且珍惜,觉得效果不好的话看第二种 …

vite 关于 global is not defined 问题

问题:在使用第三方包的时候也报错:global is not defined 原因:Vite 是 ESM 机制,有些包内部使用了 node 的 global 对象 1、最简单polyfill: window.global || window;export default defineConfig({define: {glob…