2
Gorm's official documentation provides examples of how to use chained calls correctly and counter-examples that make coroutines unsafe. Knowing how to use them correctly, you must also know the principles to use them more safely. The following is an example of the document and the source code to analyze how Gorm ensures the safety of the coroutine when it is called in a chain?

Source code analysis

The following is a common use code of gorm, first initialize the connection, and then add, delete and modify according to the chain call.

 db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
    panic("failed to connect database")
}

tx := db.Where("age = 22").Where("name = '小明'").Find(&user)

Initialize the connection through gorm.Open(), you will get a *gorm.DB structure pointer db. The clone property of the structure is 1 .

 type DB struct {
    *Config
    Error        error
    RowsAffected int64
    Statement    *Statement
    clone        int
}

func Open(dialector Dialector, opts ...Option) (db *DB, err error) {
    // ……
    db = &DB{Config: config, clone: 1}
    // ……
}

Chained query through db.Where(), Where() method will also return a *gorm.DB struct pointer tx.

 // Where add conditions
func (db *DB) Where(query interface{}, args ...interface{}) (tx *DB) {
    tx = db.getInstance()
    if conds := tx.Statement.BuildCondition(query, args...); len(conds) > 0 {
        tx.Statement.AddClause(clause.Where{Exprs: conds})
    }
    return
}

Looking at the source code, you can find that in each chain method such as Where(), Select(), Limit(), etc., you must first get *gorm.DB struct pointer tx, that is, tx = db.getInstance( ) .

 func (db *DB) getInstance() *DB {
    if db.clone > 0 {
        tx := &DB{Config: db.Config, Error: db.Error}

        if db.clone == 1 {
            // clone with new statement
            tx.Statement = &Statement{
                DB:       tx,
                ConnPool: db.Statement.ConnPool,
                Context:  db.Statement.Context,
                Clauses:  map[string]clause.Clause{},
                Vars:     make([]interface{}, 0, 8),
            }
        } else {
            // with clone statement
            tx.Statement = db.Statement.clone()
            tx.Statement.DB = tx
        }

        return tx
    }

    return db
}

When the incoming *gorm.DB pointer to the structure attribute clone is 1, it will clone a DB structure and return a new pointer to this DB structure, that is, create a new session . When clone is 0, the original pointer to the address is returned.

 db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
    panic("failed to connect database")
}

db.Where().Where()

// 等同于
tx1 = db.Where()
tx2 = tx.Where()

Based on the above analysis, in this example code, since the db pointer points to the gorm.DB structure, the clone attribute is 1. tx1 will point to a copied new gorm.DB structure with a clone attribute of 0. Therefore, tx.Where() will not copy a new structure (no new session will be created), that is, both tx2 and tx1 point to the same gorm.DB structure, which is different from db.

example

After the above analysis, and looking at the examples in the official documentation, you can understand how to use chain calls that are coroutine-safe.

Example 1

 db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})

// 安全的使用新初始化的 *gorm.DB
for i := 0; i < 100; i++ {
  go db.Where(...).First(&user)
}

In 100 coroutines, db.Where() will copy the gorm.DB structure, return its pointer, that is, create a new session, and then continue to chain calls. Therefore, in 100 coroutines, the sql queries that are chained and spliced will not interfere with each other.

Example 2

 tx := db.Where("name = ?", "jinzhu")
// 不安全的复用 Statement
for i := 0; i < 100; i++ {
  go tx.Where(...).First(&user)
}

In this example, tx points to a new gorm.DB structure, and clone is 0, so tx.Where() will not generate a new structure, ie, no new session will be created. Then, among 100 coroutines, the gorm.BD pointed to by tx is shared, which will cause the problem of mutual interference between coroutines.

Example 3

 tx := db.Where("name = ?", "jinzhu").Session(&gorm.Session{})
// 在 `新建会话方法` 之后是安全的
for i := 0; i < 100; i++ {
  go tx.Where(...).First(&user) // `name = 'jinzhu'` 会应用到查询中
}

Through the Session() method, create a new session and set the clone property of the new structure pointed to by tx to 1. "tx has the effect of db in Example 1".

Example 4

 ctx, _ := context.WithTimeout(context.Background(), time.Second)
ctxDB := db.WithContext(ctx)
// 在 `新建会话方法` 之后是安全的
for i := 0; i < 100; i++ {
  go ctxDB.Where(...).First(&user)
}

ctx, _ := context.WithTimeout(context.Background(), time.Second)
ctxDB := db.Where("name = ?", "jinzhu").WithContext(ctx)
// 在 `新建会话方法` 之后是安全的
for i := 0; i < 100; i++ {
  go ctxDB.Where(...).First(&user) // `name = 'jinzhu'` 会应用到查询中
}

These two methods reuse the Session method.

finish!

refer to
Chained Method - GORM

The article comes from how gorm ensures coroutine security

Mr_houzi
964 声望22 粉丝