Подробное описание функций пакета database/sql в Go

Пакет database/sql предоставляет универсальный интерфейс для работы с SQL-базами данных. Рассмотрим основные функции этого пакета с примерами использования.

Основные функции управления драйверами

func Drivers() []string

Эта функция возвращает отсортированный список имен зарегистрированных драйверов баз данных.

Пример использования:

package main

import (
	"database/sql"
	"fmt"
	"log"
	_ "github.com/go-sql-driver/mysql" // регистрируем MySQL драйвер
	_ "github.com/lib/pq"              // регистрируем PostgreSQL драйвер
)

func main() {
	// Получаем список зарегистрированных драйверов
	drivers := sql.Drivers()
	fmt.Println("Зарегистрированные драйверы:")
	for _, driver := range drivers {
		fmt.Println("-", driver)
	}

	// Выведет что-то вроде:
	// Зарегистрированные драйверы:
	// - mysql
	// - postgres
}

func Register(name string, driver driver.Driver)

Эта функция регистрирует драйвер базы данных под указанным именем. Если попытаться зарегистрировать два драйвера с одинаковым именем или передать nil, функция вызовет panic.

Обычно драйверы регистрируют себя автоматически при импорте с пустым идентификатором _, как в примере выше. Но можно регистрировать драйверы и вручную:

package main

import (
	"database/sql"
	"database/sql/driver"
	"fmt"
)

// Простой mock-драйвер для примера
type mockDriver struct{}

func (d *mockDriver) Open(name string) (driver.Conn, error) {
	fmt.Println("Mock driver открывает соединение с:", name)
	return nil, nil
}

func main() {
	// Регистрируем наш mock-драйвер
	sql.Register("mock", &mockDriver{})

	// Теперь можем его использовать
	db, err := sql.Open("mock", "test-connection")
	if err != nil {
		fmt.Println("Ошибка:", err)
		return
	}
	defer db.Close()

	// Проверяем, что драйвер зарегистрирован
	fmt.Println("Зарегистрированные драйверы:", sql.Drivers())
}

Основные функции для работы с БД

func Open(driverName, dataSourceName string) (*DB, error)

Открывает новое соединение с базой данных. На самом деле не устанавливает соединение сразу, а только готовит объект DB.

Пример:

db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/dbname")
if err != nil {
    log.Fatal(err)
}
defer db.Close()

func (*DB) Ping() error и func (*DB) PingContext(ctx context.Context) error

Проверяет, что соединение с БД живо и доступно.

Пример:

err = db.Ping()
if err != nil {
    log.Fatal("Не удалось подключиться к БД:", err)
}
fmt.Println("Успешное подключение к БД!")

Функции выполнения запросов

func (*DB) Exec(query string, args ...interface{}) (Result, error)

func (*DB) ExecContext(ctx context.Context, query string, args ...interface{}) (Result, error)

Выполняет запрос без возврата строк (INSERT, UPDATE, DELETE).

Пример:

result, err := db.Exec(
    "INSERT INTO users(name, age) VALUES (?, ?)",
    "Alice",
    30,
)
if err != nil {
    log.Fatal(err)
}

lastID, err := result.LastInsertId()
rowsAffected, err := result.RowsAffected()

func (*DB) Query(query string, args ...interface{}) (*Rows, error)

func (*DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error)

Выполняет запрос, возвращающий строки (SELECT).

Пример:

rows, err := db.Query("SELECT id, name, age FROM users WHERE age > ?", 25)
if err != nil {
    log.Fatal(err)
}
defer rows.Close()

for rows.Next() {
    var id int
    var name string
    var age int
    err = rows.Scan(&id, &name, &age)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%d: %s, %d\n", id, name, age)
}

if err = rows.Err(); err != nil {
    log.Fatal(err)
}

func (*DB) QueryRow(query string, args ...interface{}) *Row

func (*DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *Row

Выполняет запрос, который должен вернуть не более одной строки.

Пример:

var name string
var age int
err := db.QueryRow("SELECT name, age FROM users WHERE id = ?", 1).Scan(&name, &age)
if err != nil {
    if err == sql.ErrNoRows {
        fmt.Println("Пользователь не найден")
    } else {
        log.Fatal(err)
    }
} else {
    fmt.Printf("Имя: %s, Возраст: %d\n", name, age)
}

Функции для работы с транзакциями

func (*DB) Begin() (*Tx, error)

func (*DB) BeginTx(ctx context.Context, opts *TxOptions) (*Tx, error)

Начинает новую транзакцию.

Пример:

tx, err := db.Begin()
if err != nil {
    log.Fatal(err)
}

// Откатываем транзакцию в случае ошибки
defer func() {
    if err != nil {
        tx.Rollback()
    }
}()

_, err = tx.Exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
if err != nil {
    return err
}

_, err = tx.Exec("UPDATE accounts SET balance = balance + 100 WHERE id = 2")
if err != nil {
    return err
}

// Если все успешно - коммитим
err = tx.Commit()
if err != nil {
    return err
}

Работа с соединениями

func (*DB) Conn(ctx context.Context) (*Conn, error)

Получает одно соединение из пула для выполнения нескольких операций в одном контексте.

Пример:

conn, err := db.Conn(context.Background())
if err != nil {
    log.Fatal(err)
}
defer conn.Close()

// Выполняем несколько операций в одном соединении
var count int
err = conn.QueryRowContext(context.Background(), "SELECT COUNT(*) FROM users").Scan(&count)
if err != nil {
    log.Fatal(err)
}

_, err = conn.ExecContext(context.Background(), "UPDATE stats SET user_count = ?", count)
if err != nil {
    log.Fatal(err)
}

Заключение

Пакет database/sql предоставляет все необходимые функции для работы с SQL-базами данных в Go. Основные принципы:

  • Всегда проверяйте ошибки
  • Закрывайте ресурсы (Rows, Tx, Conn) с помощью defer
  • Используйте контексты для управления таймаутами
  • Для разных типов запросов используйте соответствующие методы (Exec, Query, QueryRow)

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