Работа с пакетом database/sql в Go: ошибки и их обработка
Переменные в пакете database/sql с примерами
Categories:
Пакет database/sql
в Go предоставляет универсальный интерфейс для работы с SQL-базами данных. Рассмотрим основные ошибки, которые могут возникнуть при работе с этим пакетом, и как их правильно обрабатывать.
Основные ошибки пакета database/sql
1. ErrConnDone - соединение уже закрыто
var ErrConnDone = errors.New("sql: connection is already closed")
Эта ошибка возникает, когда вы пытаетесь выполнить операцию на соединении, которое уже было возвращено в пул соединений.
Пример:
db, err := sql.Open("mysql", "user:password@/dbname")
if err != nil {
log.Fatal(err)
}
defer db.Close()
conn, err := db.Conn(context.Background())
if err != nil {
log.Fatal(err)
}
// Возвращаем соединение в пул
conn.Close()
// Пытаемся использовать закрытое соединение
err = conn.PingContext(context.Background())
if errors.Is(err, sql.ErrConnDone) {
fmt.Println("Ошибка: соединение уже закрыто")
}
2. ErrNoRows - нет строк в результате
var ErrNoRows = errors.New("sql: no rows in result set")
Эта ошибка возвращается методом Scan
, когда QueryRow
не находит ни одной строки.
Правильная обработка:
var name string
err := db.QueryRow("SELECT name FROM users WHERE id = ?", 123).Scan(&name)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
fmt.Println("Пользователь не найден")
} else {
log.Fatal(err)
}
} else {
fmt.Printf("Имя пользователя: %s\n", name)
}
3. ErrTxDone - транзакция уже завершена
var ErrTxDone = errors.New("sql: transaction has already been committed or rolled back")
Эта ошибка возникает при попытке выполнить операцию в уже завершенной транзакции.
Пример:
tx, err := db.Begin()
if err != nil {
log.Fatal(err)
}
// Фиксируем транзакцию
err = tx.Commit()
if err != nil {
log.Fatal(err)
}
// Пытаемся выполнить запрос в завершенной транзакции
_, err = tx.Exec("INSERT INTO users(name) VALUES (?)", "Alice")
if errors.Is(err, sql.ErrTxDone) {
fmt.Println("Ошибка: транзакция уже завершена")
}
Советы по работе с database/sql
- Всегда проверяйте ошибки после операций с базой данных.
- Используйте
errors.Is
для проверки конкретных ошибок пакета database/sql. - Закрывайте ресурсы (соединения, транзакции, результаты запросов) с помощью defer.
- Используйте контексты для управления таймаутами и отменой операций.
Полный пример работы с базой данных:
package main
import (
"context"
"database/sql"
"errors"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql"
)
type User struct {
ID int
Name string
}
func main() {
db, err := sql.Open("mysql", "user:password@/dbname")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Проверка соединения
err = db.Ping()
if err != nil {
log.Fatal(err)
}
// Пример запроса с обработкой ErrNoRows
user, err := getUser(db, 123)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
fmt.Println("Пользователь не найден")
} else {
log.Fatal(err)
}
} else {
fmt.Printf("Найден пользователь: %+v\n", user)
}
// Пример транзакции
err = transferMoney(db, 1, 2, 100)
if err != nil {
log.Fatal(err)
}
}
func getUser(db *sql.DB, id int) (*User, error) {
var user User
err := db.QueryRow("SELECT id, name FROM users WHERE id = ?", id).Scan(&user.ID, &user.Name)
if err != nil {
return nil, err
}
return &user, nil
}
func transferMoney(db *sql.DB, from, to, amount int) error {
tx, err := db.Begin()
if err != nil {
return err
}
defer func() {
if err != nil {
tx.Rollback()
return
}
err = tx.Commit()
}()
// Списание денег
_, err = tx.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, from)
if err != nil {
return err
}
// Зачисление денег
_, err = tx.Exec("UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, to)
if err != nil {
return err
}
return nil
}