作者:林冠巨集 / 指尖下的幽靈
GitHub : github.com/af913337456…
前序
距離上次擇文發表,兩月餘久。2018年也即將要結束了,目前的工作依然是與區塊鏈應用
相關的,也很榮幸在9月初受邀簽約出版暫名為《區塊鏈以太坊DApp實戰開發》
一書,預計在明年年初出版。
這次讓我有感記錄這篇文章的原因是最近在使用Go
語言重寫一個原來由PHP
語言編寫的交易所訂單撮合模組
的時候,發現訂單撮合
的部分程式碼
在撮合的時候,為保證各表資料在併發情況下不出現讀寫髒亂而採用了全域性鎖表
的操作。後面我採用了共享鎖
的形式進行了修改,於剛剛重寫完,並進行了併發單元測試
,表現正常。
目錄
- 場景描述
- 解決問題
- 訂單撮合例項
- 共享鎖 與 排他鎖
- 前置知識
- 行鎖與表鎖
- 兩種行鎖的特點
- 兩種行鎖的加鎖方式
- 鎖的釋放
- 操作例子
- 改造程式碼片段
場景描述
高併發的業務常見是有很多種類的,最常見的例如秒殺搶購
。它們都有一個共同的特點就是資料更新都比較頻繁,通常涉及到多張業務表的增改
操作,且表格越多的,要考慮的問題也越多。
訂單撮合可以理解為訂單買賣,拿這個為例子進行列舉一個可能會導致資料錯亂的情形。假設現在買賣手機,A使用者是要買手機的,B使用者是賣手機的。A的買入單
訂單1,和B的賣出單
訂單2,訂單2賣出手機,一臺手機賣1000元。此時A的網上的錢包餘額是1001元,剛好比手機價格高,是可以成交的。
此時記錄使用者錢包錢數數量的是一張資料表。每次花費了錢或者增加了錢,都要更新這個表。
當這兩筆訂單進入到系統裡面進行撮合。假設系統的訂單撮合執行流程如下圖所示:
當判斷條件進行A使用者的錢包餘額判斷的時候,發現 1001 > 1000,結果是通過,此時準備進入“進行記錄更細”步驟。但是,就在這個過程之中的時間差中,A使用者使用了系統的網上提現功能,併成功轉出了10元,剩餘的是1001 - 10 = 991
元。但是由於撮合系統的餘額判斷過程以及通過了,導致下面的交易流程依然能進行,最終A用991元買了B的1000元售價的手機。
解決問題
上述的常見問題是一個很簡單的模型,現實的系統中往往是更復雜的。但是它所體現出的問題卻是真實存在的,對於這類問題,有很多解決方案。其中,就可以考慮使用資料庫的鎖。
本文要介紹的是MySQL資料庫
的共享鎖
與 排他鎖
,其它的不作說明或引申。
訂單撮合例項
下面的截圖就是我所重寫好的撮合系統原始的PHP
程式碼,所使用了表鎖
的方式來解決前面的併發讀寫導致資料髒亂的問題。這種方式雖然是解決了問題,但是導致了效能低下
的問題。
共享鎖 與 排他鎖
前置知識:
- MySQL 是資料庫,不是
資料庫引擎
- MySQL有兩種常用儲存引擎:
MyISAM
和InnoDB
MyISAM
不支援事務操作,InnoDB
支援事務操作- MySQL 的鎖分有
行鎖
和表鎖
- MyISAM 只有表鎖
- Innodb 行鎖,表鎖都有
- 行鎖中有
共享鎖
和排他鎖
共享鎖
簡稱 S鎖,排他鎖
簡稱 X鎖
行鎖與表鎖
簡述:
-
行鎖,鎖的是表中對應的行,只限制當前行的讀寫。
-
表鎖,鎖的是整張表,限制的是整張表的資料讀寫。
比較:
- 行鎖,計算機資源開銷大,加鎖慢;會出現
死鎖
;鎖定粒度最小,鎖衝突的概率最低,併發度
最高,效能高。 - 表鎖,計算機資源開銷小,加鎖快;不會出現
死鎖
;鎖定粒度大,鎖衝突的概率最高,併發度
最低,效能低。
兩種行鎖的特點
共享鎖
A 對資料 B 加了 共享鎖,A能讀取和修改資料B,C 等其它只
能讀取
資料B,但是不能修改
。直至A釋放了B的鎖。
排他鎖
A 對資料 B 加了 排他鎖,A能讀取和修改資料B,C 等其它不能再對資料B加其它的鎖。直觀體驗是不能修改,不能使用含有加鎖動作的
select
讀取。
兩種行鎖的加鎖方式
要注意的是:
- 行鎖的實現SQL語句中必須要有索引的限制條件,例如含有
where id=xxx
這類語句。 - 行鎖的實現SQL語句沒有索引限制條件會變成
表鎖
InnoDB引擎
預設的修改資料
類SQL語句,update
,delete
,insert
等,都會自動給涉及到的資料加上排他鎖。
共享鎖
- select 的新增可以使用滿足格式:
select ... where 索引限制 lock in share mode
的語句。例如“select name from lgh_user where id = 1 lock in share model” 此時 id 是索引。
排他鎖
- 滿足格式:
select ... where 索引限制 for update
的語句
鎖的釋放
-
非事務(Transaction) 中,語句執行完畢,便釋放鎖。
-
行鎖在事務 (Transaction) 中,只有等到當前的事務Transaction 進行了 commit 或 roll back,鎖才能釋放。
操作例子
演示事務 tx 中的例子,文字解析見圖。
改造程式碼片段
撮合中的所有表鎖
替換成了共享鎖
,執行其它業務讀取所鎖的行資料,在當前事務的批量操作還沒結束之前,不允許修改。