Описание типа database/sql DB

DB - это хэндл базы данных, представляющий пул из нуля или более базовых соединений. Он безопасен для одновременного использования несколькими горутинами.
type DB struct {
	// содержит отфильтрованные или неэкспонированные поля
}

Пакет sql создает и освобождает соединения автоматически; он также поддерживает свободный пул незадействованных соединений. Если в базе данных есть понятие состояния каждого соединения, то такое состояние можно надежно наблюдать в рамках транзакции (Tx) или соединения (Conn). После вызова DB.Begin возвращаемая Tx привязывается к одному соединению. После вызова Tx.Commit или Tx.Rollback для транзакции, соединение этой транзакции возвращается в пул незанятых соединений DB. Размер пула можно контролировать с помощью DB.SetMaxIdleConns.

func Open

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

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

Большинство пользователей открывают базу данных с помощью специфической для драйвера вспомогательной функции подключения, которая возвращает *DB. Драйверы баз данных не включены в стандартную библиотеку Go. Список драйверов сторонних разработчиков см. на https://golang.org/s/sqldrivers.

Open может просто проверить свои аргументы, не создавая соединения с базой данных. Чтобы убедиться, что имя источника данных действительно, вызовите DB.Ping.

Возвращаемая БД безопасна для одновременного использования несколькими горутинами и поддерживает собственный пул незанятых соединений. Таким образом, функцию Open следует вызывать только один раз. Редко возникает необходимость закрывать БД.

func OpenDB

func OpenDB(c driver.Connector) *DB

OpenDB открывает базу данных с помощью driver.Connector, позволяя драйверам обходить строковое имя источника данных.

Большинство пользователей открывают базу данных с помощью специфической для драйвера функции-помощника подключения, которая возвращает *DB. Драйверы баз данных не включены в стандартную библиотеку Go.

OpenDB может просто проверить свои аргументы, не создавая соединения с базой данных. Чтобы убедиться, что имя источника данных действительно, вызовите DB.Ping.

Возвращаемая БД безопасна для одновременного использования несколькими горутинами и поддерживает свой собственный пул незанятых соединений. Таким образом, функция OpenDB должна быть вызвана только один раз. Редко возникает необходимость закрывать БД.

func (*DB) Close

func (db *DB) Close() error

Close закрывает базу данных и предотвращает запуск новых запросов. Затем Close ожидает завершения всех запросов, которые начали обрабатываться на сервере.

Закрывать БД приходится редко, так как хэндл БД должен быть долгоживущим и использоваться совместно многими горутинами.

db, err := sql.Open("mysql", dsn)
if err != nil {
    log.Fatal(err)
}
defer db.Close() // Всегда закрывайте соединение

func (*DB) Ping

func (db *DB) Ping() error

Ping проверяет, что соединение с базой данных еще живо, и при необходимости устанавливает соединение.

Внутри Ping использует context.Background; чтобы указать контекст, используйте DB.PingContext.

err = db.Ping()
if err != nil {
    log.Fatal("Connection failed:", err)
}

func (*DB) PingContext

func (db *DB) PingContext(ctx context.Context) error

PingContext проверяет, что соединение с базой данных еще живо, и при необходимости устанавливает соединение.

Пример
package main

import (
	"context"
	"database/sql"
	"log"
	"time"
)

var (
	ctx context.Context
	db  *sql.DB
)

func main() {

	ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
	defer cancel()

	status := "up"
	if err := db.PingContext(ctx); err != nil {
		status = "down"
	}
	log.Println(status)
}

func (*DB) Begin

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

Begin начинает транзакцию. Уровень изоляции по умолчанию зависит от драйвера.

Begin внутренне использует context.Background; чтобы указать контекст, используйте DB.BeginTx.

func (*DB) BeginTx

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

BeginTx начинает транзакцию.

Предоставленный контекст используется до тех пор, пока транзакция не будет зафиксирована или откачена. Если контекст будет отменен, пакет sql откатит транзакцию. Tx.Commit вернет ошибку, если контекст, предоставленный BeginTx, будет отменен.

Предоставленный параметр TxOptions является необязательным и может быть равен nil, если следует использовать значения по умолчанию. Если используется уровень изоляции не по умолчанию, который драйвер не поддерживает, будет возвращена ошибка.

Пример
import (
	"context"
	"database/sql"
	"log"
)

var (
	ctx context.Context
	db  *sql.DB
)

func main() {
	tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})
	if err != nil {
		log.Fatal(err)
	}
	id := 37
	_, execErr := tx.Exec(`UPDATE users SET status = ? WHERE id = ?`, "paid", id)
	if execErr != nil {
		_ = tx.Rollback()
		log.Fatal(execErr)
	}
	if err := tx.Commit(); err != nil {
		log.Fatal(err)
	}
}

func (*DB) Conn

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

Conn возвращает одно соединение, либо открывая новое соединение, либо возвращая существующее соединение из пула соединений. Conn будет блокироваться до тех пор, пока не будет возвращено соединение или не будет отменен ctx. Запросы, выполняемые по одному и тому же Conn, будут выполняться в одной и той же сессии базы данных.

Каждый Conn должен быть возвращен в пул баз данных после использования вызовом Conn.Close.

func (*DB) Driver

func (db *DB) Driver() driver.Driver

Driver возвращает базовый драйвер базы данных.

func (*DB) Exec

func (db *DB) Exec(query string, args ...any) (Result, error)

Exec выполняет запрос, не возвращая ни одной строки. В качестве args используются любые параметры-заполнители в запросе.

Exec использует внутренний контекст context.Background; чтобы указать контекст, используйте DB.ExecContext.

context.Background func Background() Context Background возвращает ненулевой, пустой Context. Он никогда не отменяется, не имеет значений и сроков. Обычно используется главной функцией, инициализацией и тестами, а также в качестве контекста верхнего уровня для входящих запросов.

func (*DB) ExecContext

func (db *DB) ExecContext(ctx context.Context, query string, args ...any) (Result, error)

ExecContext выполняет запрос, не возвращая ни одной строки. В качестве args используются любые параметры-заполнители в запросе.

func (*DB) Prepare

func (db *DB) Prepare(query string) (*Stmt, error)

Prepare создает подготовленный отчет для последующих запросов или выполнения. Из полученного отчета можно одновременно выполнять несколько запросов или операций. Вызывающая сторона должна вызвать метод *Stmt.Close оператора, когда он больше не нужен.

Prepare внутренне использует context.Background; чтобы указать контекст, используйте DB.PrepareContext.

Пример
import (
	"context"
	"database/sql"
	"log"
)

var db *sql.DB

func main() {
	projects := []struct {
		mascot  string
		release int
	}{
		{"tux", 1991},
		{"duke", 1996},
		{"gopher", 2009},
		{"moby dock", 2013},
	}

	stmt, err := db.Prepare("INSERT INTO projects(id, mascot, release, category) VALUES( ?, ?, ?, ? )")
	if err != nil {
		log.Fatal(err)
	}
	defer stmt.Close() // Prepared statements take up server resources and should be closed after use.

	for id, project := range projects {
		if _, err := stmt.Exec(id+1, project.mascot, project.release, "open source"); err != nil {
			log.Fatal(err)
		}
	}
}

func (*DB) PrepareContext

func (db *DB) PrepareContext(ctx context.Context, query string) (*Stmt, error)

PrepareContext создает подготовленный запрос для последующих запросов или выполнений. Из полученного отчета можно одновременно выполнять несколько запросов или операций. Вызывающая сторона должна вызвать метод *Stmt.Close оператора, когда он больше не нужен.

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

func (*DB) Query

func (db *DB) Query(query string, args ...any) (*Rows, error)

Query выполняет запрос, возвращающий строки, обычно SELECT. В качестве args используются любые параметры-заполнители в запросе.

Query внутренне использует context.Background; чтобы указать контекст, используйте DB.QueryContext.

Пример
package main

import (
	"context"
	"database/sql"
	"log"

	_ "github.com/lib/pq" // или другой драйвер БД
)

var db *sql.DB

func main() {
	// Инициализация подключения к БД
	var err error
	db, err = sql.Open("postgres", "user=postgres dbname=test sslmode=disable") // замените на свои параметры
	if err != nil {
		log.Fatal("Failed to connect to database:", err)
	}
	defer db.Close()

	// Проверка соединения
	if err := db.Ping(); err != nil {
		log.Fatal("Failed to ping database:", err)
	}

	age := 27
	q := `
-- Создаем временную таблицу
CREATE TEMP TABLE uid (id bigint) ON COMMIT DROP;

-- Заполняем временную таблицу
INSERT INTO uid
SELECT id FROM users WHERE age < $1;

-- Первый набор результатов
SELECT
	users.id, name
FROM
	users
	JOIN uid ON users.id = uid.id;

-- Второй набор результатов
SELECT 
	ur.user_id, ur.role_id
FROM
	user_roles AS ur
	JOIN uid ON uid.id = ur.user_id;
	`

	// Используем контекст для запроса
	ctx := context.Background()
	rows, err := db.QueryContext(ctx, q, age)
	if err != nil {
		log.Fatal("Query failed:", err)
	}
	defer rows.Close()

	// Обрабатываем первый набор результатов
	log.Println("Processing users:")
	for rows.Next() {
		var (
			id   int64
			name string
		)
		if err := rows.Scan(&id, &name); err != nil {
			log.Fatal("Failed to scan user row:", err)
		}
		log.Printf("id %d name is %s\n", id, name)
	}

	// Переходим ко второму набору результатов
	if !rows.NextResultSet() {
		if err := rows.Err(); err != nil {
			log.Fatal("Failed to get next result set:", err)
		}
		log.Fatal("Expected more result sets, but none available")
	}

	// Обрабатываем второй набор результатов
	roleMap := map[int64]string{
		1: "user",
		2: "admin",
		3: "gopher",
	}

	log.Println("\nProcessing roles:")
	for rows.Next() {
		var (
			userID int64
			roleID int64
		)
		if err := rows.Scan(&userID, &roleID); err != nil {
			log.Fatal("Failed to scan role row:", err)
		}
		roleName, ok := roleMap[roleID]
		if !ok {
			roleName = "unknown"
		}
		log.Printf("user %d has role %s\n", userID, roleName)
	}

	if err := rows.Err(); err != nil {
		log.Fatal("Rows error:", err)
	}
}

func (*DB) QueryContext

func (db *DB) QueryContext(ctx context.Context, query string, args ...any) (*Rows, error)

QueryContext выполняет запрос, возвращающий строки, обычно SELECT. В качестве args используются любые параметры-заполнители запроса.

Пример
package main

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

	_ "github.com/go-sql-driver/mysql" // или другой драйвер БД
)

func main() {
	// 1. Инициализация подключения к БД
	var err error
	db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname")
	if err != nil {
		log.Fatal("Failed to connect to database:", err)
	}
	defer db.Close()

	// 2. Проверка соединения
	ctx := context.Background()
	if err := db.PingContext(ctx); err != nil {
		log.Fatal("Failed to ping database:", err)
	}

	// 3. Выполнение запроса
	age := 27
	rows, err := db.QueryContext(ctx, "SELECT name FROM users WHERE age=?", age)
	if err != nil {
		log.Fatal("Query failed:", err)
	}
	defer rows.Close()

	// 4. Обработка результатов
	names := make([]string, 0)
	for rows.Next() {
		var name string
		if err := rows.Scan(&name); err != nil {
			log.Printf("Scan error (skipping row): %v", err) // Не прерываем выполнение
			continue
		}
		names = append(names, name)
	}

	// 5. Проверка ошибок после итерации
	if err := rows.Err(); err != nil {
		log.Fatal("Rows error:", err)
	}

	// 6. Проверка ошибок закрытия (более аккуратная обработка)
	if err := rows.Close(); err != nil {
		log.Printf("Warning: error closing rows: %v", err) // Не фатальная ошибка
	}

	// 7. Вывод результатов
	if len(names) > 0 {
		fmt.Printf("%s are %d years old\n", strings.Join(names, ", "), age)
	} else {
		fmt.Printf("No users found at age %d\n", age)
	}
}

func (*DB) QueryRow

func (db *DB) QueryRow(query string, args ...any) *Row

QueryRow выполняет запрос, который, как ожидается, вернет не более одной строки. QueryRow всегда возвращает значение, не являющееся нулем. Ошибки откладываются до вызова метода Row’s Scan. Если запрос не выбрал ни одной строки, то *Row.Scan вернет ErrNoRows. В противном случае *Row.Scan сканирует первую выбранную строку и отбрасывает остальные.

QueryRow внутренне использует context.Background;

func (*DB) QueryRowContext

func (db *DB) QueryRowContext(ctx context.Context, query string, args ...any) *Row

QueryRowContext выполняет запрос, который, как ожидается, вернет не более одной строки. QueryRowContext всегда возвращает значение non-nil. Ошибки откладываются до вызова метода Row’s Scan. Если запрос не выбрал ни одной строки, то *Row.Scan вернет ErrNoRows. В противном случае *Row.Scan сканирует первую выбранную строку и отбрасывает остальные.

Пример
package main

import (
	"context"
	"database/sql"
	"log"
	"time"

	_ "github.com/go-sql-driver/mysql" // Импорт драйвера БД
)

func main() {
	// Инициализация подключения к БД
	db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname")
	if err != nil {
		log.Fatalf("Failed to connect to database: %v", err)
	}
	defer db.Close()

	// Проверка соединения
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	if err := db.PingContext(ctx); err != nil {
		log.Fatalf("Failed to ping database: %v", err)
	}

	// Выполнение запроса
	id := 123
	var username string
	var created time.Time

	// Используем контекст с таймаутом для запроса
	queryCtx, queryCancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer queryCancel()

	err = db.QueryRowContext(queryCtx, 
		"SELECT username, created_at FROM users WHERE id=?", 
		id,
	).Scan(&username, &created)

	switch {
	case err == sql.ErrNoRows:
		log.Printf("No user found with id %d", id)
	case err != nil:
		log.Printf("Query error: %v", err) // Не используем Fatalf чтобы не прерывать программу
	default:
		log.Printf(
			"Username: %q, Account created on: %s", 
			username, 
			created.Format("2006-01-02 15:04:05"),
		)
	}
}

func (*DB) SetConnMaxIdleTime

func (db *DB) SetConnMaxIdleTime(d time.Duration)

SetConnMaxIdleTime устанавливает максимальное время, в течение которого соединение может простаивать.

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

Если d <= 0, соединения не закрываются из-за времени простоя соединения.

func (*DB) SetConnMaxLifetime

func (db *DB) SetConnMaxLifetime(d time.Duration)

SetConnMaxLifetime устанавливает максимальное количество времени, в течение которого соединение может быть использовано повторно.

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

Если d <= 0, соединения не закрываются из-за возраста соединения.

func (*DB) SetMaxIdleConns

func (db *DB) SetMaxIdleConns(n int)

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

Если MaxOpenConns больше 0, но меньше нового MaxIdleConns, то новый MaxIdleConns будет уменьшен, чтобы соответствовать лимиту MaxOpenConns.

Если n <= 0, простаивающие соединения не сохраняются.

По умолчанию максимальное количество незадействованных соединений в настоящее время равно 2. Это может измениться в будущем выпуске.

func (*DB) SetMaxOpenConns

func (db *DB) SetMaxOpenConns(n int)

SetMaxOpenConns устанавливает максимальное количество открытых соединений с базой данных.

Если MaxIdleConns больше 0, а новое значение MaxOpenConns меньше MaxIdleConns, то MaxIdleConns будет уменьшено, чтобы соответствовать новому ограничению MaxOpenConns.

Если n <= 0, то количество открытых соединений не ограничено. По умолчанию 0 (неограниченно).

func (*DB) Stats

func (db *DB) Stats() DBStats

Stats возвращает статистику базы данных.