【问题标题】:How to insert multiple data at once如何一次插入多个数据
【发布时间】:2022-01-19 19:01:03
【问题描述】:

我知道一次插入多个数据效率更高:

INSERT INTO test(n1, n2, n3) 
VALUES(v1, v2, v3),(v4, v5, v6),(v7, v8, v9);

如何在 golang 中做到这一点?

data := []map[string]string{
   {"v1":"1", "v2":"1", "v3":"1"},
   {"v1":"2", "v2":"2", "v3":"2"},
   {"v1":"3", "v2":"3", "v3":"3"},
}
//I do not want to do it
for _, v := range data {
    sqlStr := "INSERT INTO test(n1, n2, n3) VALUES(?, ?, ?)"
    stmt, _ := db.Prepare(sqlStr)
    res, _ := stmt.Exec(v["v1"], v["v2"], v["v3"])
}

使用字符串拼接,但不好。 db.Prepare 更安全吧?

sqlStr := "INSERT INTO test(n1, n2, n3) VALUES"
for k, v := range data {
    if k == 0 {
        sqlStr += fmt.Sprintf("(%v, %v, %v)", v["v1"], v["v2"], v["v3"])
    } else {
        sqlStr += fmt.Sprintf(",(%v, %v, %v)", v["v1"], v["v2"], v["v3"])
    } 
}
res, _ := db.Exec(sqlStr)

我需要一个更安全高效的函数一次插入多个数据。

【问题讨论】:

  • 我不确定 MySQL 是否支持这一点,但一些 SQL 实现支持将数组作为参数传递给查询。如果 MySQL 支持它,并且您使用的任何数据库驱动程序也支持它,您可以执行db.Exec("INSERT INTO test (n1, n2, n3) VALUES ?, ?, ?", []int{1, 2, 3}, []int{4, 5, 6}, []int{7, 8, 9}) 之类的操作。如果您希望能够处理任意数量的插入,您仍然必须手动构造查询字符串(也就是说,您必须构造一个带有“?”的查询,一遍又一遍地重复),但它比什么都没有。

标签: go


【解决方案1】:

为什么不这样呢? (写在这里没有测试,所以可能有语法错误):

sqlStr := "INSERT INTO test(n1, n2, n3) VALUES "
vals := []interface{}{}

for _, row := range data {
    sqlStr += "(?, ?, ?),"
    vals = append(vals, row["v1"], row["v2"], row["v3"])
}
//trim the last ,
sqlStr = sqlStr[0:len(sqlStr)-1]
//prepare the statement
stmt, _ := db.Prepare(sqlStr)

//format all vals at once
res, _ := stmt.Exec(vals...)

【讨论】:

  • 在修剪最后一个 "," 时,为什么是 len(sqlStr)-2 而 len(sqlStr)-1 ?
  • sqlStr = strings.TrimSuffix(sqlStr, ",")
  • @Not_a_Golfer 切片不包括end 索引的值,所以它应该是sqlStr[0:len(sqlStr)-1] demo
  • 为了改进这个已经很好的解决方案,避免所有的修剪混乱,而不是每次通过 for 循环时都使用sqlStr += "(?, ?),",而是声明一个const rowSQL= "(?, ?)" 和一个var inserts []string。每次循环时,将rowSQL 附加到inserts 和循环结束时sqlInsert = sqlInsert + strings.Join(inserts, ",")
  • 如何在需要 $1、$2 等的 postgres 中做类似的事情?
【解决方案2】:

对于 Postgres lib pq 支持批量插入:https://godoc.org/github.com/lib/pq#hdr-Bulk_imports

但同样可以通过下面的代码实现,但真正有用的是尝试执行批量条件更新(相应地更改查询)。

对于 Postgres 执行类似的批量插入,您可以使用以下函数。

// ReplaceSQL replaces the instance occurrence of any string pattern with an increasing $n based sequence
func ReplaceSQL(old, searchPattern string) string {
   tmpCount := strings.Count(old, searchPattern)
   for m := 1; m <= tmpCount; m++ {
      old = strings.Replace(old, searchPattern, "$"+strconv.Itoa(m), 1)
   }
   return old
}

所以上面的示例变成了

sqlStr := "INSERT INTO test(n1, n2, n3) VALUES "
vals := []interface{}{}

for _, row := range data {
   sqlStr += "(?, ?, ?),"
   vals = append(vals, row["v1"], row["v2"], row["v3"])
}

//trim the last ,
sqlStr = strings.TrimSuffix(sqlStr, ",")

//Replacing ? with $n for postgres
sqlStr = ReplaceSQL(sqlStr, "?")

//prepare the statement
stmt, _ := db.Prepare(sqlStr)

//format all vals at once
res, _ := stmt.Exec(vals...)

【讨论】:

    【解决方案3】:

    Gorm V2(2020 年 8 月 30 日发布)现在支持批量插入查询。

    // Pass slice data to method Create, GORM will generate a single SQL statement
    // to insert all the data and backfill primary key values,
    // hook methods will be invoked too.
    
    var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
    DB.Create(&users)
    
    for _, user := range users {
      user.ID // 1,2,3
    }
    

    更多详情请参考官方文档:https://gorm.io/docs/create.html

    【讨论】:

      【解决方案4】:

      如果启用多语句,则可以一次执行多条语句。 有了它,您应该能够处理多个插入。

      https://github.com/go-sql-driver/mysql#multistatements

      【讨论】:

      • Allow multiple statements in one query. While this allows batch queries, ... Only the result of the first query is returned, all other results are silently discarded. 这是用于查询的——但是这对插入有什么影响?这方面的文档很少!
      【解决方案5】:

      这是一种有效的过渡方式,仅在提交后才进行网络调用。

      func insert(requestObj []models.User) (bool, error) {
          tx := db.Begin()
          defer func() {
              if r := recover(); r != nil {
                  tx.Rollback()
              }
          }()
      
          for _, obj := range requestObj {
              if err := tx.Create(&obj).Error; err != nil {
                  logging.AppLogger.Errorf("Failed to create user")
                  tx.Rollback()
                  return false, err
              }
          }
          err := tx.Commit().Error
          if err != nil {
              return false, err
          }
          return true, nil
      }
      

      【讨论】:

        【解决方案6】:

        经过广泛的研究,这对我有用:

        var values []interface{}
        for _, scope := range scopes {
            values = append(values, scope.ID, scope.Code, scope.Description)
        }
        sqlStr := `INSERT INTO scopes (application_id, scope, description) VALUES %s`
        sqlStr = setupBindVars(sqlStr, "(?, ?, ?)", len(scopes))
        
        _, err = s.db.ExecContext(ctx, sqlStr, values...)
        

        // 要替换的辅助函数 ?具有正确数量的绑定变量集

        func setupBindVars(stmt, bindVars string, len int) string {
            bindVars += ","
            stmt = fmt.Sprintf(stmt, strings.Repeat(bindVars, len))
            return strings.TrimSuffix(stmt, ",")
        }
        

        【讨论】:

          【解决方案7】:

          来自https://gorm.io/docs/create.html#Batch-Insert

          代码示例:

          var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
          DB.Create(&users)
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2022-01-23
            • 2019-02-02
            • 1970-01-01
            • 2018-04-17
            • 2010-11-30
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多