gorm的并发安全问题
1 | 最近负责一个小项目的后端,需要用到sql数据库, |
下面是gorm关于并发安全的叙述
https://gorm.io/zh_CN/docs/method_chaining.html
链式方法
1 | 链式方法是将 Clauses 修改或添加到当前 Statement 的方法,例如: |
1 | 终结(方法) 是会立即执行注册回调的方法,然后生成并执行 SQL,比如这些方法: |
新建会话方法特指
1 | gorm.DB.Session(&gorm.Session{}) |
链式方法一般都是返回当前类一个实例指针的方法,比如gorm源码中这两句:
1 | func (db *DB) Session(config *Session) *DB |
对 *DB 类型实例的调用返回了一个*DB指针
如果我要对 Session方法的返回值调用 Create方法,
可以这么写:
1 | d:=db.Session(balabalabala) |
但是既然这Session返回了一个*DB指针,那么可以这样调用
1 | db.Session(balabala).Create(balabala) |
这样写出的代码更优雅一些,但是
这不是本文章的重点,算是前置知识吧
gorm中并发安全的关键
gorm定义了一个 *DB 类型
这个类型里面有一个很重要的成员: DB.clone
1 | type DB struct { |
gorm中还有另一个函数,大多数的链式方法首先会执行下面这个函数
1 | func (db *DB) getInstance() *DB { |
我们把这个函数的逻辑简化一下:
clone < 0: 返回原来的 *DB
clone = 1: 新建一个 *DB 并且新的 *DB 的clone字段为0
clone > 1: 复制原来 *DB 并返回,那么新 *DB 的clone字段和原来相同,也会 > 1
我们每对 *DB 的方法进行一次调用都有可能对它 *Statement 字段进行修改,比如Limit方法修改了一次Statement( tx.Statement.AddClause(args) )
1 | func (db *DB) Limit(limit int) (tx *DB) { |
Where方法也会修改
1 | func (db *DB) Where(query interface{}, args ...interface{}) (tx *DB) { |
如果两次条件不同的查询都使用同一个 *DB,那么就会并发地对同一个Statement进行修改,就会出现安全问题,比如
1 | select * from table1 where id = 1 |
他们向同一个statement写入了不同查询条件,最终的结果几乎一定会出错。
因此问题的关键就在于, 对不同的查询要使用不同的 *DB,有一个很实用的方法 *DB.Session(), 如果传入的*gorm.Session{}结构体指针中NewDB字段为true,那么就会返回一个clone为1的新 *DB,对这个 *DB 的调用就不会影响到原来的 *DB
1 | newDB := oldDB.Session(&gorm.Session{NewDB: true}) |
或者诸位可以自己查看各个链式方法和终结方法的源码,确定该方法对老的 *DB 会进行什么操作,只要能够保证不同的sql操作条件作用在不同的 *DB 上就好了