Ответ
Да, я активно использовал PostgreSQL в качестве основной СУБД для Go-приложений. Мой опыт включает работу через стандартный интерфейс database/sql и современные драйверы.
Основные инструменты:
database/sql: Стандартный пакет Go, который предоставляет общий интерфейс для работы с SQL-базами данных.- Драйверы: В основном использовал
pgx, так как он является наиболее производительным и функциональным современным драйвером. Ранее также работал сlib/pq.
Ключевые концепции database/sql:
-
sql.DB— это пул соединений: Важно понимать, чтоsql.DBпредставляет собой потокобезопасный пул соединений, а не одно конкретное подключение. Его нужно создавать один раз при старте приложения и переиспользовать. -
Разделение запросов:
db.QueryRowContext(): Для запросов, возвращающих одну строку. Результат считывается через.Scan().db.QueryContext(): Для запросов, возвращающих множество строк. Результат итерируется в цикле с обязательнымrows.Close()для возврата соединения в пул.db.ExecContext(): Для команд, не возвращающих строки (INSERT,UPDATE,DELETE).
Пример запроса одной строки:
import (
"database/sql"
"context"
_ "github.com/jackc/pgx/v5/stdlib"
)
var db *sql.DB // Инициализируется при старте
func GetUserName(ctx context.Context, userID int) (string, error) {
var name string
// Используем QueryRowContext для запроса одной строки
err := db.QueryRowContext(ctx, "SELECT name FROM users WHERE id = $1", userID).Scan(&name)
if err != nil {
if err == sql.ErrNoRows {
// Это не ошибка приложения, а ожидаемый результат "не найдено"
return "", fmt.Errorf("user not found")
}
// Реальная ошибка БД
return "", err
}
return name, nil
}
Мой опыт также включает:
- Транзакции: Использование
db.Begin()для выполнения группы операций в атомарной транзакции сCOMMITилиROLLBACK. - Подготовленные запросы: Работа с
db.PrepareContext()для повышения производительности часто выполняемых запросов и защиты от SQL-инъекций. - Миграции БД: Использование инструментов вроде
golang-migrate/migrateилиgooseдля версионирования схемы базы данных. - Работа с ORM: Имею представление о работе с ORM-библиотеками, такими как
GORMилиsqlc(генератор кода), и понимаю их преимущества и недостатки.
Ответ 18+ 🔞
А, PostgreSQL с Go? Да, конечно, имел дело, и не раз. Это как раз тот случай, когда инструмент не подводит, если его в руках не переворачивать.
Чем обычно воюю:
database/sql: Ну, стандарт де-факто, куда ж без него. Обёртка, которая не даёт тебе наделать совсем уж эпичных глупостей.- Драйверы: Раньше пихал
lib/pq, как все. Потом пересел наpgx— и, ёпта, как будто с велосипеда на мотоцикл пересел. Быстрее, умнее, приятнее. Рекомендую, не пожалеешь.
А теперь главное, что многие тупо не въезжают сходу:
-
sql.DB— это НЕ одно соединение, это ПУЛ. Представь бассейн с лежаками. Ты создаёшь его один раз при старте приложения и потом все тусят там, берут лежак, валяются, возвращают. Не надо на каждый чих создавать новыйsql.DB— это пиздец как ресурсоёмко и медленно. -
Три кита, на которых всё держится:
db.QueryRowContext()— когда ждёшь ровно одну запись. Сканируешь результат в переменные и живёшь дальше. Идеально дляSELECT ... WHERE id = ....db.QueryContext()— когда строк много. Обязательно, блядь, ЗАКРЫВАЙrows.Close()после использования, иначе соединение не вернётся в пул и всё накроется медным тазом. Типичная ошибка новичков.db.ExecContext()— когда тебе не нужен результат в виде строк (INSERT,UPDATE). Просто сделал дело — и свободен.
Вот, смотри, как это выглядит в коде, на примере простого запроса:
import (
"database/sql"
"context"
_ "github.com/jackc/pgx/v5/stdlib" // Драйвер
)
var db *sql.DB // Глобальная переменная — и пусть весь мир подождёт!
func GetUserName(ctx context.Context, userID int) (string, error) {
var name string
// QueryRowContext — наш выбор для одной строки
err := db.QueryRowContext(ctx, "SELECT name FROM users WHERE id = $1", userID).Scan(&name)
if err != nil {
if err == sql.ErrNoRows {
// Это не конец света, просто пользователя нет
return "", fmt.Errorf("user not found")
}
// А вот это уже реальная проблема, надо разбираться
return "", err
}
return name, nil
}
Что ещё в арсенале было:
- Транзакции:
db.Begin()— это святое. Группируешь операции, которые должны выполниться либо все, либо никак. Либо коммитишь успех, либо откатываешься, как краб. - Подготовленные выражения (
Prepare) : Полезная штука для запросов, которые крутятся в цикле. И безопаснее (инъекции отсекаются), и быстрее. - Миграции: Без этого — никуда. Использовал
golang-migrate. Версионирование схемы — это не прихоть, а необходимость, если не хочешь вручную на десятке серверов SQL-скрипты гонять. - ORMs и кодогенераторы: GORM — это, конечно, мощно, но иногда чувствуешь себя так, будто везешь диван на велосипеде.
sqlc— другое дело, генерация типобезопасного кода из SQL — это овердохуище удобно. Хотя иногда хочется просто написать запрос руками и не париться.