秒殺場景簡介
雖然秒殺已經很廣泛了,可是出於文章的完整性,仍是簡單介紹一下秒殺的業務背景。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
相似的較果。