GoLan数据库事务:如果单个 exec 语句失败,则继续



我正在编写一个Go应用程序,该应用程序应该将文件中的数千个值插入到数据库中。只要所有值都可以插入到数据库中,就可以正常工作。如果其中一个查询失败,则之后的所有查询都将失败,因为pq: : current transaction is aborted, commands ignored until end of transaction block

我想插入所有元素,如果一个元素的插入失败,应该跳过它并插入其他元素。

我的代码:

func (db *Database) Insert(values []Value) (transerr error) {
tx, err := db.Begin()
if transerr != nil {
return nil, err
}
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}
stmt, err := tx.Prepare("INSERT INTO foo VALUES (?)")
if err != nil {
return err
}
defer stmt.Close()
for _, value : range values {
_, err = stmt.Exec(value)
if err != nil {
log.Error(err)
}
}
return nil
}

我试图添加一个tx。回滚(( 以防万一 stmt.执行失败 - 但这会导致sql: statement is closed

对于 Postgresql,您可以使用在冲突时什么都不做

我已经尝试了下面的代码,上面有 postgresql db,它忽略了有错误的插入行。我做了一些其他的改变来尝试我这边。您可以忽略我的其他更改。

func insert(db *sql.DB, values []string) error {
tx, err := db.Begin()
if err != nil {
return err
}
defer tx.Commit()
stmt, err := tx.Prepare("INSERT INTO foo (  foo_col) VALUES ($1) ON CONFLICT DO NOTHING")
if err != nil {
fmt.Println("errro at stmt", err)
return err
}
defer stmt.Close()
for _, value := range values {
_, err = stmt.Exec(value)
if err != nil {
fmt.Println(value, err)
}
}
return nil
}

对于 mysql,您可以使用INSERT IGNORE

stmt, err := tx.Prepare("INSERT IGNORE INTO foo (  foo_col) VALUES ($1) ")

我的问题解决方案如下所示:

  • 不要创建单个事务并将所有语句添加到其中,而只需运行它而不创建事务。
  • 读入值时,生成新的 go 例程,并让事务并行运行(注意连接限制(。
  • 如果没有并行化,性能下降了大约 30%(从 20k 值的 20 秒下降到 30 秒 - 我们之前没有使用并行化(。
  • 通过并行化,性能提高了大约 4 倍(到 5 秒( - 只是要小心,你保持在连接范围内

最新更新