Java性能调优实战
刘超
金山软件西山居技术经理
立即订阅
7535 人已学习
课程目录
已完结 48 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 怎样才能做好性能调优?
免费
模块一 · 概述 (2讲)
01 | 如何制定性能调优标准?
02 | 如何制定性能调优策略?
模块二 · Java编程性能调优 (10讲)
03 | 字符串性能优化不容小觑,百M内存轻松存储几十G数据
04 | 慎重使用正则表达式
05 | ArrayList还是LinkedList?使用不当性能差千倍
加餐 | 推荐几款常用的性能测试工具
06 | Stream如何提高遍历集合效率?
07 | 深入浅出HashMap的设计与优化
08 | 网络通信优化之I/O模型:如何解决高并发下I/O瓶颈?
09 | 网络通信优化之序列化:避免使用Java序列化
10 | 网络通信优化之通信协议:如何优化RPC网络通信?
11 | 答疑课堂:深入了解NIO的优化实现原理
模块三 · 多线程性能调优 (10讲)
12 | 多线程之锁优化(上):深入了解Synchronized同步锁的优化方法
13 | 多线程之锁优化(中):深入了解Lock同步锁的优化方法
14 | 多线程之锁优化(下):使用乐观锁优化并行操作
15 | 多线程调优(上):哪些操作导致了上下文切换?
16 | 多线程调优(下):如何优化多线程上下文切换?
17 | 并发容器的使用:识别不同场景下最优容器
18 | 如何设置线程池大小?
19 | 如何用协程来优化多线程业务?
20 | 答疑课堂:模块三热点问题解答
加餐 | 什么是数据的强、弱一致性?
模块四 · JVM性能监测及调优 (6讲)
21 | 磨刀不误砍柴工:欲知JVM调优先了解JVM内存模型
22 | 深入JVM即时编译器JIT,优化Java编译
23 | 如何优化垃圾回收机制?
24 | 如何优化JVM内存分配?
25 | 内存持续上升,我该如何排查问题?
26 | 答疑课堂:模块四热点问题解答
模块五 · 设计模式调优 (6讲)
27 | 单例模式:如何创建单一对象优化系统性能?
28 | 原型模式与享元模式:提升系统性能的利器
29 | 如何使用设计模式优化并发编程?
30 | 生产者消费者模式:电商库存设计优化
31 | 装饰器模式:如何优化电商系统中复杂的商品价格策略?
32 | 答疑课堂:模块五思考题集锦
模块六 · 数据库性能调优 (8讲)
33 | MySQL调优之SQL语句:如何写出高性能SQL语句?
34 | MySQL调优之事务:高并发场景下的数据库事务调优
35 | MySQL调优之索引:索引的失效与优化
36 | 记一次线上SQL死锁事故:如何避免死锁?
37 | 什么时候需要分表分库?
38 | 电商系统表设计优化案例分析
39 | 数据库参数设置优化,失之毫厘差之千里
40 | 答疑课堂:MySQL中InnoDB的知识点串讲
模块七 · 实战演练场 (4讲)
41 | 如何设计更优的分布式锁?
42 | 电商系统的分布式事务调优
43 | 如何使用缓存优化系统性能?
44 | 记一次双十一抢购性能瓶颈调优
结束语 (1讲)
结束语 | 栉风沐雨,砥砺前行!
Java性能调优实战
登录|注册

36 | 记一次线上SQL死锁事故:如何避免死锁?

刘超 2019-08-13
你好,我是刘超。今天我们来聊聊死锁,开始之前,先分享个小故事,相信你可能遇到过,或能从中获得一点启发。
之前我参与过一个项目,在项目初期,我们是没有将读写表分离的,而是基于一个主库完成读写操作。在业务量逐渐增大的时候,我们偶尔会收到系统的异常报警信息,DBA 通知我们数据库出现了死锁异常。
按理说业务开始是比较简单的,就是新增订单、修改订单、查询订单等操作,那为什么会出现死锁呢?经过日志分析,我们发现是作为幂等性校验的一张表经常出现死锁异常。我们和 DBA 讨论之后,初步怀疑是索引导致的死锁问题。后来我们在开发环境中模拟了相关操作,果然重现了该死锁异常。
接下来我们就通过实战来重现下该业务死锁异常。首先,创建一张订单记录表,该表主要用于校验订单重复创建:
CREATE TABLE `order_record` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`order_no` int(11) DEFAULT NULL,
`status` int(4) DEFAULT NULL,
`create_date` datetime(0) DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_order_status`(`order_no`,`status`) USING BTREE
) ENGINE = InnoDB
为了能重现该问题,我们先将事务设置为手动提交。这里要注意一下,MySQL 数据库和 Oracle 提交事务不太一样,MySQL 数据库默认情况下是自动提交事务,我们可以通过以下命令行查看自动提交事务是否开启:
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.01 sec)
下面就操作吧,先将 MySQL 数据库的事务提交设置为手动提交,通过以下命令行可以关闭自动提交事务:
mysql> set autocommit = 0;
Query OK, 0 rows affected (0.00 sec)
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Java性能调优实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(21)

  • 张学磊
    MySQL默认开启了死锁检测机制,当检测到死锁后会选择一个最小(锁定资源最少得事务)的事务进行回滚

    作者回复: 这个回答是我想要的。

    Innodb提供了wait-for graph算法来主动进行死锁检测,我们可以通过innodb_deadlock_detect = on 打开死锁检测。

    2019-08-13
    10
  • ok
    老师,请问事例中insert order_record的事务AB中,请解答下疑惑,我描述如下
    1、事务A执行select 4 for update获取(4,+∞)间隙锁
    2、图中B事务再执行select 5 for update获取(5,+∞)的间隙锁
    3、事务A执行insert 4 发现事务A自己持有(4,+∞)间隙锁,所以不用等待呀!
    4、事务B执行insert 5 发现事务A没有commit,持有(4,+∞)间隙锁,所以等待事务A释放锁
    5、事务A提交,事务B insert 5获取到锁,commit

    请指出问题…

    作者回复: 在3的insert操作中,回去获取插入意向锁,而插入意向锁也是一种gap锁,根据矩阵图,插入意向锁和gap间隙锁是冲突的,所以insert操作需要等待间隙锁的释放。

    2019-08-16
    2
    2
  • 我已经设置了昵称
    老师。我们一般不会在查询的时候加上for update,我们的组长让我们事务中不要放查询语句,只能放插入或者更新,就是提前查好,组装好,然后开始执行事务。我觉得这其实会出现重复插入(并发量一高就会出现)。请问老师事务中真的不能做查询操作吗,还有查询的时候怎么防止同时两个事务查不到相对应的数据而造成重复插入

    作者回复: for update是一种悲观锁实现,我们可以使用性能更好的乐观锁来实现,通过版本号来实现数据更新不丢失问题,这种方式是最佳选择。

    而对于插入时防重复问题,可以对不允许重复字段设置唯一索引,进行唯一约束,这是一种不友好的实现方式。

    2019-08-13
    2
  • a、
    1.设置Transaction的超时时间
    2.设置Transaction的级别为串行化级别
    2019-08-13
    2
  • neohope
    oracle里面的自增列,一般是通过一个sequence来控制的,一直感觉不如mysql方便。根据今天的例子来看,虽然sequence会产生一些不连续的情况,但好像可以减少一些脏读和死锁的情况,反而感觉挺合理的。
    2019-11-26
  • cky.宇
    感觉还可以把隔离级别改成RC。RR下锁比较严格,举两个例子:一个就是间隙锁的原因,就像老师的例子一样,如果一个事务在表t1 insert后没有commit,其他事务就不能对表t1进行insert,这样就可能会出现用户A的insert锁住了用户B的insert的情况,其实用户A和B是业务不相关的。而RC下没有间隙锁,不会有这种情况。 第二个就是RR加锁的范围更大,RR下会锁住所有扫描过的行,只有commit后才会全部释放,例如:select no from orders where status = 1 and create_at > xx for update; 其中只有status有索引,那么mysql就要先扫描status索引再回表找满足create_at的行。如果是RR下,会锁住所有status=1的行,直到commit后释放。如果是RC下,当找到满足条件(status, craeted_at)的行后,会释放掉不满住条件但是status=1的行,不需要等到commit。这些细节都会造成RC和RR的性能差距很大。而一些需要重复读的需求可以通过代码来保证。
    2019-11-17
  • 黎波拉小建
    一个表它的主键是UUID生成的,如果说为了避免幻读而加了一个Next-key lock,那它会怎么锁的,感觉后插入的位置待定。。。还是全表锁?

    作者回复: 因为主键是唯一索引,所以不会使用next-key lock

    2019-10-21
  • 老师, 我实践了一下, 发现select col from t where t.col = 'xx' for update会导致覆盖索引失效,而使用使用共享锁就不会失效, 这是什么原理呢

    作者回复: select col from t where t.col = 'xx' for update不会导致索引失效的,使用排他锁是不是发生了死锁了呢?

    2019-10-13
  • lizhibo
    老师 ,最后那个 根据主键ID 和 订单编号更新状态的例子 我这边怎么都是 锁等待 而没有出现死锁的情况, 事物级别RR

    作者回复: 互相锁等待,又无法释放彼此等待的锁,就是死锁状态了

    2019-10-04
    1
  • Demon.Lee
    感悟:
    1、默认将mysql隔离级别调成RC
    2、使用主键更新

    疑问:RR级别下,如果使用唯一索引更新,是record lock么?

    作者回复: 是的

    2019-09-17
  • man1s
    死锁检测,消耗cpu
    2019-09-16
  • godtrue
    课后思考及问题
    1:有个疑问,文中说“插入意向锁其实也是一种 gap 锁,它与 gap lock 是冲突的,所以当其它事务持有该间隙的 gap lock 时,需要等待其它事务释放 gap lock 之后,才能获取到插入意向锁。”
    在锁的兼容矩阵图中,先获取到Next-key lock再请求获取Insert Intention lock时是冲突的,反过来就是兼容的?

    作者回复: 一样是不兼容的

    2019-09-15
  • 💢 星星💢
    老师你最后放的那张图,为啥主健索引还需要获取非主键索引的锁啊,主键索引不是已经持有这一整行数据了么?

    作者回复: 索引和数据是两个概念,我们说过,聚合索引的叶子节点会存放整个数据,而辅助索引的叶子节点存放的是主键id

    2019-09-10
  • Geek_844248
    老师,RC隔离级别下的select、update、delete基于聚簇索引和辅助索引分别获取什么lock?

    作者回复: 由于RC隔离级别只解决了脏读,所以是记录锁

    2019-08-31
  • 新世界
    for update和update都是悲观锁有什么区别?
    2019-08-18
  • K
    老师您好,麻烦问一下:“如果使用辅助索引来更新数据库,就需要使用聚簇索引来更新数据库...”这句话是什么意思啊?

    作者回复: 如果我们之前使用辅助索引来更新数据库,就需要修改为使用聚簇索引来更新数据库。

    2019-08-18
  • 星星滴蓝天
    我们有一个很悲催的经历,更新的时候没有使用主键更新,之前还好好的,后来(服务迁移、降低配置......处理流程变长)很悲剧的死锁了。

    作者回复: 过来人👍🏻

    2019-08-17
  • 阿琨
    老师,使用ON DUPLICATE KEY UPDATE这个是不是也可以解决这种呢

    作者回复: 可以

    2019-08-16
  • 奇奇
    mysql本身自带死锁检测
    超时时间在业务上是很难接受的

    作者回复: 是的

    2019-08-15
  • Fever
    兼容矩阵图里的Gap锁和横向的Insert Intention不兼容吧。。

    作者回复: 是的,跟竖向是一样的

    2019-08-13
收起评论
21
返回
顶部