sql包
Go 语言的标准库 database/sql
是一个通用的 SQL 数据库操作接口,提供统一的 API 支持多种关系型数据库(如 MySQL、PostgreSQL、SQLite 等)。其核心设计是 解耦驱动与操作,开发者只需实现对应数据库的驱动(Driver),即可通过统一接口访问数据库。
sql 包必须与数据库驱动程序结合使用。有关驱动程序列表,请参阅:驱动列表。
核心设计:
驱动接口(Driver):由第三方库实现(如
github.com/go-sql-driver/mysql
)。连接池管理:自动管理数据库连接的创建、复用和释放。
接口统一:无论底层是哪种数据库,操作方式一致(如
Query
,Exec
等)。
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
作用:表示数据库连接池,是操作的入口。
创建方式:
db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname")
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
限制)。数据库不支持事务:某些数据库或驱动可能不支持事务。
上下文超时:底层驱动在获取连接时超时。
tx, err := db.Begin()
if err != nil {
log.Fatal("事务开启失败:", err)
}
2.1.3 (db *DB) BeginTx(ctx context.Context, opts *TxOptions) (*Tx, error)
功能:支持上下文和事务选项(如隔离级别)的事务开启。
如果要使用默认的
TxOptions
则值为nil。
错误场景:
同
Begin()
,外加:上下文取消:
ctx
被取消或超时。无效事务选项:
opts
中指定了数据库不支持的事务隔离级别。
ctx := context.Background()
tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})
if err != nil {
log.Fatal("事务开启失败:", err)
}
2.1.4 (db *DB) Close() error
功能:关闭数据库连接池,释放所有连接。
错误场景:
连接关闭失败:某些连接在释放时发生错误(如数据库连接已断开)。
重复关闭:多次调用
Close()
可能返回未定义行为,但通常返回nil
。
注意:通常应忽略此错误,但建议记录日志:
if err := db.Close(); err != nil {
log.Println("关闭数据库连接池失败:", err)
}
2.1.5 (db *DB) Exec(query string, args ...any) (Result, error)
(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
超时或被取消。
示例:
import (
"context"
"database/sql"
"log"
)
var (
ctx context.Context
db *sql.DB
)
func main() {
id := 47
result, err := db.ExecContext(ctx, "UPDATE balances SET balance = balance + 10 WHERE user_id = ?", id)
if err != nil {
log.Fatal(err)
}
rows, err := result.RowsAffected()
if err != nil {
log.Fatal(err)
}
if rows != 1 {
log.Fatalf("expected to affect 1 row, affected %d", rows)
}
}
2.1.7 (db *DB) Ping() error
2.1.8 (db *DB) PingContext(ctx context.Context) error
功能:检查数据库连通性。
错误场景:
数据库不可达:网络问题或数据库服务未运行。
认证失败:用户名/密码错误。
上下文取消:
PingContext
中ctx
超时或被取消。
示例:
if err := db.Ping(); err != nil {
log.Fatal("数据库连接异常:", err)
}
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 语法错误:查询语句无效。
连接问题:无法获取连接或连接断开。
上下文取消:
PrepareContext
中ctx
超时或被取消。
示例:
stmt, err := db.Prepare("SELECT * FROM users WHERE id = ?")
if err != nil {
log.Fatal("预处理失败:", err)
}
defer stmt.Close()
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()
释放连接。
rows, err := db.Query("SELECT * FROM users")
if err != nil {
log.Fatal("查询失败:", err)
}
defer 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()
。
示例:
var name string
err := db.QueryRow("SELECT name FROM users WHERE id = ?", 1).Scan(&name)
if errors.Is(err, sql.ErrNoRows) {
log.Println("用户不存在")
} else if err != nil {
log.Fatal("查询失败:", err)
}
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
type DBStats struct {
MaxOpenConnections int // Maximum number of open connections to the database.
// Pool Status
OpenConnections int // The number of established connections both in use and idle.
InUse int // The number of connections currently in use.
Idle int // The number of idle connections.
// Counters
WaitCount int64 // The total number of connections waited for.
WaitDuration time.Duration // The total time blocked waiting for a new connection.
MaxIdleClosed int64 // The total number of connections closed due to SetMaxIdleConns.
MaxIdleTimeClosed int64 // The total number of connections closed due to SetConnMaxIdleTime.
MaxLifetimeClosed int64 // The total number of connections closed due to SetConnMaxLifetime.
}
字段说明:
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)的执行结果,提供两个核心方法获取操作元数据:
type Result interface {
LastInsertId() (int64, error) // 获取自增 ID(如插入行的主键)
RowsAffected() (int64, error) // 获取受影响的行数
}
方法详解
LastInsertId()
int64
, error
插入操作后获取自增主键(如 MySQL 的 AUTO_INCREMENT
)。
- 数据库/驱动不支持自增 ID(如 PostgreSQL 默认不返回,需用 RETURNING
子句)。
- 操作非插入语句(如 UPDATE
)。
RowsAffected()
int64
, error
获取 INSERT
/UPDATE
/DELETE
影响的行数。
2.4 sql.Row
Row
是调用 DB.QueryRow
或 Tx.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 标准使用流程
rows, err := db.Query("SELECT id, name FROM users")
if err != nil {
log.Fatal("查询失败:", err)
}
defer rows.Close() // 确保资源释放
// 遍历每一行
for rows.Next() {
var id int
var name string
if err := rows.Scan(&id, &name); err != nil {
log.Fatal("扫描失败:", err)
}
fmt.Printf("ID: %d, Name: %s\n", id, name)
}
// 检查遍历过程中是否出错
if err := rows.Err(); err != nil {
log.Fatal("遍历错误:", err)
}
处理 NULL 值**
使用
sql.Null*
类型或指针接收可能为NULL
的列:
var name sql.NullString
var age *int
err := rows.Scan(&name, &age)
if name.Valid {
fmt.Println("Name:", name.String)
} else {
fmt.Println("Name: NULL")
}
if age != nil {
fmt.Println("Age:", *age)
} else {
fmt.Println("Age: NULL")
}
多结果集处理**
适用于批量查询或存储过程:
for {
// 处理当前结果集
for rows.Next() {
// Scan 数据...
}
if err := rows.Err(); err != nil {
log.Fatal(err)
}
// 跳转至下一个结果集
if !rows.NextResultSet() {
break
}
}
2.5.3 错误处理与陷阱
错误场景
原因与示例
解决方案
未关闭 Rows
忘记调用 rows.Close()
,导致连接泄漏。
始终使用 defer rows.Close()
。
Scan
参数不符
dest
变量数量或类型与查询列不匹配。
检查 SQL 列数与 Scan
参数,使用 Columns()
动态适配。
忽略 Err()
未在 Next()
循环后检查错误,导致隐藏的连接问题。
循环结束后调用 if err := rows.Err(); err != nil { ... }
。
类型转换错误
将 NULL
扫描到非指针类型,或大数值存入小类型(如 int64
→ int8
)。
使用 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 操作。
预处理 SQL 将 SQL 语句预先编译并缓存,后续执行时只需传递参数,减少数据库解析开销。
防止 SQL 注入 通过参数化查询(占位符
?
或$1
),避免用户输入直接拼接 SQL。高效执行重复操作 适用于批量插入、更新等需要多次执行相同 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
使用流程:
准备语句:使用
DB.Prepare
或DB.PrepareContext
创建Stmt
。执行操作:调用
Exec
、Query
或QueryRow
方法(可多次执行)。关闭资源:通过
defer stmt.Close()
确保释放资源。
// 1. 准备预处理语句
stmt, err := db.Prepare("INSERT INTO users (name, age) VALUES (?, ?)")
if err != nil {
log.Fatal("预处理失败:", err)
}
defer stmt.Close() // 确保关闭
// 2. 多次执行
users := []struct {
Name string
Age int
}{
{"Alice", 30},
{"Bob", 25},
}
for _, user := range users {
result, err := stmt.Exec(user.Name, user.Age)
if err != nil {
log.Fatal("执行失败:", err)
}
id, _ := result.LastInsertId()
fmt.Println("插入用户 ID:", id)
}
特性与注意事项
并发安全
Stmt
是并发安全的,多个 Goroutine 可同时调用同一Stmt
的方法。内部通过连接池管理,无需用户处理底层连接。
资源管理
必须调用
Close()
:及时释放数据库资源(如连接、游标),避免泄漏。长期复用优化:高频使用的
Stmt
可在应用生命周期内保持打开,避免重复准备。
参数绑定
参数数量必须与 SQL 中的占位符数量一致。
参数类型需与数据库列类型兼容(如字符串、数值、时间等)。
错误处理
预处理错误:
Prepare
失败可能因 SQL 语法错误或连接问题。执行错误:
Exec
或Query
失败可能因参数错误、约束冲突等。
2.7 sql.Tx
Tx
类型代表一个数据库事务,用于确保一系列操作的原子性(要么全部成功,要么全部回滚)。
核心作用:
原子性保证:事务内的操作要么全部提交成功,要么全部回滚。
隔离性:事务中的操作对其他事务暂时不可见,直到提交。
资源管理:事务对象(
*Tx
)需显式提交或回滚,否则会导致连接泄漏。
注意事项:
事务必须调用对 Tx.Commit 或 Tx.Rollback 的结束。
调用 Tx.Commit 或 Tx.Rollback 后,对事务的所有操作都失败,并显示 ErrTxDone。
通过调用事务的 Tx.Prepare 或 Tx.Stmt 方法为事务准备的语句通过调用 Tx.Commit 或 Tx.Rollback 来关闭。
2.7.1 Commit() error
功能:提交事务,永久保存所有变更。
错误场景:
网络中断或数据库连接失败。
违反约束(如唯一键冲突)。
示例:
if err := tx.Commit(); err != nil {
log.Fatal("提交失败:", err)
}
2.7.2 Rollback() error
功能:回滚事务,撤销所有未提交的变更。
幂等性:多次调用安全,但可能返回错误。
最佳实践:使用
defer tx.Rollback()
确保异常时回滚。
tx, _ := db.Begin()
defer tx.Rollback() // 提交后调用 Rollback 会返回 ErrTxDone,但无害
2.7.3 Exec 和
ExecContext
和
ExecContext功能:执行非查询 SQL(如 INSERT/UPDATE/DELETE)。
错误场景:
SQL 语法错误。
违反约束(如外键不存在)。
事务已关闭(返回
ErrTxDone
)。
示例:
result, err := tx.Exec("UPDATE users SET active=? WHERE id=?", true, 123)
if err != nil {
return err
}
2.7.4 Query 和 QueryContext
功能:执行查询并返回多行结果(
*Rows
)。资源释放:必须调用
rows.Close()
。
rows, _ := tx.Query("SELECT * FROM orders WHERE user_id=?", 123)
defer rows.Close()
2.7.5 QueryRow 和 QueryRowContext
功能:执行单行查询,结果通过
Scan
获取。错误处理:检查
Scan
的错误,如ErrNoRows
。
var total int
err := tx.QueryRow("SELECT SUM(amount) FROM payments").Scan(&total)
if errors.Is(err, sql.ErrNoRows) {
log.Println("无支付记录")
}
2.7.6 Prepare 和 PrepareContext
功能:创建事务作用域的预处理语句,事务提交/回滚后自动关闭。
示例:
stmt, _ := tx.Prepare("INSERT INTO logs (message) VALUES (?)")
defer stmt.Close() // 可选,事务结束会自动关闭
stmt.Exec("Transaction started")
2.7.7 Stmt 和 StmtContext
功能:将全局预处理语句转换为事务专用语句。
适用场景:复用全局预处理语句,提升事务效率。
updateStmt, _ := db.Prepare("UPDATE accounts SET balance = balance + ? WHERE id = ?")
txStmt := tx.Stmt(updateStmt)
txStmt.Exec(100.0, 456)
最佳实践
资源管理:
使用
defer tx.Rollback()
确保事务回滚。及时关闭
Rows
和Stmt
对象。
错误处理:
检查所有操作的错误,尤其是
Commit
。使用
errors.Is(err, sql.ErrTxDone)
判断事务状态。
性能优化:
复用预处理语句(
tx.Stmt
)减少解析开销。合理设置事务隔离级别(通过
TxOptions
)。
上下文控制:
使用
ExecContext
/QueryContext
设置超时或取消。
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_, err := tx.ExecContext(ctx, "DELETE FROM tmp_data")
2.8 sql.Txoptions
TxOptions
结构体用于配置数据库事务的隔离级别和访问模式(只读或读写)。它通常与DB.BeginTx
方法结合使用,允许开发者对事务行为进行更精细的控制。
type TxOptions struct {
Isolation IsolationLevel // 事务隔离级别
ReadOnly bool // 是否只读
}
2.8.1 Isolation IsolationLevel
作用:指定事务的隔离级别,决定事务在并发操作中的数据可见性和锁行为。
可选值:
LevelDefault
-1
使用数据库或驱动的默认隔离级别。
LevelReadUncommitted
0
读未提交:允许读取未提交的数据(可能脏读)。
LevelReadCommitted
1
读已提交:只能读取已提交的数据(避免脏读,允许不可重复读)。
LevelRepeatableRead
2
可重复读:保证同一事务内多次读取结果一致(防止不可重复读,可能幻读)。
LevelSerializable
3
串行化:最高隔离级别,完全串行执行事务(避免脏读、不可重复读、幻读)。
LevelLinearizable
4
线性化(部分数据库支持,如 MongoDB)。
默认行为:若
Isolation
为LevelDefault
或零值,使用数据库默认级别(通常为LevelReadCommitted
)。
2.8.2 ReadOnly bool
作用:标记事务是否为只读。只读事务可能触发数据库优化(如避免写锁、减少日志记录)。
示例:
ReadOnly: true
:事务内只能执行SELECT
查询。ReadOnly: false
(默认):允许读写操作(INSERT
,UPDATE
,DELETE
)。
2.9 Null*类型
database/sql
包中,提供了一系列Null
类型用于处理数据库中的NULL
值。这些类型允许在Go中明确表示一个值可能为NULL
,同时携带有效性信息。
类型定义都类似:
type NullBool struct {
Bool bool
Valid bool // Valid is true if Bool is not 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 核心功能
表示NULL值 通过
Valid
字段标记值是否有效:Valid = true
:值有效,对应字段存储数据库中的实际值。Valid = false
:值为NULL
,对应字段为零值。
实现接口 所有
Null
类型均实现了sql.Scanner
和driver.Valuer
接口,支持:从数据库读取:自动处理
NULL
到Null
类型的转换。写入数据库:根据
Valid
字段决定写入实际值或NULL
。
查询并处理NULL值
var user struct {
ID int
Name sql.NullString
Age sql.NullInt64
Deleted sql.NullBool
}
err := db.QueryRow("SELECT id, name, age, deleted FROM users WHERE id=?", 1).Scan(
&user.ID,
&user.Name,
&user.Age,
&user.Deleted,
)
if err != nil {
log.Fatal(err)
}
// 处理可能为NULL的字段
if user.Name.Valid {
fmt.Println("Name:", user.Name.String)
} else {
fmt.Println("Name: NULL")
}
if user.Deleted.Valid && user.Deleted.Bool {
fmt.Println("账户已注销")
}
插入NULL值
newUser := struct {
Name sql.NullString
Age sql.NullInt64
}{
Name: sql.NullString{String: "", Valid: false}, // 插入NULL
Age: sql.NullInt64{Int64: 30, Valid: true}, // 插入30
}
_, err := db.Exec("INSERT INTO users (name, age) VALUES (?, ?)",
newUser.Name,
newUser.Age,
)
if err != nil {
log.Fatal(err)
}
2.10 sql.ColumnType
Name() string
返回列名(如"id"
,"name"
)。
DatabaseTypeName() string
返回数据库定义的类型名称(如VARCHAR
,INT
,DECIMAL
)。
Length() (int64, bool)
返回列类型的最大长度(适用于VARCHAR(50)
等),第二个参数表示是否支持长度信息。
DecimalSize() (precision, scale int64, ok bool)
返回DECIMAL
或NUMERIC
类型的精度(总位数)和小数位数,ok
表示是否支持。
ScanType() reflect.Type
返回 Go 语言中用于接收该列值的类型(如string
,int32
,float64
,time.Time
)。
Nullable() (nullable, ok bool)
返回列是否允许NULL
值,ok
表示驱动是否支持该元数据。
package main
import (
"database/sql"
"fmt"
"log"
"time"
_ "github.com/go-sql-driver/mysql" // 使用 MySQL 驱动
)
func main() {
// 连接数据库
db, err := sql.Open("mysql", "user:password@/testdb?parseTime=true")
if err != nil {
log.Fatal("数据库连接失败:", err)
}
defer db.Close()
// 示例表结构:
// CREATE TABLE users (
// id INT PRIMARY KEY,
// name VARCHAR(50) NOT NULL,
// age INT,
// salary DECIMAL(10,2),
// created_at DATETIME,
// is_active BOOLEAN
// );
// 执行查询
rows, err := db.Query("SELECT * FROM users")
if err != nil {
log.Fatal("查询失败:", err)
}
defer rows.Close()
// 获取列元数据
columnTypes, err := rows.ColumnTypes()
if err != nil {
log.Fatal("获取列元数据失败:", err)
}
// 遍历每一列并打印详细信息
for _, col := range columnTypes {
fmt.Printf("===== 列名: %q =====\n", col.Name())
// 1. DatabaseTypeName: 数据库类型名称(如 VARCHAR, INT)
dbType := col.DatabaseTypeName()
fmt.Printf("数据库类型: %s\n", dbType)
// 2. Length: 列类型的最大长度(字符/字节)
length, hasLength := col.Length()
if hasLength {
fmt.Printf("长度: %d\n", length)
} else {
fmt.Println("长度: 不支持或未定义")
}
// 3. DecimalSize: DECIMAL/NUMERIC 类型的精度和小数位数
precision, scale, hasDecimal := col.DecimalSize()
if hasDecimal {
fmt.Printf("精度: %d, 小数位: %d\n", precision, scale)
} else {
fmt.Println("精度和小数位: 不适用于非数字类型")
}
// 4. ScanType: Go 语言中用于接收该列值的类型
scanType := col.ScanType()
fmt.Printf("Scan 类型: %s\n", scanType)
// 5. Nullable: 列是否允许 NULL 值
nullable, ok := col.Nullable()
if ok {
fmt.Printf("可为 NULL: %v\n", nullable)
} else {
fmt.Println("可为 NULL: 元数据不可用")
}
// 6. PrecisionScale: 同 DecimalSize(部分驱动可能实现不同)
// (此处仅为演示,实际与 DecimalSize 结果相同)
fmt.Println()
}
}
最后更新于