SQL в Go: database/sql, драйверы и лучшие практики
Используйте пакет database/sql из стандартной библиотеки для работы с реляционными БД в Go. Подключайте соответствующий драйвер (MySQL, PostgreSQL, SQLite и т.д.), применяйте подготовленные выражения и транзакции, контролируйте пул соединений и обязательно защищайте запросы от SQL-инъекций. Этот материал объясняет основные приёмы, отличия драйверов и даёт практические шаблоны кода и чек-листы.
Что такое SQL и как Go взаимодействует с реляционными базами данных
SQL — язык структурированных запросов для работы с таблицами, строками и столбцами. В Go стандартный пакет database/sql обеспечивает абстракцию для выполнения SQL-команд и работы с результатами. Конкретную реализацию обеспечивает драйвер для вашего СУБД.
Коротко: database/sql — это интерфейс. Драйвер — реализация для конкретной СУБД.
Основные драйверы для Go
- github.com/go-sql-driver/mysql — MySQL/MariaDB
- github.com/lib/pq или jackc/pgx — PostgreSQL (pgx часто быстрее и даёт больше возможностей)
- github.com/mattn/go-sqlite3 — SQLite
- github.com/denisenkom/go-mssqldb — Microsoft SQL Server
Установка и импорт драйвера
Создайте модуль Go (go mod init) и установите нужный драйвер:
go get -u github.com/go-sql-driver/mysql
go get github.com/mattn/go-sqlite3Импортируйте пакет database/sql и драйвер для побочных эффектов (underscore import):
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)Так вы регистрируете драйвер в runtime, а код взаимодействует через database/sql.
Подключение к базе данных
Используйте sql.Open для создания объекта *sql.DB. sql.Open не обязательно открывает соединение немедленно — он возвращает объект-менеджер соединений:
db, err := sql.Open("sqlite3", "models/testdb.db") // SQLite
// или
db, err := sql.Open("mysql", "user:password@/dbname") // MySQLНужно проверить ошибку и протестировать соединение:
if err != nil {
log.Fatalln(err)
}
if err := db.Ping(); err != nil {
log.Fatalln("не удалось подключиться к БД:", err)
}Важные операции с объектом *sql.DB
- db.Close() — закрывает пул соединений при завершении приложения.
- db.Ping() или db.PingContext(ctx) — проверяет доступность БД.
- db.SetMaxOpenConns(n), db.SetMaxIdleConns(n), db.SetConnMaxLifetime(d) — настраивают пул соединений.
Пример: настройка пула
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(25)
db.SetConnMaxLifetime(5 * time.Minute)Подготовленные выражения и Exec
Подготовленные выражения помогают избежать SQL-инъекций и ускоряют многократное выполнение одинаковых запросов.
command, err := db.Prepare("CREATE TABLE IF NOT EXISTS login(username TEXT, password TEXT)")
if err != nil {
log.Fatalln(err)
}
_, err = command.Exec()
if err != nil {
log.Fatalln(err)
}Вставка с плейсхолдерами (формат плейсхолдеров зависит от драйвера):
command, err := db.Prepare("INSERT INTO login(username, password) values(?,?)")
exec, err := command.Exec(value1, value2)Проверка результата:
affected, err := exec.RowsAffected()
if err != nil { return }
fmt.Println("rows affected:", affected)
id, err := exec.LastInsertId()
if err != nil { return }
fmt.Println("last id:", id)Внимание: PostgreSQL-драйверы обычно используют нумерованные плейсхолдеры $1, $2 вместо ?
Запросы и чтение результатов
Для выборок используйте db.Query или db.QueryContext:
rows, err := db.Query("SELECT username, password FROM User")
if err != nil { return }
defer rows.Close()
var username, password string
for rows.Next() {
if err := rows.Scan(&username, &password); err != nil {
log.Fatalln(err)
}
fmt.Println(username, password)
}
if err := rows.Err(); err != nil {
log.Fatalln(err)
}Используйте rows.Close и rows.Err для корректной обработки ошибок.
Транзакции
Транзакции гарантируют атомарность нескольких операций:
ctx := context.Background()
tx, err := db.BeginTx(ctx, nil)
if err != nil { log.Fatalln(err) }
_, err = tx.ExecContext(ctx, "UPDATE accounts SET balance = balance - ? WHERE id = ?", 100, fromID)
if err != nil {
tx.Rollback()
return
}
_, err = tx.ExecContext(ctx, "UPDATE accounts SET balance = balance + ? WHERE id = ?", 100, toID)
if err != nil {
tx.Rollback()
return
}
if err := tx.Commit(); err != nil {
tx.Rollback()
}Используйте контекст для таймаутов и отмены.
Различия между драйверами и плейсхолдерами
- MySQL и SQLite обычно принимают знак вопроса (?) как плейсхолдер.
- PostgreSQL (lib/pq, pgx) использует $1, $2, …
- MSSQL имеет свои особенности в строке подключения и параметрах.
Учитывайте это при портировании кода между СУБД.
Обработка ошибок и контексты
Всегда проверяйте и логируйте ошибки. Для веб-приложений используйте context.Context для запросов к базе, чтобы прерывать долгие операции при разрыве HTTP-соединения:
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
defer cancel()
row := db.QueryRowContext(ctx, "SELECT ... WHERE id = ?", id)Миграции схемы
Для управления схемой используйте инструменты миграций (например, golang-migrate, goose). Ручные CREATE TABLE в коде подходят для быстрых прототипов, но для продакшена нужны версионируемые миграции.
Безопасность и жёсткая защита
- Никогда не конкатенируйте строки для SQL — используйте параметризованные запросы.
- Ограничьте привилегии учетной записи БД только необходимыми правами.
- Шифруйте соединения при необходимости (TLS).
- Логируйте запросы с осторожностью — не записывайте пароли или персональные данные.
Когда database/sql не подходит
- Если нужен ORM с автоматическим отображением моделей на таблицы, рассмотрите GORM или sqlc/ent.
- Для высокопроизводительных приложений PostgreSQL pgx может дать лучшие результаты.
- Для простых скриптов SQLite иногда удобнее, но не для параллельной нагрузки.
Альтернативные подходы
- ORM (GORM, ent) упрощают CRUD и миграции, но скрывают SQL и могут снизить контроль.
- SQL генераторы (sqlc) генерируют типобезопасный код на основе SQL-результатов.
- Использование драйвера pgx без database/sql даёт доступ к расширенным возможностям PostgreSQL.
Контрпримеры и когда оно ломается
- Небрежная работа с пулом соединений приводит к исчерпанию fd и таймаутам.
- Отсутствие правильной обработки ошибок в транзакциях может оставить данные в неконсистентном состоянии.
- Неправильные плейсхолдеры при смене драйвера приводят к синтаксическим ошибкам.
Мини-методология для добавления БД в проект
- Выберите СУБД по требованиям: ACID, масштабирование, репликация.
- Добавьте модуль миграций и создайте базовую схему.
- Подключите драйвер и настроьте пул соединений.
- Покройте критические операции транзакциями и тестами.
- Настройте мониторинг каналов ошибок и метрик пула.
Ролевые чек-листы
Разработчик
- Использовать контексты для запросов.
- Применять параметризованные запросы.
- Закрывать rows и stmt.
- Писать юнит- и интеграционные тесты с тестовой БД.
Администратор БД
- Настроить безопасную учётную запись и бэкапы.
- Контролировать лимиты соединений и метрики.
- Прямо тестировать миграции на стейджинге.
Операции и DevOps
- Настроить мониторинг latency, connection pool usage и ошибок.
- Обеспечить TLS и ротацию паролей.
- Планировать резервирование и восстановление.
Критерии приёмки
- Приложение подключается и отвечает на Ping.
- Миграции применяются без ошибок.
- Критические сценарии завершаются атомарно (транзакции).
- Запросы не содержат конкатенированных параметров.
Шаблоны и сниппеты
Пример безопасного запроса с контекстом:
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
row := db.QueryRowContext(ctx, "SELECT name FROM users WHERE id = ?", id)
var name string
if err := row.Scan(&name); err != nil {
if err == sql.ErrNoRows {
// обработать отсутствие
} else {
return err
}
}Пример try/rollback для транзакций:
func transfer(ctx context.Context, db *sql.DB, fromID, toID int, amount int) error {
tx, err := db.BeginTx(ctx, nil)
if err != nil { return err }
defer func() {
if p := recover(); p != nil {
tx.Rollback()
panic(p)
}
}()
// операции tx.ExecContext...
if err := tx.Commit(); err != nil {
tx.Rollback()
return err
}
return nil
}Совместимость и миграция
- При смене БД проверьте SQL-диалекты (BOOLEAN, AUTOINCREMENT, LIMIT/OFFSET).
- Используйте абстракции SQL только там, где они действительно нужны.
- Тестируйте на стейджинге с той же СУБД, что и прод.
Короткая справка по отладке
- Проверяйте пул соединений: превышение MaxOpenConns = блокировки.
- Включайте подробный лог драйвера на время разработки.
- Используйте EXPLAIN для медленных запросов.
Короткий план внедрения в проект
- Добавить драйвер и базовую конфигурацию db в init.
- Настроить миграции и выполнить на стейджинге.
- Писать тесты, покрывающие CRUD и транзакции.
- Настроить мониторинг и алерты на ошибки БД.
Итог
Пакет database/sql даёт надёжную и универсальную основу для работы с реляционными базами в Go. Комбинация подготовленных выражений, транзакций, корректной настройки пула и использования контекстов позволит создать стабильное и безопасное приложение. При необходимости используйте ORM или более низкоуровневые драйверы для специальных возможностей или оптимизации.
Важно
- Всегда используйте параметризованные запросы.
- Контролируйте пул соединений.
- Тестируйте миграции и критические пути с реальной СУБД.
Краткое резюме
SQL в Go — это сочетание database/sql и подходящего драйвера. Понимание различий между драйверами, управление транзакциями, соблюдение безопасности и настройка пула соединений — ключевые навыки успешного использования баз данных в Go.
Похожие материалы
RDP: полный гид по настройке и безопасности
Android как клавиатура и трекпад для Windows
Советы и приёмы для работы с PDF
Calibration в Lightroom Classic: как и когда использовать
Отключить Siri Suggestions на iPhone