参数化查询是防止sql注入的唯一可靠解法,它由数据库驱动自动处理值的编码与转义,避免引号冲突和跨库兼容问题;手动转义仅限ddl动态对象等极少数场景且须严格限定字符范围。

用参数化查询代替字符串拼接是唯一可靠解法
手动转义单引号、反斜杠或下划线,看似能绕过报错,实则埋下SQL注入和跨库兼容隐患。真正出问题的不是数据本身,而是把用户输入直接拼进SQL字符串里——比如'O'Connor'会让解析器在第一个'闭合后就中断,后续O'Connor被当成裸标识符报错。
参数化查询让数据库驱动接管值的编码:传入"O'Connor & Co.",驱动自动按协议打包为二进制参数,不经过SQL词法分析,也就不存在“引号冲突”或“反斜杠吃掉下一个字符”的问题。
- Python(psycopg2):
cursor.execute("INSERT INTO users (name) VALUES (%s)", ("O'Connor & Co.",)) - Java(JDBC):
PreparedStatement ps = conn.prepareStatement("INSERT INTO users (name) VALUES (?)"); ps.setString(1, "O'Connor & Co."); - Node.js(mysql2):
connection.execute("INSERT INTO users (name) VALUES (?)", ["O'Connor & Co."]); - C#(SqlClient):
cmd.Parameters.AddWithValue("@name", "O'Connor & Co.");
注意:只要有一处拼接,整条语句就失效。例如f"INSERT INTO t (id) VALUES ({user_id})"混用,哪怕其他字段用参数,也等于放弃防护。
为什么_和%在INSERT里根本不用转义
_和%只是LIKE子句里的通配符,在INSERT语句中它们就是普通字符。写INSERT INTO logs (msg) VALUES ('error_code_404');完全合法,数据库原样存储,不会报错也不会变形。
真正需要处理的场景,是后续用LIKE去查这些字段时——比如WHERE msg LIKE 'error%'会匹配所有error开头的记录,但WHERE msg LIKE 'error_code_404'会错误地匹配errorAcodeB404这类字符串。
- 解决办法是显式加
ESCAPE子句,例如WHERE msg LIKE 'error\_code\_404' ESCAPE '\' - 转义符选
!比\更安全,避免与编程语言字符串转义冲突:WHERE msg LIKE 'error!_code!_404' ESCAPE '!' - 如果业务只需精确匹配,直接用
=而非LIKE,彻底避开通配符逻辑
SQL Server里单引号必须写成两个'',但别手写
SQL Server规定字符串内单引号需用两个连续单引号表示,即'It''s a test'才合法。若手动拼接,漏掉一个'就会触发ERROR: incorrect syntax near 's'。
更麻烦的是,不同客户端对反斜杠\的处理不一致:JDBC可能吃掉一层,而SQL Server本身不认\'作为转义形式,导致本地测试通过、上线后报错。
- 绝对不要写
"INSERT INTO t (v) VALUES ('" + s.replace("'", "''") + "');" - 改用
SqlCommand参数绑定:cmd.Parameters.AddWithValue("@v", s);,驱动自动完成''替换 - 若必须动态生成DDL(如
CREATE TABLE),再考虑REPLACE(s, "'", "''"),且仅限该场景
手动转义只在极少数场景下可用,且必须严格限定范围
参数化无法覆盖的边界情况只有两类:一是构建动态表名/列名(DDL语句),二是ORM底层封装层未透传参数(如Django extra()或SQLAlchemy text()未绑定变量)。此时才考虑手动处理,但必须满足三个条件:
- 仅处理明确已知的字符:SQL Server只处理
'→'';MySQL在NO_BACKSLASH_ESCAPES关闭时可处理\',但开启后必须用'' - 禁止对
_、%、&等非语法冲突字符做无谓替换——它们不影响INSERT解析 - 永远不处理Unicode控制字符(如U+2028行分隔符),这类字符应由应用层清洗,而非塞进SQL语句
最易被忽略的一点:即使用了参数化,LIKE中的通配符语义仍由SQL引擎执行。传入"admin_test"给WHERE name LIKE ?,依然会当通配符用——必须把转义逻辑写进SQL模板,例如WHERE name LIKE ? ESCAPE '!',再传"admin!_test"。











