Работа с пакетом database/sql в Go: ошибки и их обработка

Переменные в пакете database/sql с примерами

Пакет 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

  1. Всегда проверяйте ошибки после операций с базой данных.
  2. Используйте errors.Is для проверки конкретных ошибок пакета database/sql.
  3. Закрывайте ресурсы (соединения, транзакции, результаты запросов) с помощью defer.
  4. Используйте контексты для управления таймаутами и отменой операций.

Полный пример работы с базой данных:

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
}