使用golang实现类InnoDB数据行锁效果

道闻 · · 1373 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

在关系型数据库领域,为人津津乐道的一个特性,便是数据库的锁设计及事务隔离级别。
本文通过golang系统库sync,来实现简单的数据库数据读写操作。

场景说明

小明经营一家水果店,创业初始资金为100000元,所有的收入以及支出通过2个银行账户进行往来。
因交易频繁,可能存在并发更新账户数据及查账的需求,需要保障账户数据针对所有操作的一致性。
此处需要引入读写锁,保障读写的安全性及高效性。

需求分析

在MySQL中,使用InnoDB存储引擎,配合合适的事务隔离级别,可以做到数据行级锁定,也就是:

操作类型 查账户A 查账户B 写账户A 写账户B
查账户A 可并发 可并发 互斥 可并发
查账户B 可并发 可并发 可并发 互斥
写账户A 互斥 可并发 互斥 可并发
写账户B 可并发 互斥 可并发 互斥

账户A和账户B的读写操作相互独立,最大化账户的并发操作。
那么,如何使用golang实现简单表格中的场景呢?另外, 是否可以设置读写的优先级呢?
我们下面先来介绍下golang的两个类:

  • sync.RWMutex
    读写锁,支持单写多读特性。区别于sync.Mutex的全局互斥锁特性(不支持同时读)
  • sync.WaitGroup
    可通过Add方法,将请求分组,同一组的gorountine可通过Wait方法,控制组内全部结束,才能完成gorountine,否则一直阻塞主线程。

如下代码为实现样例,假设当前有如下数量请求并发:
5个A账户读,B账号读,3个A账号写,B账户写
其中A账户设置了低优先级读。

功能实现

package main

import (
    "fmt"
    "math/rand"
    "sync"
    "time"
)
// 账户的初始数据
var accountTypeMap = map[string]int{
    "A": 50000,
    "B": 50000,
}
// init方法,设置seed,用于控制随机数生成的初始值,确保其随机性
func init() {
    rand.Seed(time.Now().Unix())
}

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

// 读取账号余额,低优先级的读,采取随机sleep的方式,等待请求
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毫秒,方便确认并发是否生效
    time.Sleep(time.Duration(10) * time.Millisecond)
    // 释放读锁
    m.RUnlock()
    wg.Done()
}

// 写入账号,低优先级的写,采取随机sleep的方式,等待请求
func writeAccount(accountType string, addMoney int, m *sync.RWMutex, wg *sync.WaitGroup, lowPriority ...string) {
    if (len(lowPriority)) > 0 {
        sleep()
    }
    // 使用写锁(排他锁)
    m.Lock()
    accountTypeMap[accountType] = accountTypeMap[accountType] + addMoney
    fmt.Println("time:", time.Now().UnixNano()/1e6, "modify account ", accountType, " add money:", addMoney)
    // sleep 10毫秒,方便确认并发是否生效
    time.Sleep(time.Duration(10) * time.Millisecond)
    m.Unlock()
    wg.Done()
}

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

    wg := sync.WaitGroup{}

    // 设置5个A账户读,B账号读,3个A账号写,B账户写
    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"])
}

结果分析

返回的结果具体随机性,其中A读具有低优先级,返回在最后。以下为一种结果:

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行的返回结果看,写账户A和写账户B 可并发操作,
从第3,4,5 行看,读账户B和写账户可并发操作,满足前面表格的场景。


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:道闻

查看原文:使用golang实现类InnoDB数据行锁效果

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1373 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传