Ответ
В Go для работы с SQL-совместимыми базами данных, включая PostgreSQL, используется стандартный пакет database/sql
. Этот пакет предоставляет легковесный, универсальный интерфейс, а для конкретной СУБД подключается соответствующий драйвер.
Популярные драйверы для PostgreSQL:
- pgx: Современный, высокопроизводительный драйвер с богатой функциональностью. Рекомендуется для новых проектов.
- lib/pq: Более старый, но все еще широко используемый драйвер.
Пример подключения и выполнения запросов с pgx
:
package main
import (
"context"
"database/sql"
"fmt"
"log"
_ "github.com/jackc/pgx/v5/stdlib" // Анонимный импорт драйвера
)
func main() {
// Строка подключения (DSN)
connStr := "postgres://user:password@localhost:5432/dbname?sslmode=disable"
// sql.Open создает пул соединений. Он потокобезопасен.
db, err := sql.Open("pgx", connStr)
if err != nil {
log.Fatalf("Не удалось подключиться к базе данных: %v", err)
}
defer db.Close() // Важно закрыть пул при завершении работы
// Проверка реального соединения с базой
if err := db.Ping(); err != nil {
log.Fatalf("Не удалось проверить соединение: %v", err)
}
// 1. Выполнение запросов без возврата строк (INSERT, UPDATE, DELETE)
// Используйте параметризованные запросы ($1, $2) для защиты от SQL-инъекций!
res, err := db.ExecContext(context.Background(),
"INSERT INTO users(name, age) VALUES($1, $2)", "Bob", 30)
if err != nil {
log.Printf("Ошибка вставки: %v", err)
}
// Можно получить количество затронутых строк
rowsAffected, _ := res.RowsAffected()
fmt.Printf("Добавлено %d строкn", rowsAffected)
// 2. Запрос одной строки
var name string
var age int
err = db.QueryRowContext(context.Background(),
"SELECT name, age FROM users WHERE id = $1", 1).
Scan(&name, &age)
if err != nil {
// Важно обрабатывать sql.ErrNoRows отдельно
if err == sql.ErrNoRows {
fmt.Println("Пользователь с id=1 не найден")
} else {
log.Printf("Ошибка запроса одной строки: %v", err)
}
}
// 3. Запрос нескольких строк
rows, err := db.QueryContext(context.Background(), "SELECT id, name FROM users WHERE age > $1", 25)
if err != nil {
log.Printf("Ошибка запроса нескольких строк: %v", err)
}
defer rows.Close() // Обязательно закрывать rows!
for rows.Next() { // Итерация по результатам
var id int
var currentName string
if err := rows.Scan(&id, ¤tName); err != nil {
log.Printf("Ошибка сканирования строки: %v", err)
}
fmt.Printf("ID: %d, Name: %sn", id, currentName)
}
// Проверка на ошибки, возникшие во время итерации
if err := rows.Err(); err != nil {
log.Printf("Ошибка итерации: %v", err)
}
}
Лучшие практики:
- Защита от SQL-инъекций: Всегда используйте плейсхолдеры (
$1
,$2
для Postgres) вместо конкатенации строк. - Управление соединениями:
sql.Open
создает пул соединений. Не нужно открывать/закрывать его на каждый запрос. Создайте один раз при старте приложения. defer.Close()
: Всегда закрывайте объектыsql.DB
иsql.Rows
с помощьюdefer
, чтобы избежать утечек ресурсов.- Обработка ошибок: Тщательно проверяйте все ошибки, возвращаемые функциями
database/sql
, особенноsql.ErrNoRows
дляQueryRow
. - Использование
Context
: Для запросов, которые могут выполняться долго, передавайтеcontext.Context
для управления таймаутами и отменой. - Альтернативы: Для сложных приложений рассмотрите использование кодогенераторов (например, sqlc) для создания типобезопасного кода из SQL-запросов или ORM (например, GORM), помня о компромиссах в производительности и контроле.