Я пишу приложение 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.Rollback () в случае сбоя stmt.Exec - однако это приводит к sql: statement is closed.

go
4
Mou 15 Сен 2018 в 16:47

2 ответа

Лучший ответ

Мое решение проблемы выглядит так:

  • Не создавайте одну транзакцию и не добавляйте в нее все операторы, вместо этого просто запустите ее, не создавая транзакции.
  • По мере считывания значений создайте новые подпрограммы go и позвольте транзакции выполняться параллельно (будьте осторожны с ограничениями на количество подключений).
  • Без распараллеливания производительность упала примерно на 30% (с 20 с для 25k значений до 30 с - раньше мы не использовали распараллеливание).
  • При распараллеливании производительность увеличилась примерно в 4 раза (до 5 секунд) - только будьте осторожны, оставайтесь в пределах диапазона подключений.
1
Mou 20 Сен 2018 в 22:14

Для Postgresql вы можете использовать ON CONFLICT DO NOTHING

Я пробовал приведенный ниже код с 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) ")
2
Jack_125 16 Сен 2018 в 16:27