直接用原生sql可行但必须参数化查询防注入;gin不提供db能力,真正执行靠database/sql、sqlx或gorm;选库决定写法、防护与调试方式。

直接用原生 SQL 是可行的,但必须用参数化查询,否则大概率被注入。 Gin 本身不提供数据库能力,它只负责路由和上下文;真正执行 SQL 的是 database/sql、sqlx 或 GORM 底层驱动。你选哪个库,决定了怎么写、怎么防、怎么 debug。
用 database/sql 执行原生 SQL 最简路径
这是 Go 标准库方案,不依赖 ORM,适合轻量或强定制场景。关键点不是“能不能写”,而是“连接要不要复用”“错误要不要提前判”“参数要不要占位”。
-
sql.Open只是初始化 driver,不建真实连接;db.Ping()才真连一次,上线前必须加 - 所有用户输入必须走
?占位符,禁止字符串拼接,例如:"SELECT * FROM user WHERE id = ?"✅,"SELECT * FROM user WHERE id = " + idStr❌ -
Exec用于 INSERT/UPDATE/DELETE,返回sql.Result;Query或QueryRow用于 SELECT,必须调用rows.Close()或row.Scan()否则连接泄漏 - 结构体字段名和 SQL 列名不自动映射,得手动
Scan(&u.ID, &u.Username, &u.Password)
GORM.Raw() 和 sqlx.Queryx() 的行为差异
两者都支持原生 SQL,但处理结果方式不同:GORM 默认尝试把结果塞进 struct 字段(按 tag 或字段名),sqlx 则靠反射+列名匹配,更宽松也更易出错。
-
GORM.Raw("SELECT id, name FROM users").Scan(&users)要求users是切片,且 struct 有ID、Name字段(或对应gorm:"column:id"tag) -
sqlx.Select(&users, "SELECT id, name FROM users")会按查询返回的列名(如id小写)去匹配 struct 字段,若字段是Id且没 tag,就扫不进值 -
GORM.Exec()不校验 SQL 语法,错在运行时才报;sqlx.MustExec()会在编译期提示语句无效(需配合sqlx.Rebind()处理方言)
常见报错:为什么 sql: expected 1 arguments, got 2
这是占位符数量和传参数量对不上,90% 出现在 WHERE 条件动态拼接时。比如你想查多个 ID:"SELECT * FROM user WHERE id IN (?)".Scan(&users, []int{1,2,3}) —— 这会炸,因为 ? 只代表一个值,不是一组。
- 正确做法是动态生成占位符串:
"SELECT * FROM user WHERE id IN (" + strings.Repeat("?,", len(ids)-1) + "?) ",再展开ids... - 或者用
sqlx.In()(sqlx提供)或GORM.Where("id IN ?", ids)(GORM 封装过) - 别信“用
fmt.Sprintf拼 ID 列表”,哪怕你过滤了数字,只要没进参数绑定,就是注入温床
事务里混用原生 SQL 和 GORM 方法要小心
如果你在 gorm.Session(&gorm.Session{NewDB: true}) 或 sqlx.Beginx() 里同时调 db.Exec 和 gorm.Create(),它们默认不在同一个连接上,事务会失效。
- GORM 事务对象
*gorm.DB必须透传到底层原生操作:用db.Statement.ConnPool拿到*sql.Conn,再用它跑ExecContext -
sqlx更直白:事务对象*sqlx.Tx本身就带Exec/Query方法,直接用就行 - 最稳的做法:整个事务只用一种风格,要么全 GORM,要么全
database/sql原生,别交叉
参数绑定不是可选项,是保命线;连接池配置、超时控制、日志开关这些看似外围的点,在压测或异常时才会暴露成单点故障。别等线上 too many connections 了才翻文档。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











