Как в Go работать с реляционными базами данных (например, PostgreSQL, MySQL)?

Ответ

Для работы с SQL-совместимыми базами данных в Go используется стандартный пакет database/sql. Он предоставляет общий интерфейс, а для каждой конкретной СУБД требуется соответствующий драйвер.

Пример работы с PostgreSQL:

package main

import (
    "database/sql"
    "fmt"
    "log"

    // Пустой импорт _ для регистрации драйвера PostgreSQL.
    // Драйвер зарегистрирует себя в пакете database/sql через свою init() функцию.
    _ "github.com/lib/pq"
)

const connStr = "user=postgres dbname=mydb password=secret host=db.example.com port=5432 sslmode=disable"

func main() {
    // sql.Open не устанавливает соединение сразу, а лишь готовит объект для работы с БД.
    db, err := sql.Open("postgres", connStr)
    if err != nil {
        log.Fatalf("Ошибка при подготовке к подключению к БД: %v", err)
    }
    // defer db.Close() гарантирует, что пул соединений будет закрыт при выходе из main.
    defer db.Close()

    // db.Ping() проверяет реальное подключение к базе данных.
    if err = db.Ping(); err != nil {
        log.Fatalf("Ошибка подключения к БД: %v", err)
    }

    fmt.Println("Успешное подключение к PostgreSQL!")

    // Выполнение запроса и обработка результатов
    rows, err := db.Query("SELECT id, name FROM users WHERE id > $1", 0)
    if err != nil {
        log.Fatalf("Ошибка выполнения запроса: %v", err)
    }
    defer rows.Close()

    for rows.Next() {
        var id int
        var name string
        if err := rows.Scan(&id, &name); err != nil {
            log.Printf("Ошибка сканирования строки: %v", err)
            continue
        }
        fmt.Printf("ID: %d, Name: %sn", id, name)
    }

    if err := rows.Err(); err != nil {
        log.Fatalf("Ошибка при итерации по результатам: %v", err)
    }
}

Ключевые моменты:

  1. Пакет database/sql: Предоставляет абстракцию для работы с БД. Вы работаете с типами sql.DB, sql.Rows и т.д.
  2. Драйвер СУБД: Для каждой БД (PostgreSQL, MySQL, SQLite) нужен свой драйвер. Он импортируется с пустым идентификатором (_), чтобы выполнилась его функция init() для регистрации в database/sql.
  3. Пул соединений: sql.DB — это не одно соединение, а потокобезопасный пул соединений. Не нужно открывать и закрывать его для каждого запроса. Инициализируйте его один раз и используйте в течение жизни приложения.
  4. Обработка ошибок: Всегда проверяйте ошибки после каждой операции с БД, включая rows.Close() и итерацию rows.Next() (через rows.Err()).
  5. Подготовленные выражения (Prepared Statements): Для защиты от SQL-инъекций и повышения производительности повторяющихся запросов используйте db.Prepare() и stmt.Exec()/stmt.Query().