秒杀场景简介
虽然秒杀已经很广泛了,可是出于文章的完整性,仍是简单介绍一下秒杀的业务背景。sql
例如,Iphone的1元秒杀,若是我只放出1台Iphone,咱们把它当作一条记录,秒杀开始后,谁先抢到(更新这条记录的锁),谁就算秒杀成功。数据库
对数据库来讲,秒杀瓶颈在于并发的对同一条记录的屡次更新请求,只有一个或者少许请求是成功的,其余请求是以失败或更新不到记录而了结。并发
例若有100台IPHONE参与秒杀,并发来抢的用户有100万,对于数据库来讲,最小粒度的为行锁,当有一个用户在更新这条记录时,其余的999999个用户是在等待中度过的,以此类推。优化
除了那100个幸运儿,其余的用户的等待都是无谓的,甚至它们不该该到数据库中来浪费资源。ui
传统的作法,使用一个标记位来表示这条记录是否已经被更新,或者记录更新的次数(几台Iphone)。spa
update tbl set xxx=xxx,upd_cnt=upd_cnt+1 where id=pk and upd_cnt+1<=5; -- 假设能够秒杀5台
这种方法的弊端:设计
得到锁的用户在处理这条记录时,可能成功,也可能失败,或者可能须要很长时间,(例如数据库响应慢)在它结束事务前,其余会话只能等着。code
等待是很是不科学的,由于对于没有得到锁的用户,等待是在浪费时间。事务
经常使用的秒杀优化手段
1. 通常的优化处理方法是先使用for update nowait的方式来避免等待,即若是没法便可得到锁,那么就不等待。资源
begin;
select 1 from tbl where id=pk for update nowait; -- 若是用户没法即刻得到锁,则返回错误。从而这个事务回滚。
update tbl set xxx=xxx,upd_cnt=upd_cnt+1 where id=pk and upd_cnt+1<=5;
end;
这种方法能够减小用户的等待时间,由于没法即刻得到锁后就直接返回了。
第二种方案
秒杀场景的核心问题是在更新热点商品的库存后到commit
之间即便有1~2ms
延迟就大大下降了并发程度,因此将热点数据放在事务最后一条更新并进行自动提交事务可大大提升事务的吞吐量。
建表SQL:
create table item_order ( id bigint not null, item_id bigint not null, order_id bigint not null, order_count int not null ); create table item ( id bigint not null, count int not null );
产生一个订购以下:
item_id: 123 order_id: 456 order_count: 1
事务处理:
start transaction insert into item_order (NEXT_ID, 123, 456, 1); // 插入一个名细,不阻赛事务,用于跨库事务和对帐 update /*+ [auto_commit affect_rows 1] */ item set count=count-1 where count >= 1 and id = 123; // 减库存 // 可省略的 commit
另外一种设计方法,不须要修改定制数据库,建表SQL:
create table item ( id bigint not null, count int not null, last_order_id bigint not null, last_order_count int not null );
产生的订单和上面的同样,这时候生成的事务处理以下:
// 注意这里不起事务,直接入库 update item set count = count - 1, order_id = 456, last_order_id = 456, last_order_count = 1 where count >= 1 and id = 123; // 减库存,同时写入名细
根据update
返回的记录数就能够判断减库存是否成功。剩下的问题是如何取得以前item_order
中的明细数据呢?
答案是抓取数据库的binlog
,达到以前item_order
相似的较果。