使用golang實(shí)現(xiàn)類(lèi)InnoDB數(shù)據(jù)行鎖效果

在關(guān)系型數(shù)據(jù)庫(kù)領(lǐng)域,為人津津樂(lè)道的一個(gè)特性,便是數(shù)據(jù)庫(kù)的鎖設(shè)計(jì)及事務(wù)隔離級(jí)別。
本文通過(guò)golang系統(tǒng)庫(kù)sync,來(lái)實(shí)現(xiàn)簡(jiǎn)單的數(shù)據(jù)庫(kù)數(shù)據(jù)讀寫(xiě)操作。

場(chǎng)景說(shuō)明

小明經(jīng)營(yíng)一家水果店,創(chuàng)業(yè)初始資金為100000元,所有的收入以及支出通過(guò)2個(gè)銀行賬戶進(jìn)行往來(lái)。
因交易頻繁,可能存在并發(fā)更新賬戶數(shù)據(jù)及查賬的需求,需要保障賬戶數(shù)據(jù)針對(duì)所有操作的一致性。
此處需要引入讀寫(xiě)鎖,保障讀寫(xiě)的安全性及高效性。

需求分析

在MySQL中,使用InnoDB存儲(chǔ)引擎,配合合適的事務(wù)隔離級(jí)別,可以做到數(shù)據(jù)行級(jí)鎖定,也就是:

操作類(lèi)型 查賬戶A 查賬戶B 寫(xiě)賬戶A 寫(xiě)賬戶B
查賬戶A 可并發(fā) 可并發(fā) 互斥 可并發(fā)
查賬戶B 可并發(fā) 可并發(fā) 可并發(fā) 互斥
寫(xiě)賬戶A 互斥 可并發(fā) 互斥 可并發(fā)
寫(xiě)賬戶B 可并發(fā) 互斥 可并發(fā) 互斥

賬戶A和賬戶B的讀寫(xiě)操作相互獨(dú)立,最大化賬戶的并發(fā)操作。
那么,如何使用golang實(shí)現(xiàn)簡(jiǎn)單表格中的場(chǎng)景呢?另外, 是否可以設(shè)置讀寫(xiě)的優(yōu)先級(jí)呢?
我們下面先來(lái)介紹下golang的兩個(gè)類(lèi):

  • sync.RWMutex
    讀寫(xiě)鎖,支持單寫(xiě)多讀特性。區(qū)別于sync.Mutex的全局互斥鎖特性(不支持同時(shí)讀)
  • sync.WaitGroup
    可通過(guò)Add方法,將請(qǐng)求分組,同一組的gorountine可通過(guò)Wait方法,控制組內(nèi)所有g(shù)orountine全部結(jié)束,才能繼續(xù)主線程,否則一直阻塞主線程。

如下代碼為實(shí)現(xiàn)樣例,假設(shè)當(dāng)前有如下數(shù)量請(qǐng)求并發(fā):
5個(gè)A賬戶讀,B賬號(hào)讀,3個(gè)A賬號(hào)寫(xiě),B賬戶寫(xiě)
其中A賬戶設(shè)置了低優(yōu)先級(jí)讀。

功能實(shí)現(xiàn)

package main

import (
    "fmt"
    "math/rand"
    "sync"
    "time"
)
// 賬戶的初始數(shù)據(jù)
var accountTypeMap = map[string]int{
    "A": 50000,
    "B": 50000,
}
// init方法,設(shè)置seed,用于控制隨機(jī)數(shù)生成的初始值,確保其隨機(jī)性
func init() {
    rand.Seed(time.Now().Unix())
}

func sleep() {
    time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
}

// 讀取賬號(hào)余額,低優(yōu)先級(jí)的讀,采取隨機(jī)sleep的方式,等待請(qǐng)求
func readAccount(accountType string, m *sync.RWMutex, wg *sync.WaitGroup, lowPriority ...string) {
    if (len(lowPriority)) > 0 {
        sleep()
    }
   // 使用讀鎖
    m.RLock()
    fmt.Println("time:", time.Now().UnixNano()/1e6, " read account ", accountType, " left money:", accountTypeMap[accountType])
    // sleep 10毫秒,方便確認(rèn)并發(fā)是否生效
    time.Sleep(time.Duration(10) * time.Millisecond)
    // 釋放讀鎖
    m.RUnlock()
    wg.Done()
}

// 寫(xiě)入賬號(hào),低優(yōu)先級(jí)的寫(xiě),采取隨機(jī)sleep的方式,等待請(qǐng)求
func writeAccount(accountType string, addMoney int, m *sync.RWMutex, wg *sync.WaitGroup, lowPriority ...string) {
    if (len(lowPriority)) > 0 {
        sleep()
    }
    // 使用寫(xiě)鎖(排他鎖)
    m.Lock()
    accountTypeMap[accountType] = accountTypeMap[accountType] + addMoney
    fmt.Println("time:", time.Now().UnixNano()/1e6, "modify account ", accountType, " add money:", addMoney)
    // sleep 10毫秒,方便確認(rèn)并發(fā)是否生效
    time.Sleep(time.Duration(10) * time.Millisecond)
    m.Unlock()
    wg.Done()
}

func main() {
    var mutexA sync.RWMutex
    var mutexB sync.RWMutex

    wg := sync.WaitGroup{}

    // 設(shè)置5個(gè)A賬戶讀,B賬號(hào)讀,3個(gè)A賬號(hào)寫(xiě),B賬戶寫(xiě)
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go readAccount("A", &mutexA, &wg, "lowpriority")
    }

    for i := 0; i < 5; i++ {
        wg.Add(1)
        go readAccount("B", &mutexB, &wg)
    }

    for i := 0; i < 3; i++ {
        wg.Add(1)
        go writeAccount("A", 1000, &mutexA, &wg)
    }

    for i := 0; i < 3; i++ {
        wg.Add(1)
        go writeAccount("B", 3000, &mutexB, &wg)
    }

    wg.Wait()

    fmt.Println("account A left: ", accountTypeMap["A"])
    fmt.Println("account B left: ", accountTypeMap["B"])
}

結(jié)果分析

返回的結(jié)果具體隨機(jī)性,其中A讀具有低優(yōu)先級(jí),返回在最后。以下為一種結(jié)果:

time: 1565345040128 modify account  B  add money: 3000
time: 1565345040128 modify account  A  add money: 1000
time: 1565345040139  read account  B  left money: 53000
time: 1565345040139  read account  B  left money: 53000
time: 1565345040139 modify account  A  add money: 1000
time: 1565345040139  read account  B  left money: 53000
time: 1565345040139  read account  B  left money: 53000
time: 1565345040139  read account  B  left money: 53000
time: 1565345040149 modify account  A  add money: 1000
time: 1565345040149 modify account  B  add money: 3000
time: 1565345040160 modify account  B  add money: 3000
time: 1565345040493  read account  A  left money: 53000
time: 1565345040693  read account  A  left money: 53000
time: 1565345040693  read account  A  left money: 53000
time: 1565345040828  read account  A  left money: 53000
time: 1565345041091  read account  A  left money: 53000
account A left:  53000
account B left:  59000

從前面2行的返回結(jié)果看,寫(xiě)賬戶A和寫(xiě)賬戶B 可并發(fā)操作,
從第3,4,5 行看,讀賬戶B和寫(xiě)賬戶可并發(fā)操作,滿足前面表格的場(chǎng)景。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容