Go 的 database/sql 遇到非法 SQL 时不 panic 而是延迟报错,需用 sqlc/goose 在编译期或迁移期校验语法,运行时通过驱动错误码(如 pgx 的 "42601"、MySQL 的 1064)区分语法错误,并避免字符串拼接,改用 sqlx.Named 或 squirrel 构建安全查询。

Go 的 database/sql 遇到非法 SQL 时不会 panic,但会返回 error
Go 标准库的 database/sql 对 SQL 语法错误的处理很“安静”:它不会提前校验语句合法性,而是把错误延迟到真正执行(Query、Exec 等)时才暴露。这意味着你写的拼接 SQL 一旦含错,运行时才崩,且错误信息往往只含数据库返回的原始提示(比如 PostgreSQL 的 ERROR: syntax error at or near "WHERE"),没有行号或上下文。
常见错误现象包括:
- 拼接 SQL 时漏空格,导致
SELECT*FROM users这类非法 token - 占位符数量/顺序与
Args不匹配,某些驱动(如pgx)可能报ERROR: bind message supplies 2 parameters, but prepared statement "" requires 3 - 用字符串拼接代替参数化查询,引号未转义,生成
WHERE name = 'O'Reilly'
用 sqlc 或 goose 等工具在编译期/迁移期捕获语法问题
依赖运行时暴露 SQL 错误太晚。更靠谱的做法是把校验前移:
-
sqlc能解析 SQL 文件并生成 Go 代码,如果 SQL 有语法错误(比如少逗号、错关键字),sqlc generate直接失败,错误明确指向文件和行号 -
goose up执行迁移前会先 parse SQL 文件;若含非法语法(如CREATE TABLE foo (id INT,);多余逗号),会报error parsing SQL: near ";": syntax error - 不推荐手动写正则或 AST 解析来校验 SQL —— 各数据库方言差异大,得不偿失
示例:一个错 SQL 在 sqlc.yaml 中被引用后,sqlc generate 输出:query.sql:5:21: syntax error at or near "ORDER",立刻定位。
立即学习“go语言免费学习笔记(深入)”;
运行时捕获并区分 SQL 语法错误与业务错误
不能把所有 err != nil 都当连接失败或超时处理。尤其在调试阶段,需快速识别是不是自己写的 SQL 有问题:
- PostgreSQL 驱动(
lib/pq或jackc/pgx)可通过类型断言提取错误码:if pgErr, ok := err.(*pgconn.PgError); ok && pgErr.Code == "42601" { /* syntax_error */ } - MySQL 驱动(
go-sql-driver/mysql)可检查mysql.MySQLError.Number是否为1064(ER_PARSE_ERROR) - SQLite 驱动(
mattn/go-sqlite3)错误信息通常含near "...": syntax error字样,可用strings.Contains(err.Error(), "syntax error")快速判断(不严谨但够用) - 注意:不要依赖错误字符串全匹配 —— 不同版本、不同语言环境返回内容可能变
避免手拼 SQL:用 sqlx.Named 或 squirrel 构建动态查询
90% 的语法错误来自字符串拼接。与其反复检查引号、空格、括号,不如让库帮你兜底:
-
sqlx.Named支持命名参数,自动处理类型和转义:sqlx.Named("SELECT * FROM users WHERE age > :min_age", map[string]interface{}{"min_age": 18}),即使min_age是字符串也安全 -
squirrel提供链式构建:squirrel.Select("*").From("users").Where(squirrel.Eq{"status": "active"}),最终生成合法 SQL,拼错直接编译失败(比如写成.Whre) - 慎用
fmt.Sprintf拼接表名/列名 —— 这些无法参数化,必须白名单校验或使用sqllit等专用转义函数
一个典型坑:用 fmt.Sprintf("SELECT * FROM %s", userTable),若 userTable = "users; DROP TABLE users;",就不是语法错误,而是注入了 —— 所以语法校验不能替代输入过滤。
真正难的不是识别语法错误,而是当错误来自嵌套子查询、CTE 或 JSON 函数时,数据库返回的提示常极度简略。这时候得靠日志里打出完整 SQL(脱敏后)+ 驱动错误码,再贴到 psql/mysql 客户端里逐段试。










