Go初级项目如何操作数据库_Go数据库CRUD实战

10次阅读

最简路径是用database/sql搭配pgx/v5(postgresql)或go-sql-driver/mysql(MySQL),注意sql.Open不建连、必须db.Ping验证、及时db.Close、防sql注入、正确处理sql.ErrNoRows、事务用defer回滚。

Go初级项目如何操作数据库_Go数据库CRUD实战

database/sql 连 PostgreSQL 或 MySQL 最简路径

Go 没有内置 ORM,但标准库 database/sql 足够支撑初级项目的 CRUD。关键不是选多“高级”的框架,而是避开连接泄漏、SQL 注入和空指针这三类高频问题。

以 PostgreSQL 为例,推荐用 pgx/v5 驱动(比 lib/pq 更现代),MySQL 则用 go-sql-driver/mysql。两者都兼容 database/sql 接口,切换成本低。

  • sql.Open 只是初始化驱动,不校验连接;真正建连发生在第一次 db.Querydb.Exec
  • 必须调用 db.Close() —— 不然进程退出前连接不会释放,本地开发容易忽略,上线后连接数爆满
  • 连接字符串里避免硬编码密码:用环境变量(如 os.Getenv("DB_URL"))或配置文件
package main 

import ( "database/sql" "log" _ "github.com/jackc/pgx/v5" )

func main() { db, err := sql.Open("pgx", "postgres://user:pass@localhost:5432/mydb") if err != nil { log.Fatal(err) } defer db.Close() // 必须放这里,不是在函数末尾随便写

err = db.Ping() // 主动触发一次连接测试 if err != nil {     log.Fatal(err) }

}

QueryRowQuery 的边界在哪

初学者常混淆单行 vs 多行查询的处理方式,导致 panic 或漏读数据。

  • db.QueryRow() 用于「预期最多一行」的场景(如 select * FROM users WHERE id = ?),必须调用 .Scan(),否则行被丢弃且无提示
  • db.Query() 返回 *sql.Rows,必须循环 rows.Next() 并在结束后调用 rows.Close(),否则连接被占用
  • 如果 QueryRow().Scan() 找不到数据,会返回 sql.ErrNoRows,不是 nil 错误 —— 忘判这个是线上空指针主因
var name string err := db.QueryRow("SELECT name FROM users WHERE id = $1", 123).Scan(&name) if err == sql.ErrNoRows {     // 处理不存在的情况 } else if err != nil {     log.Fatal(err) } // name 现在可用

INSERT 时如何安全获取自增 ID 或 UUID

PostgreSQL 和 MySQL 对返回插入 ID 的语法不同,硬写 SQL 容易出错。

  • PostgreSQL 推荐用 RETURNING:直接在 INSERT 语句末尾加 RETURNING id,再用 QueryRow().Scan()
  • MySQL 用 LAST_INSERT_ID(),但必须确保在同一个连接上执行(db.Exec 后立刻 db.QueryRow("SELECT LAST_INSERT_ID()")
  • 更稳妥的做法是业务层生成 UUID(如 github.com/google/uuid),INSERT 时显式传入,彻底规避数据库依赖
// PostgreSQL 示例 var newID int err := db.QueryRow(     "INSERT INTO users(name, email) VALUES($1, $2) RETURNING id",     "Alice", "a@example.com", ).Scan(&newID) if err != nil {     log.Fatal(err) }

事务里忘了 tx.Commit()tx.Rollback() 怎么办

事务不结束,连接就卡在“in transaction”状态,后续操作可能被锁住或超时。Go 没有类似 pythonwith 自动管理,得靠代码结构兜底。

  • 不要把 tx.Commit() 写在 if err == nil 分支末尾 —— 中间加新逻辑容易漏掉
  • 统一用 defer func() 匿名函数包裹回滚,再在成功时显式标记并跳过
  • 所有 tx.Query / tx.Exec 都必须用 tx 对象,不能混用 db,否则不在事务内
tx, err := db.Begin() if err != nil {     log.Fatal(err) } defer func() {     if r := recover(); r != nil {         tx.Rollback()         panic(r)     } }() if err := tx.QueryRow("INSERT ...").Scan(&id); err != nil {     tx.Rollback()     log.Fatal(err) } if err := tx.Commit(); err != nil {     log.Fatal(err) }

实际项目中,最常被忽略的是 rows.Close()db.Close() 的调用时机,以及对 sql.ErrNoRows 的显式判断 —— 这些不报编译错误,但一到并发或异常路径就暴露。

text=ZqhQzanResources