目录
一、实现方法:
通过对数据表增加一个数字类型的version字段来实现。读取记录时,将version字段一同读出,数据每更新一次,对version字段+1。当更新记录时,检查记录当前version是否与之前读取时的相同,只有相同才给予记录更新操作。
乐观锁不是数据库自带的,需要自己在代码中实现。乐观锁指更新数据库时,想法很乐观,认为这次操作不会导致冲突。在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新时,再去判断是否有冲突了。整体思想就是CAS思想。
从字面意思看,用于读多写少的场景。
二、伪SQL代码
SELECT count,version FROM product WHERE id = 10000;
UPDATE product SET count = count -1,version = version+1 WHERE id = 10000 AND version= oldversion;
三、go代码实现:
package main
import (
"fmt"
"log"
"sync"
"sync/atomic"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)
type Product struct {
ID int `gorm:"primaryKey"`
Count int
Version int
}
var DSN = "root:123@tcp(192.168.4.41:3306)/test?charset=utf8mb4"
var DB *gorm.DB
func init() {
var err error
DB, err = gorm.Open(mysql.Open(DSN), &gorm.Config{
AllowGlobalUpdate: true,
PrepareStmt: true,
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
})
if err != nil {
log.Fatalln(err)
}
DB.Exec("DROP TABLE IF EXISTS product")
DB.AutoMigrate(Product{})
DB.Create(Product{ID: 10000, Count: 10, Version: 0})
}
//减库存
func Decr(id int) bool {
for {
var product Product
DB.Debug().First(&product, id)
if product.ID == 0 || product.Count <= 0 {
return false
}
rf := DB.Debug().Exec("UPDATE product SET count = count -1,version = version +1 WHERE id = ? AND version = ?", id, product.Version).RowsAffected
if rf > 0 {
return true
}
}
return false
}
func main() {
var count int32
wg := sync.WaitGroup{}
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
if ret := Decr(10000); ret {
atomic.AddInt32(&count, 1)
}
}()
}
wg.Wait()
fmt.Println("成功更新记录数:", count)
}
运行结果:只能有10个协程更新成功!
数据库库存成功减到零。
四、优缺点:
1.优点:在读操作下,不需要频繁加锁解锁来消耗系统资源,比较轻量。
2.缺点:在写操作较多时,很多线程会不断循环来判断锁的状态,这给cpu带来很大的开销。所以乐观锁只适合读多写少的场景。