前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >高并发场景下的数据库事务调优

高并发场景下的数据库事务调优

作者头像
故里
发布2020-11-25 16:05:04
1K0
发布2020-11-25 16:05:04
举报
文章被收录于专栏:故里学Java故里学Java

数据库事务是访问可能操作各种数据项的一个数据库操作序列,这些操作要么全部成功,要么全部失败。提起事务,大家都知道ACID属性,这些特性在前边的文章里都有详细的讲解,感兴趣的可以通过历史文章查看。在Java中有并发编程,可以多线程并发执行,并发可以提高程序执行的效率,也会带来线程安全的。数据库事务和多线程一样,为了提高数据库处理事务的吞吐量,数据库也支持并发事务,在并发处理数据的过程中,也存在着安全问题。

我们本文将从并发事务可能引发的问题、解决并发问题、MySQL的锁机制、锁的实现等方面逐渐深入,探讨高并发场景下的事务调优问题。

并发事务可能引发的问题

1.数据丢失
2.脏读、
3.幻读
4.不可重复读

事务隔离解决的并发问题

数据丢失可以基于数据库中的悲观锁来避免发生,即在查询时通过在事务中使用 select xx for update 语句来实现一个排他锁,保证在该事务结束之前其他事务无法更新该数据。 我们也可以基于乐观锁来避免,即将某一字段作为版本号,如果更新时的版本号跟之前的版本一致,则更新,否则更新失败。剩下3 个问题,其实是数据库读一致性造成的,需要数据库提供一定的事务隔离机制来解决。

MySQL 的锁机制

InnoDB实现了两种类型的锁机制:共享锁(S)和排他锁(X)。共享锁允许一个事务读数据,不允许修改数据,如果其他事务要再对该行加锁,只能加共享锁;排他锁是修改数据时加的锁,可以读取和修改数据,一旦一个事务对该行数据加锁,其他事务将不能再对该数据加任务锁。

不同的锁机制会产生不同的事务隔离级别,不同的隔离级别分别可以解决并发事务产生的问题,如读未提交、读已提交、可重复读、可序列化等。(1号发的《MySQL的事务隔离级别和长事务,看这一篇就够了》一文中有介绍过)

InnoDB中的读已提交和可重复读隔离事务是基于多版本并发控制(MVCC)实现高性能事务。一旦数据被加上排他锁,其他的事务将无法加入共享锁,且处于阻塞等待状态,如果一张表有大量的请求,这样的性能将是无法支持的。

MVCC对普通的Select 不加锁,如果读取的数据正在执行delete或者update操作,这时读取操作不会等待排他锁的释放,而是直接利用MVCC读取该行的数据快照。MVCC避免了对数据重复加锁的过程,大大提高了毒草在的性能。(数据快照是指在该行的之前版本的数据,而数据快照的版本是基于undo实现的,undo是用来做事务回滚的,记录了回滚的不同版本的行记录)

锁的具体实现算法

InnoDB既实现了行锁,也实现了表锁,行锁是通过索引实现的,如果不通过索引条件检索数据,那么InnoDB将表中所有的记录进行加锁,其实就是升级为表锁。

行锁的具体实现算法有三种:record lock、gap lock和next-key lock。record lock是专门对索引项加锁;gap lock是对索引项之间的间隙加锁,next-key lock则是前面两种的组合,对索引项及其之间的间隙加锁。

只在可重复读或以上隔离级别下的特定操作才会取得 gap lock 或 next-key lock,在 Select 、Update 和 Delete 时,除了基于唯一索引的查询之外,其他索引查询时都会获取 gap lock 或 next-key lock,即锁住其扫描的范围。

优化高并发事务

上边的讲解,都是为了对事务、锁和隔离级别更加深入了解,下边将聊聊高并发场景下的事务是如何调优的。

  1. 结合业务场景,使用低级别事务隔离

在高并发业务中,为了保证业务数据的一致性,操作数据库时往往会使用不同级别的事务隔离,隔离等级越高,并发性能就越低。

那在实际的业务中,我们要如何选择呢,下边举两个例子:

在修改用户的最后登录时间,或者用户的个人资料等数据时,这些数据都只有用户自己登录和登陆后才会修改,不存在一个事务提交的信息被覆盖的可能,所以这样的业务我们就最低的隔离级别。

如果账户的余额或者积分的消费,就可能存在多个客户端同时消费一个账户的情况,此时我们应该选择可重复读隔离级别,来保证当一个客户端在操作的时候,其他客户端不能对该数据进行操作。

  1. 避免行锁升级表锁

我们知道,InnoDB中行锁是通过索引实现的,当不通过索引条件检索数据时,行锁就会升级成表锁,我们知道表锁会严重影响我们对整张表的操作,应该避免这种情况。

  1. 控制事务的大小,减少锁定的资源和锁定的时间

下边这个SQL异常相比很多并发比较高的系统里都会遇见,比如抢购系统的日志中:

代码语言:javascript
复制
MySQLQueryInterruptedException: Query execution was interrupted

由于抢购系统中,提交订单业务开启了事务,在并发环境中对一条记录进行更新操作的情况下,由于更新记录所在的事务还可能存在其他操作,导致一个事务比较长,当大量请求进入时,就可能导致一些请求同时进入事务中,由于锁的竞争是不公平的,当多个事务同时对一条记录进行更新时,极端情况下,一个更新操作进去排队系统后,可能会一直拿不到锁,最后因超市被系统中断,就会抛出上边这个异常。

提交订单需要创建订单和扣减库存,两种不同顺序的执行方式,结果都一样,但是性能确实不一样的:

这两种不同的执行方式,虽然这些操作都在一个事务中,但是锁的申请不在同一时间,锁只有当其他操作都执行完成才会释放锁。扣减库存是更新操作,属于行锁,如果先扣减库存会影响到其他操作该数据的事务,所以我们应该尽可能的避免长时间持有该锁,尽快的释放锁。

因为创建订单和扣除库存不管先执行哪一步都不影响业务,所以我们可以先执行新增操作,把扣除库存放到最后,也就是使用执行顺序1 ,来减少锁的持有时间。

总结

MySQL 的并发事务调优和 Java 的多线程编程调优非常类似,都是可以通过减小锁粒度和减少锁的持有时间进行调优。在 MySQL 的并发事务调优中,我们尽量在可以使用低事务隔离级别的业务场景中,避免使用高事务隔离级别。

在功能业务开发时,我们往往会为了追求开发速度,习惯使用默认的参数设置来实现业务功能。例如,在 service 方法中,你可能习惯默认使用 transaction,很少再手动变更事务隔离级别。但要知道,transaction 默认是 RR 事务隔离级别,在某些业务场景下,可能并不合适。因此,我们还是要结合具体的业务场景,进行考虑。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-11-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 故里学Java 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 并发事务可能引发的问题
    • 1.数据丢失
      • 2.脏读、
        • 3.幻读
          • 4.不可重复读
          • 事务隔离解决的并发问题
          • MySQL 的锁机制
          • 锁的具体实现算法
          • 优化高并发事务
          • 总结
          相关产品与服务
          云数据库 SQL Server
          腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档