1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/dana-go-database-sql-tutorial-translation

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
05.0.modifying.md 8.1 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 02.03.2025 18:11 571ab8e
layout title
article
Изменение данных и работа с транзакциями

Теперь мы готовы рассмотреть, как изменять данные и работать с транзакциями. Различие может показаться искусственным, если вы привыкли к языкам программирования, использующим объект "выражение" для получения строк и изменения данных, но в Go существует важная причина для этого различия.

Выражения, изменяющие данные

Используйте Exec(), желательно с подготовленным выражением, чтобы выполнить операцию INSERT, UPDATE, DELETE или другое выражение, которое не возвращает строки. В следующем примере показано, как вставить строку и проверить метаданные о данной операции:

stmt, err := db.Prepare("INSERT INTO users(name) VALUES(?)")
if err != nil {
    log.Fatal(err)
}
res, err := stmt.Exec("Долли")
if err != nil {
    log.Fatal(err)
}
lastId, err := res.LastInsertId()
if err != nil {
    log.Fatal(err)
}
rowCnt, err := res.RowsAffected()
if err != nil {
    log.Fatal(err)
}
log.Printf("ID = %d, affected = %d\n", lastId, rowCnt)

Выполнение выражения производит sql.Result, который предоставляет доступ к метаданным выражения: последнему вставленному ID и количеству затронутых строк.

А что делать, если вам не важно результат? А что делать, если вы просто хотите выполнить выражение и проверить наличие ошибок, игнорируя результат? Не будут ли следующие два выражения выполнять одно и то же?

_, err := db.Exec("DELETE FROM users")  // ОК
_, err := db.Query("DELETE FROM users") // НЕПРАВИЛЬНО

Ответ — нет. Они не делают одно и то же, и вы никогда не должны использовать Query() таким образом. Выражение Query() вернет sql.Rows, которое зарезервирует соединение с базой данных до тех пор, пока sql.Rows не будет закрыто. Существует возможность непрочитанных данных (например, больше строк), поэтому соединение не может использоваться. В данном примере соединение никогда не будет освобождено снова. Утилита сборщика мусора в конечном итоге закроет основной net.Conn за вас, но это может занять длительное время. Кроме того, пакет database/sql продолжает отслеживать соединение в пуле, надеясь, что вы его освободите в какой-то момент, чтобы соединение могло быть использовано снова. Эта антипаттерн является хорошим способом исчерпания ресурсов (например, слишком много соединений).

Работа с транзакциями

В Go транзакция представляет собой объект, который зарезервирует соединение с хранилищем данных. Она позволяет выполнять все операции, которые мы видели ранее, гарантируя, что они будут выполняться на одном и том же соединении.

Вы начинаете транзакцию вызовом db.Begin(), а завершаете её методами Commit() или Rollback() на полученном объекте Tx. Под капотом Tx получает соединение из пула и зарезервирует его только для использования этой транзакции. Методы на Tx один-к-одному соответствуют методам, которые можно вызвать напрямую на базе данных, таких как Query() и так далее.

Подготовленные выражения, созданные в рамках транзакции, привязываются исключительно к этой транзакции. Смотрите подготовленные выражения для более подробной информации.

Не следует смешивать использование функций, связанных с транзакциями, таких как Begin() и Commit(), с SQL выражениями, такими как BEGIN и COMMIT в вашем SQL коде. Это может привести к плохим последствиям:

  • Объекты Tx могут остаться открытыми, зарезервировав соединение из пула и не вернув его обратно.
  • Состояние базы данных может выйти из синхронизации со состоянием переменных Go, представляющих его.
  • Вы можете полагать, что выполняете запросы на одном соединении внутри транзакции, когда на самом деле Go создало несколько соединений для вас невидимо, и некоторые выражения не являются частью транзакции.

Пока вы работаете внутри транзакции, следует быть внимательным и не делать вызовы к переменной Db. Делайте все свои вызовы к переменной Tx, созданной с помощью db.Begin(). Переменная Db не находится в транзакции, только Tx находится в ней. Если вы сделаете дальнейшие вызовы к db.Exec() или аналогичным методам, эти вызовы произойдут вне области вашего транзакционного контекста, на других соединениях.

Если вам требуется работать с несколькими выражениями, изменяющими состояние соединения, вам потребуется Tx, даже если вы не хотите иметь конкретную транзакцию. Например:

  • Создание временных таблиц, которые видны только одному соединению.
  • Установка переменных, такие как синтаксис SET @var := somevalue в MySQL.
  • Изменение опций соединения, такие как набор символов или таймауты.

Если вам требуется выполнить любое из этих действий, вам потребуется привязать свою активность к единственному соединению, и единственный способ сделать это в Go — использовать Tx.

Предыдущий: Извлечение множества результатов Следующий: Использование подготовленных выражений

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/dana-go-database-sql-tutorial-translation.git
git@api.gitlife.ru:oschina-mirror/dana-go-database-sql-tutorial-translation.git
oschina-mirror
dana-go-database-sql-tutorial-translation
dana-go-database-sql-tutorial-translation
master