OneSQL :: 什么是热点商品队列?根据商品的ID进行事务排队,减少死锁检测代价!

感觉上在一台机器上增加更多的CPU后应当可以得到线性增长,事实上并非这样。并发连接数还是会超过CPU核的数量,线程会在不同的CPU之间切来切去,并且需要访问和串行更新同一块内存。就算同一个内存地址的访问能并行化,一切就OK了吗?试想一下在eBay或淘宝上看到半价iPhone的优惠时会发生什么?

iphone6_hot_bidding

假设有许多许多的人对这个特惠商器感兴趣,并且他们在积极地出价竞拍或购买,如何来精确地处理库存的余额?如何来精确地处理卖家账号的钱?对于关系型数据库(比如MySQL、Oracle),为了保证数据的准确性,所有更新同一行的操作是串行地处理的,这个CPU及IO的速度都没有关系。如果有上千个连接同时开启了一个事务要更新同一个表的同一行记录(同一商品或同一账号),在MySQL里会发生什么?MySQL的InnoDB层针对每一个事务都会进行可能的死锁检测(Oracle也会,只是相对高效一点点),这是一个非常消耗CPU的事情(可以检查”lock0lock.cc”文件中的代码),可以用光一台机器的所有CPU,无率你用哪种数据库都(Oracle、DB2、SQL Server)不会表现太好,并且检测完了后只有一个会话能通过,实在是件坏事情。

但从业务角度来讲是好事,创造了营销的效果,技术上应当支持它,问题是从系统的角度来讲如何解决这个问题?焦点在于如何防止过多的更新同一行的事务进入到InnoDB层,在应用层或MySQL的Server层进行排队,就象我们见惯了的排队买票、排队进站的现象一样,否则就被堵死了。

ticket_queue_in_life

只要我们能根据商品ID或账号在进入InnoDB层以前进行排队(可以配置最大的并发度),事情就会好很多,可以选对在应用层排队,也可以选择在MySQL Server层排队,各有优势。当应用布署量较少时,应用层排队效果会不错,但当应用布署量非常大时,应用层排队效果打折。而MySQL Server层排队则需要对源代码进行定制,只有少数企业才有这个技术实力。

相对于InnoDB来讲,应用层的并发控制通常不够精确,因此最好的方法是在MySQL层(进入事务引挚之前)进行排队。OneSQL增强版本里就实现了这个功能,称之为热点商品队列,可以让所有用户方便地用到这个高级排队功能。

如果有大量的会话在更新同一行,我们可以使用注释告诉OneSQL此次SQL操作的商品号或账号,如下所示:

update [trx_queue :iphone_id] t_inv_deposit
set deposit_balance = deposit_balance - 1
where deposit_id = :iphone_id;

如查是一个复杂的多语句事务,则在事务的第一个SQL里使用注释告诉OneSQL此次SQL操作的商品号或账号,如下所示:

start transaction;
update [trx_queue :iphone_id] t_inv_deposit
    set deposit_balance = deposit_balance - 1
    where deposit_id = :iphone_id;
......
commit;

并不需要在所有的事务中加上热点标识,只需要在热点比较集中的事务中使用,需要更改的SQL数量并不多。然后使用如下参数调整同一个商品上可以同时操作的最大并发度。

mysql> show variables like 'tcc_max_sub%';
+-------------------------------------+-------+
| Variable_name                       | Value |
+-------------------------------------+-------+
| tcc_max_sub_query_concurrency       | 4     |
| tcc_max_sub_transaction_concurrency | 3     |
+-------------------------------------+-------+
2 rows in set (0.00 sec)

对于查询语句也同样有效,假如你的合作伙伴,正在查询和他们有关的交易记录,只要业务量稍不平均,较大的合作伙伴完全可能将整台机器的资源用尽。下面我们来做一个性能压测,下面是测试用例(一半的会话会执行第一个SQL,另外一半的会话执行另一个SQL):

option
  user test/test@172.30.12.4:3306:test
  log  /dev/null
  time 1m
  groups 2
declare
  vid1 int 1 500
  vid2 int 501 1000
begin
  group 1 update test_table 
           set col2=col2 - 1 where id = 10;
  group -1 update test_table 
           set col2=col2 - 1 where id = :vid1;
end

当我们开启1000个并发进行压测时,有500个并发会话会去更新同一行,另外500个会话会进行随机更新,线程池虽然有利于性能提升,但OneSQL的热点商品队列可以将性能发挥到极致,下图是测试结果:

onesql_hot_item_queue_testing

所需要做的事情只是换上OneSQL版本,并在几个SQL语句中增加一个注释,并不需要大的应用代码变动或重构,效果却是非常明显的。