Описание типа database/sql DB
Categories:
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.
Подробнее о Conn
Работа с соединениями (Conn
) в database/sql
Соединение (Conn
) представляет собой одиночное подключение к базе данных, которое можно использовать как в уже открытом пуле соединений (DB
), так и для прямого управления подключением.
Основные концепции
-
Отличие от
DB
:DB
- это пул соединений (управляет множеством подключений)Conn
- одно конкретное соединение
-
Когда использовать:
- Когда нужно выполнить несколько операций в рамках одного соединения
- Для работы с сессионными переменными
- Для транзакций, требующих изоляции от других операций
Примеры использования
1. Получение соединения из пула
package main
import (
"context"
"database/sql"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql"
)
func main() {
db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Получаем отдельное соединение из пула
conn, err := db.Conn(context.Background())
if err != nil {
log.Fatal(err)
}
defer conn.Close() // Важно возвращать соединение в пул
// Используем соединение
var result int
err = conn.QueryRowContext(context.Background(), "SELECT 1 + 1").Scan(&result)
if err != nil {
log.Fatal(err)
}
fmt.Println("Result:", result)
}
2. Использование сессионных переменных
// Устанавливаем переменную сессии и используем её в запросе
err = conn.ExecContext(context.Background(), "SET @user_id = 123").Scan(&result)
if err != nil {
log.Fatal(err)
}
var userID int
err = conn.QueryRowContext(context.Background(), "SELECT @user_id").Scan(&userID)
fmt.Println("User ID:", userID) // Выведет: User ID: 123
3. Транзакции в отдельном соединении
// Начинаем транзакцию в этом соединении
tx, err := conn.BeginTx(context.Background(), &sql.TxOptions{Isolation: sql.LevelSerializable})
if err != nil {
log.Fatal(err)
}
// Выполняем операции в транзакции
_, err = tx.Exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
_, err = tx.Exec("UPDATE accounts SET balance = balance + 100 WHERE id = 2")
if err != nil {
tx.Rollback()
log.Fatal(err)
} else {
tx.Commit()
}
4. Прямое подключение (без пула)
Для прямого подключения без пула можно использовать драйвер напрямую, но это не рекомендуется:
package main
import (
"database/sql"
"database/sql/driver"
"fmt"
"log"
)
func main() {
// Получаем драйвер по имени
driver := sql.Driver(new(mysql.MySQLDriver))
// Создаем прямое соединение (не рекомендуется для production)
conn, err := driver.Open("user:password@tcp(localhost:3306)/dbname")
if err != nil {
log.Fatal(err)
}
defer conn.Close() // Закрываем соединение
// Преобразуем в тип Conn (если нужно)
if sqlConn, ok := conn.(driver.Conn); ok {
// Работаем с соединением...
}
}
Когда использовать Conn
вместо DB
?
- Сессионные переменные: Когда нужно сохранить состояние между запросами
- Временные таблицы: Которые видны только в текущем соединении
- LOCK TABLES: Когда нужно заблокировать таблицы на время сессии
- Точный контроль: Когда важно, чтобы несколько операций использовали одно соединение
Важные замечания
- Всегда закрывайте
Conn
с помощьюdefer conn.Close()
- Не держите соединения долго - возвращайте их в пул
- Для большинства случаев достаточно стандартного
DB
Conn
дороже в создании, чем операции черезDB
Пример комплексного использования:
func complexOperation(db *sql.DB) error {
// Получаем соединение
conn, err := db.Conn(context.Background())
if err != nil {
return err
}
defer conn.Close()
// Настраиваем сессию
if _, err := conn.ExecContext(context.Background(), "SET @start_time = NOW()"); err != nil {
return err
}
// Выполняем транзакцию
tx, err := conn.BeginTx(context.Background(), nil)
if err != nil {
return err
}
// Бизнес-логика...
if _, err := tx.Exec("INSERT INTO orders (...) VALUES (...)"); err != nil {
tx.Rollback()
return err
}
// Получаем время выполнения
var startTime string
conn.QueryRowContext(context.Background(), "SELECT @start_time").Scan(&startTime)
fmt.Println("Operation started at:", startTime)
return tx.Commit()
}
Используйте Conn
осознанно, только когда действительно нужно контролировать конкретное соединение. В большинстве случаев достаточно стандартного DB
.
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 возвращает статистику базы данных.