作者:阿里云数据库资深技术专家胡炜
做电商业务的朋友一定不会对“热点” 这个数据库的使用场景感到陌生,大卖家、热点商品、热点库存等等都会遇到,即使在阿里这样电商业务相对非常成熟的体系中“热点”也困扰了业务非常久, 原因就在于“热点”是一个对于数据库业务不太友好的场景, 粗放的利用数据库来存取“热点”业务一定会遇到问题。
1、前言
在标准的关系型数据库中,由于需要确保事务的ACID, 因此不同的事务在并发更新同一行记录的时候需要完全的串行化。在乐观并发控制的实现中, 并发的多个更新事务只有一个能成功,其他的全部回滚;在悲观并发控制的实现中,每一个更新要等待前一个持有该行的事务提交或者回滚释放了行锁之后才可以进行更新。
常用的开源MySQL就是悲观的加锁并发控制实现。下图是一个典型的热点事务, 一共包含5条事务,其中Q3即是对热点行的更新。
经典热点事务场景
在加锁这样的悲观并发控制实现下,数据库理论上只要每时每刻都有一个事务处于更新这一行的过程,那么总的吞吐量能达到最优,吞吐量为:
但是随着并发请求的数量上升, 线程的上下文切换开销以及锁等待、唤醒的开销都会非常明显,因此在大并发下整体吞吐呈明显的下降趋势:
下面是简单的热点更新测试性能表现,即所有事务对同一行进行更新:
第一个朴素的想法是消灭锁等待, 因此在过往的AliSQL 以及 官方MySQL-8.0 中都提供了no_wait的功能, 这相当于将热点场景变成了乐观的并发控制, 当事务发现需要等锁的时候直接回滚。
另一个很朴素的优化思路是控制事务的并发请求量,首先可以通过线程池来限制住数据库内部的并发线程数,假设线程池内部总共有128个工作线程, 那么性能基本就能够稳定图2的128并发的吞吐量上。 再其次, 通过对同一行更新的事务进行排队,也可以减少InnoDB锁等待唤醒的开销。 这种优化可以让吞吐无限接近于1s/trxrt。
RDS三节点企业版提供了另一种思路的解决方案,也就是提供新的思路来达到增加“热点”场景吞吐的目的。
RDS企业三节点的针对热点的主要手段包括:
- 缩短事务持有锁的时间
- 组提交
2、缩短事务持有锁的时间
事务的持续时间包括数据库内核中各条SQL执行的时间以及客户端与数据库之间的网络交互时间,根据图1的经典热点事务场景模型, 一个热点事务包括5条SQL语句,以及9次客户端以及数据库之间的网络通信(最后commit 返回客户端前事务已经结束)。
3、select from update
Oracle 有returning语法来通过一条DML完成修改数据和返回数据的目的。三节点企业版支持select from update的语法, 支持将某条记录更新,同时返回查询结果, 具体用法如下:
有了这个特性, 我们很容易将图1中经典热点模型的Q3 和 Q4 进行合并,节约一次数据库内部执行Query的时间,并且节约两次客户端和数据库的网络通信开销。
4、Commit On Success / Rollback On Fail /Target Affect Row
一个非auto commit的连接如果需要提交一个事务,在通常情况下需要客户端额外给数据库发送一个commit请求, 为了减少这一次客户端和数据库的网络开销,RDS企业三节点提供了Commit On Success / Rollback On Fail / Target Affect Row 的hint 语法。
Commit on success / Rollback on fail 非常容易理解, 即热点行更新操作成功即自动commit事务, 如果更新失败则rollback整个事务。
这里如何定义成功/失败是一个需要理解的概念, 这里引入Target Affect Row的概念,即更新语句实际更新了几条数据, 如果Target Affect Row为1,则更新到了一条数据我们认为更新是成功, 如果更新的where条件没有命中任何记录则认为是失败,这样配合Commit on success / Rollback on fail就能够方便的实现业务的逻辑。需要注意的是这几个hint必须是伴随着热点update 语句使用的。
5、组提交热点事务
通过上一节的缩短事务持有锁的时间,我们能够缩短每个事务的运行时长,从而使得1s/trxrt 尽量增大, 同过这样的方式,我们能将单线程的吞吐增加到接近10000TPS/s的量级, 想让吞吐更进一步则非常困难。RDS企业三节点提供了另外一种组提交的方式,即对并发更新同一行的不同事务打包成一个group, 这个group对这行热点记录做一次合并更新, 这样就能让系统的吞吐再提升好几倍。
为了方便理解这个概念, 举个例子,假设现在同时有10个事务想要更新t1表上id = 3 的记录,这里假设每个事务的更新语句都是:
通过组提交的技术,RDS三节点企业版内核可以把这样的更新语句进行内部逻辑合并打包,实际上是将id为3这一行的b值更新为b-10.这样实际上对数据库的存储引擎就进行了一次更新就达到了顺序执行10个事务一样的效果,持有锁的时间可以大大缩短,并且对客户端的用户是透明的。产生的binlog也是和原来十个事务串行是一样的效果。启用组更新需要设置系统的变量:
组更新的效果可以通过全局的status来观测:
这里的Group_update_leader_count 代表着热点更新组的数量, Group_update_follower_count代表的是组里的事务数目, 两个值相除就能得到平均每组中事务的数目。这个值越大则组更新合并的效果越好。
6、总结
RDS企业三节点提供的热点更新功能能够将关系数据库在热点记录更新这一个场景的吞吐得到大规模提升, 也是在阿里巴巴内部的场景中有着非常广泛应用的特性。它能缩短每个事务持有锁的时间,也能打包一批事务做合并更新。
当然组更新有启用的限制, 即需要在更新语句中带上commit_on_success的 hint, 也要确定在where语句中带有主键的等值条件,这样的更新语句数据库才会识别为走热点组更新的语句,并按照组提交的逻辑进行处理,一个组的事务或者全部成功,或者全部回滚, 即如果组中有一个事务的用户发起断开连接回滚事务的操作, 那么这个组中的所有事务都会回滚。
总而言之如果用户对热点场景有诉求,并且需要数据库提供高成功率的吞吐保障,可以对SQL稍微加以设计使用RDS企业三节点的高级热点功能。