必须先调用sql.Open获取*sql.DB但不立即建连,首次Query/Exec才触发连接;需导入驱动、检查Open和Ping的error;Query用于多行、QueryRow用于单行;Exec执行增删改并获取结果;事务须显式Commit/Rollback。

如何用 database/sql 连接数据库并执行查询
必须先调用 sql.Open 获取 *sql.DB,但它不立即建连;真正触发连接的是第一次执行查询(如 Query 或 Exec)。常见错误是忽略返回的 error,导致后续操作 panic。
-
sql.Open的第一个参数是驱动名(如"mysql"、"postgres"),需提前导入对应驱动(如_ "github.com/go-sql-driver/mysql") - 第二个参数是数据源字符串(DSN),格式因驱动而异:
"user:pass@tcp(127.0.0.1:3306)/dbname?parseTime=true" - 务必调用
db.Ping()主动验证连接是否可用,否则首次查询失败时才暴露问题
db, err := sql.Open("mysql", "root:@tcp(localhost:3306)/test")
if err != nil {
log.Fatal(err)
}
if err := db.Ping(); err != nil {
log.Fatal("failed to connect:", err)
}
Query 和 QueryRow 的区别与选用场景
Query 用于返回多行结果(如 SELECT 多条记录),返回 *sql.Rows;QueryRow 专为「预期只有一行」设计(如 SELECT ... LIMIT 1 或聚合函数),自动调用 Scan 并处理 sql.ErrNoRows。
- 用
QueryRow时,即使 SQL 本意是查一行,若实际无结果,Scan会返回sql.ErrNoRows,不是nil -
Query必须显式调用rows.Close(),否则连接不会归还到连接池——这是最常被漏掉的资源泄漏点 - 不要对同一
*sql.Rows多次调用Scan;每次Next()移动游标,需在循环中配对使用
var name string
err := db.QueryRow("SELECT name FROM users WHERE id = ?", 123).Scan(&name)
if err == sql.ErrNoRows {
// 处理未找到
} else if err != nil {
// 处理其他错误
}插入、更新、删除必须用 Exec,不能用 Query
Exec 专门处理不返回结果集的操作(INSERT/UPDATE/DELETE),它返回 sql.Result,可获取影响行数或最后插入 ID;用 Query 执行这类语句虽可能不报错,但会浪费连接、阻塞连接池,且无法获取执行结果。
-
Exec不支持查询缓存,也不走查询分析器优化路径,纯命令执行 - 获取自增 ID 要用
result.LastInsertId(),但注意:SQLite 和某些 MySQL 配置下该值可能不可靠,PostgreSQL 则根本不支持,得改用RETURNING子句配合QueryRow - 批量插入别拼 SQL 字符串,用
VALUES (?, ?), (?, ?)占位符 + 多参数传入,避免 SQL 注入和语法错误
res, err := db.Exec("INSERT INTO users(name, email) VALUES(?, ?)", "Alice", "a@example.com")
if err != nil {
log.Fatal(err)
}
id, _ := res.LastInsertId() // 注意:PostgreSQL 不适用事务处理必须显式 Commit 或 Rollback
db.Begin() 返回的 *sql.Tx 是独立连接,所有操作都绑定在其上;忘记 Commit 或 Rollback 会导致连接长期占用、锁表、甚至连接池耗尽。
立即学习“go语言免费学习笔记(深入)”;
- 推荐用
defer tx.Rollback()开头,再在成功路径末尾tx.Commit()—— 这样能确保无论中间哪步出错,事务都会回滚 - 事务内不能再用原始
*sql.DB执行语句,否则会脱离事务上下文 - 事务超时由驱动和数据库决定,
database/sql本身不提供超时控制,需靠上下文(context.WithTimeout)传入BeginTx
tx, err := db.Begin()
if err != nil {
return err
}
defer tx.Rollback()
_, err = tx.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", 100, 1)
if err != nil {
return err
}
return tx.Commit()
事务的边界容易被忽视:比如在函数里开了事务,但调用方没处理 panic,defer 就不会触发 Rollback;更稳妥的做法是把 Rollback 放在 recover 之后,或者统一用带 context 的 BeginTx 配合超时兜底。










