sqlx.In不能直接用于INSERT INTO VALUES,因其仅生成占位符且不支持多行值展开;正确方式是用sqlx.NamedExec配合结构体切片,由sqlx自动展开为多组VALUES,并通过Rebind适配驱动语法。

sqlx.In 为什么不能直接用在 INSERT INTO ... VALUES 里
因为 sqlx.In 只负责参数绑定和占位符生成,它不修改 SQL 结构。如果你写 db.Exec("INSERT INTO users(name, age) VALUES (?, ?)", args...) 再套 sqlx.In,会报错:数据库驱动不认识 ? 和 IN 混用的语法,尤其 PostgreSQL 用 $1、SQLite 用 ?,而批量插入需要的是多组 (?, ?), (?, ?),不是单个 IN 列表。
用 sqlx.NamedExec + struct 切片最稳妥
这是 sqlx 官方推荐且兼容性最好的方式,避免手拼 SQL、类型错位、SQL 注入风险。核心是让 sqlx 自动展开结构体切片为多行值。
- 定义结构体字段名必须和 SQL 中列名(或命名参数名)一致,区分大小写
- 调用
sqlx.NamedExec时传入原始 SQL 和 struct 切片,不要展开成单个参数 - PostgreSQL 需开启
sqlx.DB.BindNamed(sqlx.DOLLAR),MySQL/SQLite 默认用问号即可
type User struct {
Name string `db:"name"`
Age int `db:"age"`
}
users := []User{{"alice", 25}, {"bob", 30}}
_, err := db.NamedExec("INSERT INTO users(name, age) VALUES (:name, :age)", users)
手动拼 VALUES 占位符时必须控制批次大小
当数据量极大(如 > 5000 行)或需精细控制事务边界时,得自己构造 (?, ?), (?, ?), ...。但别无脑拼——不同数据库有最大参数数限制:MySQL 默认 65535,PostgreSQL 默认 65535,SQLite 默认 999。超出就报 too many SQL variables 或类似错误。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 按每批 100–500 行切分原始数据,具体看字段数:10 列 × 500 行 = 5000 参数,留余量
- 用
sqlx.In生成占位符串时,要配合sqlx.Rebind适配目标方言,例如sqlx.Rebind(sqlx.POSTGRES, query) - 千万别用
fmt.Sprintf拼接值——绕过参数绑定等于裸奔
事务 + Prepare 提升大批量性能但要注意连接复用
单纯 NamedExec 对几千条还行,但压测下每条都走 prepare → exec → close 流程太重。可显式用 *sqlx.Tx 包一层,并对语句做一次 PrepareNamed 复用 Stmt。
- PrepareNamed 返回的
*sqlx.NamedStmt是线程安全的,可跨 goroutine 复用 - 但不能在多个不同
*sqlx.DB实例间共享;也不能在事务提交/回滚后继续用该 stmt - 如果用了连接池(默认开启),stmt 的生命周期应与事务一致,避免 “statement not found” 错误
真正容易被忽略的是:PostgreSQL 在事务中多次执行同一预编译语句时,若参数类型发生隐式变化(比如某次传了 nil,另一次传了 int),会导致 plan cache 失效甚至报错 cached plan must not change result type —— 这时候得统一字段类型或改用非预编译路径。










