OneSQL :: 热点事务提交优化,不再等客户端客外指令,让热点商品出库如丝般顺滑。

在许多人的印象中,觉得只有NoSQL才是高性能的,于是一切和秒杀有关的数据都被放置在缓存中,包括库存的余额。这样做性能上的确有优势,但也付出了惨痛的代价,2012年双十一大促之后,所有的商家都在忙碌地盘点着库存,应当卖掉的没有卖掉,不应当卖那么多的偏偏卖了那么多,不能按时发货的赔偿金的数量引起的大家的关注,这就是使用无事务机制的NoSQL的真实故事,余下的货让其他网站大发了。缓存的确很好用,但最核心的东西却偏偏不能相信缓存,那怕是持久化的,关键的业务场景依旧是关系数据库的天下。

于是大家展开了对解决方案的讨论,一堆科学家、研究员开始互相讨论,像我这样的资深专家也只是作为一个小兵参与进去,也是从测试开始的。从Oracle测试到MySQL,从跨机房测试到同机房测试,再到同一台机器的测试,又对比测试了一下去掉事务的情况。最后发现只有应用和数据库布署在同一台机器上或者禁掉事务功能测出来的TPS效果才是最好的。于是开始思考其本质区别,直到有一天发现Oracle OCI编程接口中执行SQL时,可以设置一个标志位“COMMIT ON SUCCESS”,忽然灵光一闪想通了现有应用中事务处理太慢本质原因。

从业务抽象的角度上来讲,我们面临着如何更快地更新同一条记录的技术问题。下面是一个经典的事务处理逻辑:

start transaction;
insert into inv_operation values (......);
update inv_balance set deposit = deposit - 1
    where inv_id = 1;
commit;

为什么应用和数据库在一起时的本机测试会快?为什么禁掉事务的情况下会快?为们机器的内存足够大,完全能够放下最近一天的“inv_operation”表的数据和全部的“inv_balance”表的数据,没有IO的问题,CPU利用率也很低。在单线程测试中,发现简单的Update语句只需要耗时100微秒。为什么并发情况下会慢呢?冷静一下后,就很容易分析出来原因了,Update虽然快,但只有收到客户端的“COMMIT”指令后,其他的会话才能执行。而Update和Commit之间有多少距离呢?本机执行时,基本上是不需要时间的,同一个机房时大约是0.2毫秒,而跨机房时一般是0.6毫秒,如果运气不好撞上JVM GC,则有可能在Update和Commit之间花费数十或上百毫秒。

于是开始研究MySQL的客户端编程接口中,是否有“COMMIT ON SUCCESS”标记,结果是没有找到,于是就想着扩展一下SQL的语法了,可将“COMMIT”指令放到事务的最后一个SQL语句中,只要最后一个SQL执行没有出错就直接执行“COMMIT”指令,再响应给客户端,于是Update和Commit之间的距离就永远是0了。如下所示:

start transaction;
insert into inv_operation values (......);
update [auto_commit] inv_balance 
    set deposit = deposit - 1 where inv_id = 1;
commit;

事情往往没这么简单,在“Commit”命令之前可能会有一些应用逻辑的判断,于是又一次扫描应用代码,看看通常情况下需要做一些什么判断。最后发现,只需要增加一个更新记录数的判断就可以了。因为业务代码中真实的SQL可能是如下所示:

start transaction;
insert into inv_operation values (......);
update [auto_commit] inv_balance 
    set deposit = deposit - 1 
    where inv_id = 1 and deposit > 0;
commit;

由于Where条件中带了余额大于0的判断,如果更新的记录条数为0,从业务角度来讲应当更妥成失败,整个事务不能够进行提交,所以进一步扩展了SQL的语法,如下所示:

start transaction;
insert into inv_operation values (......);
update [affect_rows 1 auto_commit auto_rollback]
    inv_balance  set deposit = deposit - 1 
    where inv_id = 1 and deposit > 0;
commit;

除SQL语句本身执行成功外,还要检查更新的记录数,如果两个条件都满足则自动执行“COMMIT”,反之则自动执行“ROLLBACK”命令,以尽快释放锁资源,为大量并发开路。现在我们可以来做一个单行更新的性能对比了,我的测试是在一台SATA盘的机器上测试的,绝对值不够高,但相对的提升比例可以做一个参考。

onesql_fast_commit

简简单单地换上OneSQL版本,并且在比较忙的接口上修改几个SQL语句,单行记录(热点商品)的处理能力就可以得到轻松地提升了,实现这个功能补丁的代码好象总计不到200行。