sql包

Go 语言的标准库 ​database/sql​ 是一个通用的 SQL 数据库操作接口,提供统一的 API 支持多种关系型数据库(如 MySQL、PostgreSQL、SQLite 等)。其核心设计是 ​​解耦驱动与操作​​,开发者只需实现对应数据库的驱动(Driver),即可通过统一接口访问数据库。

sql 包必须与数据库驱动程序结合使用。有关驱动程序列表,请参阅:驱动列表arrow-up-right

核心设计

  • ​驱动接口(Driver)​​:由第三方库实现(如 github.com/go-sql-driver/mysql)。

  • ​连接池管理​​:自动管理数据库连接的创建、复用和释放。

  • ​接口统一​​:无论底层是哪种数据库,操作方式一致(如 Query, Exec 等)。

缓存机制:

数据库
驱动缓存特性
连接复用影响
刷新方式

​SQLite​

每个连接缓存表结构

DDL后需要刷新连接

PRAGMA 或重建连接

​MySQL​

服务器全局元数据缓存

会话内自动失效

FLUSH TABLES (可选)

​PgSQL​

每个会话缓存执行计划

表结构变更自动通知会话

无需手动刷新

​SQL Srv​

连接级缓存部分元数据

DDL可能导致缓存失效

重连或 sp_refreshsqlmodule

在测试代码中,最可靠的做法是始终通过查询数据库的系统表(如SQLite的pragma_table_info, MySQL的information_schema)来获取最新结构,而非依赖rows.Columns()这类可能缓存的方法。

1 变量

  • sql包提供了三个错误变量:

var ErrConnDone = errors.New("sql: connection is already closed")

var ErrNoRows = errors.New("sql: no rows in result set")

var ErrTxDone = errors.New("sql: transaction has already been committed or rolled back")
  • 变量说明

错误变量
触发条件
处理重点
典型场景

ErrConnDone

操作已释放的连接

确保连接在有效期内使用

手动管理连接、提前关闭rows

ErrNoRows

单行查询无结果

显式处理“无数据”逻辑

按ID查询不存在的记录

ErrTxDone

操作已结束的事务

严格管理事务生命周期

提交后误用事务对象

2 ​核心接口与结构​​

2.1 ​sql.DB​

  • ​作用​​:表示数据库连接池,是操作的入口。

  • ​创建方式​​:

    • sql.Open 的第一个参数是驱动名(需提前注册驱动)。

    • 第二个参数是数据库的 ​​DSN(数据源名称)​​,格式由驱动定义。dbname是可选的,可以在查询中使用 dbname.tablename 指定使用库。

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

  • ​功能​​:创建数据库连接池,通过驱动名称和数据源字符串初始化。

    • Open 可能只验证其参数而不创建连接 添加到数据库。要验证数据源名称是否有效,请调用Ping方法 。

    • 返回的 DB 可以安全地被多个 goroutine 并发使用,并维护自己的空闲连接池。因此,Open 函数应该只调用一次。很少需要关闭 DB。

2.1.2 (db *DB) Begin() (*Tx, error)​

  • 功能​​:开启事务,返回事务对象 *Tx

  • ​错误场景​​:

    • ​连接获取失败​​:连接池中无可用连接,且无法创建新连接(如达到 SetMaxOpenConns 限制)。

    • ​数据库不支持事务​​:某些数据库或驱动可能不支持事务。

    • ​上下文超时​​:底层驱动在获取连接时超时。

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

  • ​功能​​:支持上下文和事务选项(如隔离级别)的事务开启。

    • 如果要使用默认的 TxOptions 则值为nil。

  • ​错误场景​​:

    • Begin(),外加:

    • ​上下文取消​​:ctx 被取消或超时。

    • ​无效事务选项​​:opts 中指定了数据库不支持的事务隔离级别。

2.1.4 (db *DB) Close() error​

  • ​功能​​:关闭数据库连接池,释放所有连接。

  • ​错误场景​​:

    • ​连接关闭失败​​:某些连接在释放时发生错误(如数据库连接已断开)。

    • ​重复关闭​​:多次调用 Close() 可能返回未定义行为,但通常返回 nil

  • ​注意​​:通常应忽略此错误,但建议记录日志:

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

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

  • ​功能​​:执行不返回行的 SQL 操作(如 INSERT/UPDATE/DELETE)。

  • ​错误场景​​:

    • ​SQL 语法错误​​:查询语句有误。

    • ​参数类型不匹配​​:args 与 SQL 中的占位符类型不一致。

    • ​违反约束​​:如主键冲突、外键约束失败。

    • ​连接问题​​:执行期间连接断开。

    • ​上下文取消​​:ctx 超时或被取消。

  • ​示例​​:

2.1.7 (db *DB) Ping() error​

2.1.8 ​(db *DB) PingContext(ctx context.Context) error​

  • ​功能​​:检查数据库连通性。

  • ​错误场景​​:

    • ​数据库不可达​​:网络问题或数据库服务未运行。

    • ​认证失败​​:用户名/密码错误。

    • ​上下文取消​​:PingContextctx 超时或被取消。

  • ​示例​​:

2.1.9 (db *DB) Prepare(query string) (*Stmt, error)​

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

  • ​功能​​:创建预处理语句,提升重复执行效率。

    • 当不再需要语句时,调用方必须调用语句的 *Stmt.Close 方法。

  • ​错误场景​​:

    • ​SQL 语法错误​​:查询语句无效。

    • ​连接问题​​:无法获取连接或连接断开。

    • ​上下文取消​​:PrepareContextctx 超时或被取消。

  • ​示例​​:

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

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

  • ​功能​​:执行查询并返回多行结果(*Rows)。

  • ​错误场景​​:

    • Exec(),外加:

    • ​结果集迭代错误​​:在 rows.Next()rows.Scan() 时可能发生错误(但此错误在 Query 调用后才会返回)。

  • ​注意​​:必须调用 rows.Close() 释放连接。

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

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

  • ​功能​​:执行单行查询,返回 *Row(延迟错误检查到 Scan)。

  • ​错误场景​​:

    • 错误不会直接返回,需在 Scan() 时检查:

      • sql.ErrNoRows:查询结果为空。

      • 其他错误同 Query()

  • ​示例​​:

2.1.15 (db *DB) Stats() DBStats​

  • ​功能​​:返回连接池的统计信息(如打开连接数、空闲连接数)。

  • ​错误场景​​:无错误返回。

2.1.16 连接池配置方法

方法​

​参数类型​

​默认值​

​作用​

​适用场景​

​注意事项​

SetMaxOpenConns(n int)

int

无限制

设置连接池中​​最大打开的连接数​​(包括活跃和空闲连接)。

高并发场景下,防止数据库因连接数过多而过载。

- 设为 0 表示无限制。 - 应根据数据库的 max_connections 配置合理调整。

SetMaxIdleConns(n int)

int

2

设置连接池中​​最大空闲连接数​​(未被使用但保持打开的连接)。

平衡资源消耗与性能:空闲连接过多浪费资源,过少则需频繁创建新连接。

- 建议设为 SetMaxOpenConns 的 1/4 到 1/2。 - 若设为 0,则禁用空闲连接。

SetConnMaxLifetime(d time.Duration)

time.Duration

无限制

设置​​连接的最长存活时间​​(从创建到关闭的总时间,即使空闲也会超时关闭)。

数据库有连接存活时间限制(如 MySQL 的 wait_timeout),避免应用使用已被数据库关闭的连接。

- 建议设为略小于数据库的 wait_timeout。 - 设为 0 表示无限制。

SetConnMaxIdleTime(d time.Duration)

time.Duration

无限制

设置​​连接的最长空闲时间​​(连接在空闲状态下可保留的最长时间)。

释放长时间未使用的空闲连接,减少资源占用(适用于连接使用频率波动较大的场景)。

- 建议设为分钟级(如 5*time.Minute)。 - 设为 0 表示无限制。

2.2 sql.DBStats

  • 字段说明:

字段名
类型
说明

​MaxOpenConnections​

int

连接池允许的最大打开连接数(由 SetMaxOpenConns 设置)。

​OpenConnections​

int

当前已建立的连接总数(包括正在使用和空闲的连接)。

​InUse​

int

正在被使用的连接数(例如:活跃的查询或事务)。

​Idle​

int

当前空闲的连接数(可被复用的连接)。

​WaitCount​

int64

因连接池耗尽而等待获取连接的总次数(从 DB 创建开始累计)。

​WaitDuration​

time.Duration

所有等待获取连接的总耗时(反映因连接不足导致的延迟)。

​MaxIdleClosed​

int64

因超过 SetMaxIdleConns 设置的空闲连接数而被关闭的连接总数。

​MaxIdleTimeClosed​

int64

因超过 SetConnMaxIdleTime 设置的空闲时间而被关闭的连接总数。

​MaxLifetimeClosed​

int64

因超过 SetConnMaxLifetime 设置的连接最大存活时间而被关闭的连接总数。

  • 核心用途:​

    • ​监控连接池状态​​:实时查看连接使用情况(活跃、空闲、总量)。

    • ​性能调优​​:根据统计指标优化连接池参数配置。

    • ​故障排查​​:识别连接泄漏、资源竞争或配置不当问题。

2.3 sql.Result

Result 接口用于描述 ​​非查询类 SQL 操作​​(如 INSERT/UPDATE/DELETE)的执行结果,提供两个核心方法获取操作元数据:

  • 方法详解​​

方法
返回值
适用场景
常见错误场景

LastInsertId()

int64, error

插入操作后获取自增主键(如 MySQL 的 AUTO_INCREMENT)。

- 数据库/驱动不支持自增 ID(如 PostgreSQL 默认不返回,需用 RETURNING 子句)。 - 操作非插入语句(如 UPDATE)。

RowsAffected()

int64, error

获取 INSERT/UPDATE/DELETE 影响的行数。

2.4 sql.Row​

Row 是调用 DB.QueryRowTx.QueryRow 方法后返回的结果,用于处理​​预期最多返回一行​​的查询(如按主键查询)。其核心特点是:

  • ​单行处理​​:若查询返回多行,仅扫描第一行,其余丢弃。

  • ​延迟错误处理​​:查询错误不会立即返回,而是在调用 Scan()Err() 时暴露。​

  • 资源释放:即使不读取数据,也需调用 Scan()Err() 确保底层连接释放。

2.4.1 ​func (r *Row) Scan(dest ...any) error​

  • ​功能​​:将查询结果的列值复制到 dest 变量中。

  • ​行为​​:

    • ​成功​​:返回 nil,数据存入 dest

    • ​无结果​​:返回 sql.ErrNoRows

    • ​查询错误​​:返回执行查询时的错误(如 SQL 语法错误、连接失败)。

    • ​多行结果​​:静默丢弃后续行,仅扫描第一行。

  • ​必须调用​​:即使不关心结果,也应调用 Scan() 以确保释放资源。

2.4.2 ​func (r *Row) Err() error(Go 1.15+)​​

  • ​功能​​:直接返回查询过程中的错误,无需调用 Scan()

  • ​适用场景​​:需要提前检查错误(如日志记录),或在不扫描数据时获取错误信息。

  • 常见错误

错误场景​

​触发条件​

​处理方法​

sql.ErrNoRows

查询结果为空(如按不存在的 ID 查询)。

根据业务逻辑处理,如返回 404 或忽略。

​多行结果​

查询返回多行(如未在 SQL 中限制为单行)。

确保查询条件唯一(如使用主键),或在 SQL 中添加 LIMIT 1

​SQL 语法错误​

SQL 语句错误(如表名拼写错误)。

检查 SQL 语句,记录错误详情。

​连接错误​

数据库连接中断或超时。

重试逻辑或返回服务不可用状态。

​类型不匹配​

Scan 目标变量类型与数据库列类型不兼容(如字符串扫描到 int)。

确保目标变量类型与查询列类型匹配。

2.5 sql.Rows

Rows 表示一个查询结果集,用于处理​​多行数据​​的遍历与解析。其核心机制是通过游标逐行读取数据,支持多结果集处理(如存储过程或批量查询)。

2.5.1 ​核心方法​

​方法​

​功能​

​关键行为​

Close() error

关闭结果集,释放连接。

幂等操作,多次调用安全。必须显式或通过 defer 调用,避免连接泄漏。

Columns() ([]string, error)

返回列名列表。

Rows 已关闭,返回错误。通常在遍历前调用,用于动态处理结果集。

Next() bool

移动游标至下一行,准备扫描。

返回 true 表示有数据;false 表示无数据或出错,需检查 Err()

Scan(dest ...any) error

将当前行数据扫描到 dest 变量中。

dest 数量须与列数一致。支持类型自动转换,处理 NULL 需用 sql.Null* 类型。

Err() error

返回遍历过程中发生的错误(如网络中断、SQL 异常)。

应在 Next() 返回 false 后调用,以区分正常结束与错误。

NextResultSet() bool

移动到下一个结果集(如多语句查询)。

返回 true 表示存在下一个结果集,需再次调用 Next() 遍历其数据。

ColumnTypes() ([]*ColumnType, error)

返回列元数据(类型、精度、是否可为 NULL)。

用于动态解析结果集,部分驱动可能不支持某些元数据。

2.5.2 ​​标准使用流程​

  • 处理 NULL 值​**​

    • 使用 sql.Null* 类型或指针接收可能为 NULL 的列:

  • 多结果集处理​**​

    • 适用于批量查询或存储过程:

2.5.3 ​错误处理与陷阱​​

​错误场景​

​原因与示例​

​解决方案​

​未关闭 Rows

忘记调用 rows.Close(),导致连接泄漏。

始终使用 defer rows.Close()

Scan 参数不符​

dest 变量数量或类型与查询列不匹配。

检查 SQL 列数与 Scan 参数,使用 Columns() 动态适配。

​忽略 Err()

未在 Next() 循环后检查错误,导致隐藏的连接问题。

循环结束后调用 if err := rows.Err(); err != nil { ... }

​类型转换错误​

NULL 扫描到非指针类型,或大数值存入小类型(如 int64int8)。

使用 sql.Null* 类型或指针接收 NULL,验证数值范围。

​跨行引用 RawBytes

RawBytes 数据在下次 Next() 后被覆盖。

仅在当前行使用 RawBytes,或复制数据到独立 []byte

总结​

  • Rows 是处理多行查询的核心​​,需严格遵循 Open → Next → Scan → Close 的生命周期。

  • ​错误处理不可忽略​​:检查 Query 错误、Scan 错误及遍历结束后的 Err()

  • ​灵活应对复杂场景​​:动态列处理、多结果集遍历需结合 Columns()ColumnTypes()

2.6 sql.Stmt

sql.Stmt 类型表示一个预处理语句(Prepared Statement),用于高效执行重复的 SQL 操作。

  1. ​预处理 SQL​​ 将 SQL 语句预先编译并缓存,后续执行时只需传递参数,减少数据库解析开销。

  2. ​防止 SQL 注入​​ 通过参数化查询(占位符 ?$1),避免用户输入直接拼接 SQL。SQL 参数占位符 ?不能用于数据库名、表名、列名等标识符,它只能用于值。

  3. ​高效执行重复操作​​ 适用于批量插入、更新等需要多次执行相同 SQL 的场景。

方法
功能
返回类型

Exec(args ...any) (Result, error)

执行非查询操作(如 INSERT, UPDATE)并返回结果。

sql.Result

Query(args ...any) (*Rows, error)

执行查询操作(如 SELECT)并返回多行结果集。

*sql.Rows

QueryRow(args ...any) *Row

执行单行查询,返回一行结果。

*sql.Row

Close() error

释放预处理语句占用的资源(如数据库连接)。

error

ExecContext(ctx, args...)

支持上下文的 Exec 操作(可设置超时或取消)。

sql.Result, error

QueryContext(ctx, args...)

支持上下文的 Query 操作。

*sql.Rows, error

QueryRowContext(ctx, args...)

支持上下文的 QueryRow 操作。

*sql.Row

  • 使用流程:​

    1. ​准备语句​​:使用 DB.PrepareDB.PrepareContext 创建 Stmt

    2. ​执行操作​​:调用 ExecQueryQueryRow 方法(可多次执行)。

    3. ​关闭资源​​:通过 defer stmt.Close() 确保释放资源。

​​特性与注意事项​​

  1. ​并发安全​

    • Stmt 是并发安全的,多个 Goroutine 可同时调用同一 Stmt 的方法。

    • 内部通过连接池管理,无需用户处理底层连接。

  2. ​资源管理​

    • ​必须调用 Close()​:及时释放数据库资源(如连接、游标),避免泄漏。

    • ​长期复用优化​​:高频使用的 Stmt 可在应用生命周期内保持打开,避免重复准备。

  3. ​参数绑定​

    • 参数数量必须与 SQL 中的占位符数量一致。

    • 参数类型需与数据库列类型兼容(如字符串、数值、时间等)。

  4. ​错误处理​

    • ​预处理错误​​:Prepare 失败可能因 SQL 语法错误或连接问题。

    • ​执行错误​​:ExecQuery 失败可能因参数错误、约束冲突等。

2.7 sql.Tx

Tx 类型代表一个数据库事务,用于确保一系列操作的原子性(要么全部成功,要么全部回滚)。

  • 核心作用

    1. ​原子性保证​​:事务内的操作要么全部提交成功,要么全部回滚。

    2. ​隔离性​​:事务中的操作对其他事务暂时不可见,直到提交。

    3. ​资源管理​​:事务对象(*Tx)需显式提交或回滚,否则会导致连接泄漏。

  • 注意事项

    1. 事务必须调用对 Tx.Commit 或 Tx.Rollback 的结束。

    2. 调用 Tx.Commit 或 Tx.Rollback 后,对事务的所有操作都失败,并显示 ErrTxDone。

    3. 通过调用事务的 Tx.Prepare 或 Tx.Stmt 方法为事务准备的语句通过调用 Tx.Commit 或 Tx.Rollback 来关闭。

2.7.1 Commit() error

  • ​功能​​:提交事务,永久保存所有变更。

  • ​错误场景​​:

    • 网络中断或数据库连接失败。

    • 违反约束(如唯一键冲突)。

  • ​示例​​:

2.7.2 Rollback() error

  • ​功能​​:回滚事务,撤销所有未提交的变更。

  • ​幂等性​​:多次调用安全,但可能返回错误。

  • ​最佳实践​​:使用 defer tx.Rollback() 确保异常时回滚。

2.7.3 ExecExecContext

  • ​功能​​:执行非查询 SQL(如 INSERT/UPDATE/DELETE)。

  • ​错误场景​​:

    • SQL 语法错误。

    • 违反约束(如外键不存在)。

    • 事务已关闭(返回 ErrTxDone)。

  • ​示例​​:

2.7.4 Query 和 QueryContext​

  • ​功能​​:执行查询并返回多行结果(*Rows)。

  • ​资源释放​​:必须调用 rows.Close()

2.7.5 ​QueryRow 和 QueryRowContext

  • ​功能​​:执行单行查询,结果通过 Scan 获取。

  • ​错误处理​​:检查 Scan 的错误,如 ErrNoRows

2.7.6 Prepare 和 PrepareContext​

  • ​功能​​:创建事务作用域的预处理语句,事务提交/回滚后自动关闭。

  • ​示例​​:

2.7.7 Stmt 和 StmtContext​

  • ​功能​​:将全局预处理语句转换为事务专用语句。

  • ​适用场景​​:复用全局预处理语句,提升事务效率。

最佳实践

  1. ​资源管理​​:

    • 使用 defer tx.Rollback() 确保事务回滚。

    • 及时关闭 RowsStmt 对象。

  2. ​错误处理​​:

    • 检查所有操作的错误,尤其是 Commit

    • 使用 errors.Is(err, sql.ErrTxDone) 判断事务状态。

  3. ​性能优化​​:

    • 复用预处理语句(tx.Stmt)减少解析开销。

    • 合理设置事务隔离级别(通过 TxOptions)。

  4. ​上下文控制​​:

    • 使用 ExecContext/QueryContext 设置超时或取消。

2.8 sql.Txoptions

  • TxOptions 结构体用于配置数据库事务的隔离级别和访问模式(只读或读写)。它通常与 DB.BeginTx 方法结合使用,允许开发者对事务行为进行更精细的控制。

2.8.1 Isolation IsolationLevel​

  • ​作用​​:指定事务的隔离级别,决定事务在并发操作中的数据可见性和锁行为。

  • ​可选值​​:

常量
描述

LevelDefault

-1

使用数据库或驱动的默认隔离级别。

LevelReadUncommitted

0

读未提交:允许读取未提交的数据(可能脏读)。

LevelReadCommitted

1

读已提交:只能读取已提交的数据(避免脏读,允许不可重复读)。

LevelRepeatableRead

2

可重复读:保证同一事务内多次读取结果一致(防止不可重复读,可能幻读)。

LevelSerializable

3

串行化:最高隔离级别,完全串行执行事务(避免脏读、不可重复读、幻读)。

LevelLinearizable

4

线性化(部分数据库支持,如 MongoDB)。

  • ​默认行为​​:若 IsolationLevelDefault 或零值,使用数据库默认级别(通常为 LevelReadCommitted)。

2.8.2 ReadOnly bool

  • 作用​​:标记事务是否为只读。只读事务可能触发数据库优化(如避免写锁、减少日志记录)。

  • ​示例​​:

    • ReadOnly: true:事务内只能执行 SELECT 查询。

    • ReadOnly: false(默认):允许读写操作(INSERT, UPDATE, DELETE)。

2.9 Null*类型

database/sql包中,提供了一系列Null类型用于处理数据库中的NULL值。这些类型允许在Go中明确表示一个值可能为NULL,同时携带有效性信息。

  • 类型定义都类似:

类型
结构体定义
用途

sql.NullString

type NullString struct { String string; Valid bool }

处理可为NULL的字符串

sql.NullInt64

type NullInt64 struct { Int64 int64; Valid bool }

处理可为NULL的64位整数

sql.NullFloat64

type NullFloat64 struct { Float64 float64; Valid bool }

处理可为NULL的浮点数

sql.NullBool

type NullBool struct { Bool bool; Valid bool }

处理可为NULL的布尔值

sql.NullTime

type NullTime struct { Time time.Time; Valid bool }

处理可为NULL的时间

sql.NullByte

type NullByte struct { Byte byte; Valid bool }

处理可为NULL的字节

2.9.1 ​核心功能​​

  1. ​表示NULL值​​ 通过Valid字段标记值是否有效:

    • Valid = true:值有效,对应字段存储数据库中的实际值。

    • Valid = false:值为NULL,对应字段为零值。

  2. ​实现接口​​ 所有Null类型均实现了sql.Scannerdriver.Valuer接口,支持:

    • ​从数据库读取​​:自动处理NULLNull类型的转换。

    • ​写入数据库​​:根据Valid字段决定写入实际值或NULL

  1. 查询并处理NULL值​​

  1. 插入NULL值​​

2.10 sql.ColumnType

  1. Name() string​ 返回列名(如 "id", "name")。

  2. DatabaseTypeName() string​ 返回数据库定义的类型名称(如 VARCHAR, INT, DECIMAL)。

  3. Length() (int64, bool)​ 返回列类型的最大长度(适用于 VARCHAR(50) 等),第二个参数表示是否支持长度信息。

  4. DecimalSize() (precision, scale int64, ok bool)​ 返回 DECIMALNUMERIC 类型的精度(总位数)和小数位数,ok 表示是否支持。

  5. ScanType() reflect.Type​ 返回 Go 语言中用于接收该列值的类型(如 string, int32, float64, time.Time)。

  6. Nullable() (nullable, ok bool)​ 返回列是否允许 NULL 值,ok 表示驱动是否支持该元数据。

最后更新于