MySQL 在高併發下的 訂單撮合 系統使用 共享鎖 與 排他鎖 保證資料一致性

林冠巨集_指尖下的幽靈發表於2018-11-23

作者:林冠巨集 / 指尖下的幽靈

掘金:juejin.im/user/587f0d…

部落格:www.cnblogs.com/linguanh/

GitHub : github.com/af913337456…

騰訊雲專欄: cloud.tencent.com/developer/u…

蟲洞區塊鏈專欄:www.chongdongshequ.com/article/153…


前序

距離上次擇文發表,兩月餘久。2018年也即將要結束了,目前的工作依然是與區塊鏈應用相關的,也很榮幸在9月初受邀簽約出版暫名為《區塊鏈以太坊DApp實戰開發》一書,預計在明年年初出版。

這次讓我有感記錄這篇文章的原因是最近在使用Go語言重寫一個原來由PHP語言編寫的交易所訂單撮合模組的時候,發現訂單撮合的部分程式碼在撮合的時候,為保證各表資料在併發情況下不出現讀寫髒亂而採用了全域性鎖表的操作。後面我採用了共享鎖的形式進行了修改,於剛剛重寫完,並進行了併發單元測試,表現正常。

目錄

  • 場景描述
  • 解決問題
  • 訂單撮合例項
  • 共享鎖 與 排他鎖
    • 前置知識
    • 行鎖與表鎖
    • 兩種行鎖的特點
    • 兩種行鎖的加鎖方式
    • 鎖的釋放
    • 操作例子
  • 改造程式碼片段

場景描述

高併發的業務常見是有很多種類的,最常見的例如秒殺搶購。它們都有一個共同的特點就是資料更新都比較頻繁,通常涉及到多張業務表的增改操作,且表格越多的,要考慮的問題也越多。

訂單撮合可以理解為訂單買賣,拿這個為例子進行列舉一個可能會導致資料錯亂的情形。假設現在買賣手機,A使用者是要買手機的,B使用者是賣手機的。A的買入單訂單1,和B的賣出單訂單2,訂單2賣出手機,一臺手機賣1000元。此時A的網上的錢包餘額是1001元,剛好比手機價格高,是可以成交的。

此時記錄使用者錢包錢數數量的是一張資料表。每次花費了錢或者增加了錢,都要更新這個表。

當這兩筆訂單進入到系統裡面進行撮合。假設系統的訂單撮合執行流程如下圖所示:

MySQL 在高併發下的 訂單撮合 系統使用 共享鎖 與 排他鎖 保證資料一致性

當判斷條件進行A使用者的錢包餘額判斷的時候,發現 1001 > 1000,結果是通過,此時準備進入“進行記錄更細”步驟。但是,就在這個過程之中的時間差中,A使用者使用了系統的網上提現功能,併成功轉出了10元,剩餘的是1001 - 10 = 991元。但是由於撮合系統的餘額判斷過程以及通過了,導致下面的交易流程依然能進行,最終A用991元買了B的1000元售價的手機。

解決問題

上述的常見問題是一個很簡單的模型,現實的系統中往往是更復雜的。但是它所體現出的問題卻是真實存在的,對於這類問題,有很多解決方案。其中,就可以考慮使用資料庫的鎖。

本文要介紹的是MySQL資料庫共享鎖排他鎖,其它的不作說明或引申。

訂單撮合例項

下面的截圖就是我所重寫好的撮合系統原始的PHP程式碼,所使用了表鎖的方式來解決前面的併發讀寫導致資料髒亂的問題。這種方式雖然是解決了問題,但是導致了效能低下的問題。

MySQL 在高併發下的 訂單撮合 系統使用 共享鎖 與 排他鎖 保證資料一致性

共享鎖 與 排他鎖

前置知識:

  • MySQL 是資料庫,不是資料庫引擎
  • MySQL有兩種常用儲存引擎: MyISAMInnoDB
  • 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 中的例子,文字解析見圖。

MySQL 在高併發下的 訂單撮合 系統使用 共享鎖 與 排他鎖 保證資料一致性

MySQL 在高併發下的 訂單撮合 系統使用 共享鎖 與 排他鎖 保證資料一致性

MySQL 在高併發下的 訂單撮合 系統使用 共享鎖 與 排他鎖 保證資料一致性

MySQL 在高併發下的 訂單撮合 系統使用 共享鎖 與 排他鎖 保證資料一致性

MySQL 在高併發下的 訂單撮合 系統使用 共享鎖 與 排他鎖 保證資料一致性

MySQL 在高併發下的 訂單撮合 系統使用 共享鎖 與 排他鎖 保證資料一致性

MySQL 在高併發下的 訂單撮合 系統使用 共享鎖 與 排他鎖 保證資料一致性

MySQL 在高併發下的 訂單撮合 系統使用 共享鎖 與 排他鎖 保證資料一致性

改造程式碼片段

撮合中的所有表鎖替換成了共享鎖,執行其它業務讀取所鎖的行資料,在當前事務的批量操作還沒結束之前,不允許修改。

MySQL 在高併發下的 訂單撮合 系統使用 共享鎖 與 排他鎖 保證資料一致性

相關文章