
本文介绍在 Go 使用 database/sql + go-sql-driver/mysql 时,如何避免手动重复书写占位符(如 ?)和参数,通过切片展开(...)动态传入多个相同或分组重复的 SQL 参数,提升代码可维护性与可读性。
本文介绍在 go 使用 database/sql + go-sql-driver/mysql 时,如何避免手动重复书写占位符(如 `?`)和参数,通过切片展开(`...`)动态传入多个相同或分组重复的 sql 参数,提升代码可维护性与可读性。
在实际开发中,我们常遇到需对多个表或字段执行相同模糊匹配(如 LIKE '%keyword%')的场景。例如,需同时在 questions、books 和 sites 表的 description 字段中搜索关键词 "golang":
SELECT id FROM questions WHERE description LIKE '%?%' UNION SELECT id FROM books WHERE description LIKE '%?%' UNION SELECT id FROM sites WHERE description LIKE '%?%';
若参数多达 10 个甚至更多,硬编码 ? 占位符并逐个传参(如 stmt.Query(param, param, ..., param))极易出错且难以维护。
✅ 正确做法是:统一构造参数切片,再用 ... 展开调用。例如处理 10 个相同的搜索词:
keyword := "golang"
// 构建 10 个重复的参数
args := make([]interface{}, 10)
for i := range args {
args[i] = "%" + keyword + "%"
}
// 动态生成含 10 个 ? 的 SQL(注意:每个 ? 对应一个独立参数,不能写成 '%?%'!)
sql := `
SELECT id FROM questions WHERE description LIKE ?
UNION
SELECT id FROM books WHERE description LIKE ?
UNION
SELECT id FROM sites WHERE DESCRIPTION LIKE ?
-- ... 继续添加其他 UNION 分支,确保 ? 数量 = len(args)
`
stmt, err := db.Prepare(sql)
if err != nil {
log.Fatal(err)
}
defer stmt.Close()
rows, err := stmt.Query(args...)
if err != nil {
log.Fatal(err)
}⚠️ 关键注意事项:
- '%?%' 是错误写法:MySQL 驱动不支持在字符串字面量中嵌入 ? 占位符。? 必须作为独立参数出现,匹配值应预先拼接好通配符(如 "%" + keyword + "%");
- 所有 ? 占位符数量必须严格等于 args 切片长度,否则触发 sql: expected X arguments, got Y 错误;
- 若存在多组重复参数(如 10 个 keyword + 3 个 status),可分别构建子切片再合并:
var args []interface{} args = append(args, repeatParam("%"+keyword+"%", 10)...) args = append(args, repeatParam("active", 3)...) stmt.Query(args...)其中 repeatParam(val interface{}, n int) 是封装好的辅助函数。
? 进阶建议:对于复杂、动态的多表 UNION 查询,可考虑使用查询构建器(如 squirrel)或模板化 SQL 生成,进一步解耦逻辑与 SQL 结构。但对多数场景,合理使用 []interface{} + ... 已足够简洁、安全且符合 Go 的惯用风格。


















