Описание типа Conn database/sql
Categories:
type Conn struct {
// содержит отфильтрованные или неэкспонированные поля
}
Conn представляет собой одно соединение с базой данных, а не пул соединений с базой данных. Предпочтительнее запускать запросы из БД, если нет особой необходимости в постоянном соединении с одной базой данных.
Conn должен вызвать Conn.Close, чтобы вернуть соединение в пул баз данных, и может делать это одновременно с выполняющимся запросом.
После вызова Conn.Close все операции над соединением завершаются с ошибкой ErrConnDone.
func (*Conn) BeginTx
func (c *Conn) BeginTx(ctx context.Context, opts *TxOptions) (*Tx, error)
BeginTx начинает транзакцию.
Предоставленный контекст используется до тех пор, пока транзакция не будет зафиксирована или откачена. Если контекст будет отменен, пакет sql откатит транзакцию. Tx.Commit вернет ошибку, если контекст, предоставленный BeginTx, будет отменен.
Предоставленный параметр TxOptions является необязательным и может быть равен nil, если следует использовать значения по умолчанию. Если используется уровень изоляции не по умолчанию, который драйвер не поддерживает, будет возвращена ошибка.
func (*Conn) Close
func (c *Conn) Close() error
Close возвращает соединение в пул соединений. Все операции после Close возвращаются с ошибкой ErrConnDone. Close безопасно вызывать одновременно с другими операциями, и он будет блокироваться до тех пор, пока все остальные операции не завершатся. Может оказаться полезным сначала отменить любой используемый контекст, а затем вызвать Close непосредственно после него.
func (*Conn) ExecContext
func (c *Conn) ExecContext(ctx context.Context, query string, args ...any) (Result, error)
ExecContext выполняет запрос, не возвращая никаких строк. В качестве args используются любые параметры-заполнители в запросе.
Пример
package main
import (
"context"
"database/sql"
"log"
"os"
_ "github.com/go-sql-driver/mysql" // Импорт драйвера БД
)
func main() {
// 1. Инициализация подключения к БД
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. Получение выделенного соединения
conn, err := db.Conn(ctx)
if err != nil {
log.Fatal("Failed to get connection:", err)
}
defer func() {
if err := conn.Close(); err != nil {
log.Printf("Warning: error closing connection: %v", err)
}
}()
// 4. Выполнение транзакции
id := 41
tx, err := conn.BeginTx(ctx, nil)
if err != nil {
log.Fatal("Failed to begin transaction:", err)
}
// 5. Выполнение UPDATE в транзакции
result, err := tx.ExecContext(ctx,
`UPDATE balances SET balance = balance + 10 WHERE user_id = ?`,
id,
)
if err != nil {
tx.Rollback()
log.Fatal("Failed to update balance:", err)
}
// 6. Проверка количества измененных строк
rows, err := result.RowsAffected()
if err != nil {
tx.Rollback()
log.Fatal("Failed to get rows affected:", err)
}
if rows != 1 {
tx.Rollback()
log.Printf("Expected single row affected, got %d rows affected", rows)
os.Exit(1) // Более мягкое завершение чем log.Fatal
}
// 7. Фиксация транзакции
if err := tx.Commit(); err != nil {
log.Fatal("Failed to commit transaction:", err)
}
log.Println("Balance updated successfully")
}
func (*Conn) PingContext
func (c *Conn) PingContext(ctx context.Context) error
PingContext проверяет, что соединение с базой данных все еще живо.
func (*Conn) PrepareContext
func (c *Conn) PrepareContext(ctx context.Context, query string) (*Stmt, error)
PrepareContext создает подготовленный запрос для последующих запросов или выполнений. Несколько запросов или выполнений могут быть запущены одновременно из полученного утверждения. Вызывающая сторона должна вызвать метод *Stmt.Close оператора, когда он больше не нужен.
Предоставленный контекст используется для подготовки утверждения, а не для его выполнения.
func (*Conn) QueryContext
func (c *Conn) QueryContext(ctx context.Context, query string, args ...any) (*Rows, error)
QueryContext выполняет запрос, возвращающий строки, обычно SELECT. В args указываются любые параметры-заполнители запроса.
func (*Conn) QueryRowContext
func (c *Conn) QueryRowContext(ctx context.Context, query string, args ...any) *Row
QueryRowContext выполняет запрос, который, как ожидается, вернет не более одной строки. QueryRowContext всегда возвращает значение non-nil. Ошибки откладываются до вызова метода *Row.Scan. Если запрос не выбрал ни одной строки, то *Row.Scan вернет ErrNoRows. В противном случае *Row.Scan сканирует первую выбранную строку и отбрасывает остальные.
func (*Conn) Raw
func (c *Conn) Raw(f func(driverConn any) error) (err error)
Raw выполняет f, раскрывая базовое соединение драйвера на время выполнения f. DriverConn не должен использоваться вне f.
После того как f вернется и err не будет driver.ErrBadConn, Conn будет продолжать использоваться до вызова Conn.Close.
Разбираем метод Raw()
для работы с низкоуровневыми соединениями
Метод Raw()
в database/sql
позволяет получить прямое доступ к драйвер-специфичному соединению, минуя абстракции пакета database/sql
. Это нужно для использования специфичных функций драйвера, которые не доступны через стандартный API.
Что такое f
в этом контексте?
f
- это ваша функция-колбэк, которая получает доступ к нативному соединению драйвера. Она должна иметь сигнатуру:
func(driverConn any) error
Зачем это нужно?
- Использование специфичных функций драйвера (например, PostgreSQL-специфичные команды)
- Оптимизация производительности для критичных участков кода
- Работа с нестандартными возможностями БД
Простой пример (PostgreSQL)
package main
import (
"context"
"database/sql"
"fmt"
"log"
_ "github.com/lib/pq"
)
func main() {
db, err := sql.Open("postgres", "user=postgres dbname=test sslmode=disable")
if err != nil {
log.Fatal(err)
}
defer db.Close()
conn, err := db.Conn(context.Background())
if err != nil {
log.Fatal(err)
}
defer conn.Close()
// Используем Raw для доступа к нативному соединению PostgreSQL
err = conn.Raw(func(driverConn interface{}) error {
// Приводим к типу, который ожидаем (для PostgreSQL)
pgConn, ok := driverConn.(driver.Conn) // Здесь нужен ваш тип соединения
if !ok {
return fmt.Errorf("unexpected driver connection type")
}
// Пример: выполняем LISTEN для PostgreSQL-нотификаций
// Это специфичная функция PostgreSQL, недоступная через стандартный API
_, err := pgConn.(*pq.Conn).Exec("LISTEN channel_name")
return err
})
if err != nil {
log.Fatal(err)
}
}
Более практичный пример (MySQL)
package main
import (
"context"
"database/sql"
"log"
_ "github.com/go-sql-driver/mysql"
)
func main() {
db, err := sql.Open("mysql", "user:password@/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()
// Получаем низкоуровневое соединение MySQL
err = conn.Raw(func(driverConn interface{}) error {
// Приводим к ожидаемому типу соединения MySQL
mysqlConn, ok := driverConn.(*mysql.Conn) // Требуется import драйвера
if !ok {
return fmt.Errorf("expected mysql.Conn, got %T", driverConn)
}
// Используем специфичные методы MySQL
fmt.Println("Server version:", mysqlConn.GetServerVersion())
fmt.Println("Connection ID:", mysqlConn.GetConnectionID())
// Можно выполнить специфичные команды
return mysqlConn.Ping()
})
if err != nil {
log.Fatal(err)
}
}
Важные предупреждения:
-
Тип соединения зависит от драйвера:
- PostgreSQL:
*pq.Conn
- MySQL:
*mysql.Conn
- SQLite: зависит от используемого драйвера
- PostgreSQL:
-
Безопасность:
- Не сохраняйте соединение вне функции
f
- Все ошибки должны быть обработаны
- Не сохраняйте соединение вне функции
-
Совместимость:
- Код становится зависимым от конкретного драйвера
- Может сломаться при смене драйвера
Когда стоит использовать Raw()
?
- Когда вам нужен доступ к специфичным функциям СУБД
- Для оптимизации критических участков
- При работе с нестандартными возможностями (нотификации, специфичные команды)
В большинстве случаев стандартного API database/sql
достаточно, и Raw()
не понадобится.