Golang批量更新数据操作

本文介绍了如何使用GORM框架在MySQL、PostgreSQL和SQLite数据库中实现批量数据插入,并在已有记录时执行更新操作。通过定义结构体`BatchProvider`和相关方法,实现了根据不同数据库类型动态构建`INSERT ON DUPLICATE KEY UPDATE`或`ON CONFLICT DO UPDATE`语句,从而高效地处理批量入库业务。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基础知识

在现实的开发场景中,有许许多多的数据在入库之前,我们并不知道到数据库是否已经存在该数据,其实都会先查询一遍,若存在,则执行更新语句,否则执行插入语句。

但是在SQL标准语句中,对于批量插入数据若存在则更新的语句实现方式有

insert ... on duplicate key update ...

针对批量插入或者更新的业务,我们可以实现拼接SQL语句来提高数据入库的效率。

适用数据库类型

  • MySQL

  • PostgresSQL

  • SQLite

数据库适用框架

  • GORM

代码分析

  • 首先定义个结构体

type BatchProvider struct {
  TableName      string   `json:"table_name"`   // 表名称
  Fields         []string `json:"fields"`       // 字段名称列表
  ConflictFields []string `json:"conflict_fields, omitempty"`  // 冲突字段名称列表
  UpdateFields   []string `json:"update_fields"`   // 更新字段名称列表
  BatchAmount    int      `json:"batch_amount"`    // 每一次批量操作数
}
  • 然后定义一个Update更新方法并实现

// records 批量数据
func (provider *BatchProvider) Update(engine *gorm.DB, records [][]interface{}) error {
  var (
    index = 0
    end   int
    err   error
  )
  for index < len(records) {
    end = index + provider.BatchAmount
    if end > len(records) {
      end = len(records)
    }
    if err = provider.load(engine, records[index:end]); err != nil {
      return err
    }
    index = end
  }
  return err
}
​
func (provider *BatchProvider) load(engine *gorm.DB, records [][]interface{}) error {
  // 定义变量
  var (
    sql  string
    args []interface{}
    err  error
  )
  // 构造sql
  sql, err = provider.constructSQL(records, provider.engineJudge(*engine))
  if err != nil {
    return err
  }
  // 添加值列表
  for _, record := range records {
    args = append(args, record...)
  }
  return engine.Exec(sql, args...).Error
}
​
// 声明定义三种不同的执行引擎类型
type DialectorType string
const (
  DIALECTOR_MYSQL  DialectorType = "mysql"
  DIALECTOR_PGSQL  DialectorType = "postgres"
  DIALECTOR_SQLITE DialectorType = "sqlite"
)
​
// 通过执行Dialector的名称,判断执行引擎的类型
func (provider *BatchProvider) engineJudge(engine gorm.DB) DialectorType {
  switch engine.Dialector.Name() {
  case (&postgres.Dialector{}).Name():
    return DIALECTOR_PGSQL
  case (&mysql.Dialector{}).Name():
    return DIALECTOR_MYSQL
  case (&sqlite.Dialector{}).Name():
    return DIALECTOR_SQLITE
  default:
    return ""
  }
}
  • 最关键的代码就在constructSQL方法中

func (provider *BatchProvider) constructSQL(records [][]interface{}, dialectorType DialectorType) (string, error) {
  switch dialectorType {
  case DIALECTOR_PGSQL:
    return provider.constructPGSQL(records), nil
  case DIALECTOR_MYSQL:
    return provider.constructMYSQL(records), nil
  case DIALECTOR_SQLITE:
    return provider.constructSQLite(records), nil
  default:
    return "", fmt.Errorf("dialector type is invalid")
  }
}
  • 对于不同的数据库,批量操作的SQL语句是有略微不同的,因此需要分开进行编写

func (provider *BatchProvider) constructPGSQL(records [][]interface{}) string {
  var (
    valueNames        string
    valuePlaceHolder  string
    valuePlaceHolders string
    sql               string
  )
  valueNames = strings.Join(provider.Fields, ", ")
  valuePlaceHolder = strings.Repeat("?,", len(provider.Fields))
  valuePlaceHolder = "(" + valuePlaceHolder[:len(valuePlaceHolder)-1] + "),"
  valuePlaceHolders = strings.Repeat(valuePlaceHolder, len(records))
  valuePlaceHolders = valuePlaceHolders[:len(valuePlaceHolders)-1]
  sql = "insert into " + provider.TableName + " (" + valueNames + ") values" + valuePlaceHolders
  if len(provider.ConflictFields) > 0 {
    var onDups []string
    sql += " on conflict(" + strings.Join(provider.ConflictFields, ", ") + ") do "
    if len(provider.UpdateFields) > 0 {
      for _, field := range provider.UpdateFields {
        onDups = append(onDups, field+"=excluded."+field)
      }
      sql += "update set " + strings.Join(onDups, ", ")
    } else {
      sql += "nothing"
    }
  }
  return sql
}
​
func (provider *BatchProvider) constructMYSQL(records [][]interface{}) string {
  var (
    valueNames        string
    valuePlaceHolder  string
    valuePlaceHolders string
    sql               string
  )
  valueNames = strings.Join(provider.Fields, ", ")
  valuePlaceHolder = strings.Repeat("?,", len(provider.Fields))
  valuePlaceHolder = "(" + valuePlaceHolder[:len(valuePlaceHolder)-1] + "),"
  valuePlaceHolders = strings.Repeat(valuePlaceHolder, len(records))
  valuePlaceHolders = valuePlaceHolders[:len(valuePlaceHolders)-1]
  sql = "insert into " + provider.TableName + " (" + valueNames + ") values" + valuePlaceHolders
  var onDups []string
  sql += " on duplicate key "
  if len(provider.UpdateFields) > 0 {
    for _, field := range provider.UpdateFields {
      onDups = append(onDups, field+"=values("+field+")")
    }
    sql += "update " + strings.Join(onDups, ", ")
  } else {
    sql += "nothing"
  }
  return sql
}
​
// 同PostgresSQL一样
func (provider *BatchProvider) constructSQLite(records [][]interface{}) string {
  return provider.constructPGSQL(records)
}

代码测试

  • 定义一个结构体,对应到数据库中的一张表

type User struct {
  Id              string `gorm:"column:id;pk" json:"id"`
  Name            string `gorm:"column:name" json:"name"`
  UpdateTimestamp int64  `gorm:"column:update_timestamp" json:"update_timestamp"`
}
​
func (user *User) TableName() string {
  return "user"
}
  • 创建GORM的实例,进行自动建表,并插入(更新)数据

// SQLite
func NewSQLiteEngine() (*gorm.DB, error) {
  var dataSource = "db/sqlite.db?_timeout=5000"
  // 获取配置文件,创建相应的目录
  dataSourceDir := filepath.Dir(dataSource)
  _, fileErr := os.Stat(dataSourceDir)
  if fileErr != nil || !os.IsExist(fileErr) {
    _ = os.MkdirAll(dataSourceDir, os.ModePerm)
  }
  return gorm.Open(sqlite.Open(dataSource), &gorm.Config{})
}
​
// MySQL
func NewMySQLEngine() (*gorm.DB, error) {
  var mysqlUrl = "root:123456@tcp(127.0.0.1:23306)/batch-update?charset=utf8mb4&parseTime=True&loc=Local"
  return gorm.Open(mysql.Open(mysqlUrl), &gorm.Config{})
}
​
func main() {
  var p = &BatchProvider{
    TableName:      (&User{}).TableName(),
    Fields:         []string{"id", "name", "update_timestamp"},
    ConflictFields: []string{"id"},
    UpdateFields:   []string{"name", "update_timestamp"},
    BatchAmount:    100,
  }
  var records = [][]interface{}{
    {"1", "name_1", time.Now().Unix()},
    {"2", "name_2", time.Now().Unix()},
    {"3", "name_3", time.Now().Unix()},
  }
  // SQLite测试
  engine, err := NewSQLiteEngine()
  if err != nil {
    panic(err)
  }
  engine = engine.Debug()
  if err = engine.AutoMigrate(&User{}); err != nil {
    panic(err)
  }
  if err = p.Update(engine, records); err != nil {
    panic(err)
  }
  // MySQL测试
  engine, err = NewMySQLEngine()
  if err != nil {
    panic(err)
  }
  engine = engine.Debug()
  if err = engine.AutoMigrate(&User{}); err != nil {
    panic(err)
  }
  if err = p.Update(engine, records); err != nil {
    panic(err)
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值