pymysql 查询遇到特殊字符?单引号、反斜杠等问题完整解决方案
标签:#Python #pymysql #MySQL #SQL注入 #后端踩坑
前言
在使用 pymysql 做业务开发时,经常遇到文本字段包含单引号 '、双引号 "、反斜杠 \、换行符等特殊字符。
直接使用 f‑string / str.format() 拼接 SQL,就会出现 SQL 语法报错,严重时还会带来 SQL 注入安全漏洞。
很多新手会有一个误区:pymysql 驱动会自动帮我把字符串转义处理好。
实际上 pymysql 不会处理你手动拼接进 SQL 字符串里的内容;只有通过参数化查询传入的变量,驱动才会自动完成转义。
本文结合真实业务场景,讲解报错根因、多种处理方案、以及生产环境避坑要点。
一、复现问题:单引号引发的SQL语法错误
假设数据库表 information,title 字段存储新闻标题,标题内部包含英文单引号。
示例数据:
SMS group's advanced technology upgrade
❌错误写法:直接 f‑string 拼接SQL
import pymysql
conn = pymysql.connect(host="127.0.0.1", user="root", password="xxx", database="test", charset="utf8mb4")
cursor = conn.cursor()
title = "SMS group's advanced technology upgrade"
# 直接字符串拼接
sql = f"""
SELECT id FROM information WHERE title = '{title}'
"""
cursor.execute(sql)
执行直接抛出异常:
pymysql.err.ProgrammingError: (1064, "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 's advanced technology upgrade'' at line 2")
报错根因
拼接之后发送给 MySQL 的SQL片段:
WHERE title = 'SMS group's advanced technology upgrade'
字符串内部的单引号,提前闭合了SQL字符串字面量,破坏了SQL语法结构。
不止单引号,双引号、反斜杠、
\0空字节都会带来同类问题。
二、最优方案:参数化查询(生产环境标准方案)
pymysql 使用 %s 作为占位符,不要给占位符手工加单引号,变量通过元组传入 execute 的第二个参数。
驱动底层自动完成:特殊字符转义、字符串加引号、防御SQL注入。
✅正确示例:
import pymysql
conn = pymysql.connect(host="127.0.0.1", user="root", password="xxx", database="test", charset="utf8mb4")
cursor = conn.cursor()
title = "SMS group's advanced technology upgrade"
href = "https://example.com/demo"
spider_name = "sms_press_releases_spider"
# SQL中写 %s占位符,不要加 ''
sql = """
SELECT id,uuid,hash,version_no FROM information
WHERE title = %s AND title_href = %s AND spider_name = %s
"""
# 参数按顺序放入元组
cursor.execute(sql, (title, href, spider_name))
rows = cursor.fetchall()
print(rows)
cursor.close()
conn.close()
参数化查询重点注意事项
- 占位符是
%s,不是问号?,不是{}; %s外面绝对不要手动写单引号;- 第二个参数接收元组
(val1,val2,...),只有一个参数也要写逗号(title,); - 底层驱动根据连接的
charset完成转义,兼容utf8mb4。
参数化查询同时解决两件事:特殊字符语法报错 + SQL注入漏洞。业务代码强制优先使用此方案。
三、特殊场景:非要手动拼接SQL字符串怎么办?
⚠️警告:生产业务尽量避免拼接SQL;该方案多用于日志打印、调试场景。
pymysql connection 对象提供 escape_string() 方法,专门对字符串做SQL转义。
功能:
'→\'"→\"\→\\- 过滤
\0空字节等危险字符
注意:
escape_string()只转义内容,不会添加外层单引号,拼接SQL的时候你必须自己手动加上单引号。
import pymysql
conn = pymysql.connect(host="127.0.0.1", user="root", password="xxx", database="test", charset="utf8mb4")
cursor = conn.cursor()
title = "SMS group's advanced technology upgrade"
# 使用连接对象的escape_string做转义
esc_title = conn.escape_string(title)
# 拼接时手动包裹单引号
sql = f"SELECT id FROM information WHERE title = '{esc_title}'"
cursor.execute(sql)
print(sql)
输出拼接后的SQL片段:
SELECT id FROM information WHERE title = 'SMS group\'s advanced technology upgrade'
使用 escape_string() 的致命坑
- 必须依赖真实的数据库连接对象
转义逻辑受连接的charset字符集影响,不能脱离连接离线预处理。不要自己写简单的replace("'","\\'"),手写替换会漏掉反斜杠、空字节等,存在绕过注入风险。
❌不推荐手写替换:
# 不要这么写!存在安全隐患
bad_title = title.replace("'", "\\'")
-
区分数据类型
只对字符串调用escape_string();数字、时间等不需要转义,也不要套单引号。 -
转义后的字符串拼接时,外层必须手动加单引号,忘记加引号直接语法报错。
四、调试:拿到完整渲染后的SQL用于日志打印
有时候我们想看完整的最终SQL用于日志排查,但又不想自己手动拼接,可以使用 cursor.mogrify()。
mogrify只会生成SQL字符串用于打印,业务执行依然使用参数化execute,不要把mogrify输出再丢给execute执行。
import pymysql
conn = pymysql.connect(host="127.0.0.1", user="root", password="xxx", database="test", charset="utf8mb4")
cursor = conn.cursor()
title = "SMS group's advanced technology upgrade"
sql = "SELECT id FROM information WHERE title = %s"
params = (title,)
# 打印完整可调试SQL
full_sql = cursor.mogrify(sql, params)
print("完整SQL:", full_sql)
# 正常参数化执行
cursor.execute(sql, params)
五、常见踩坑清单
坑1:给占位符加上单引号
# ❌错误
sql = "SELECT * FROM information WHERE title='%s'"
cursor.execute(sql, (title,))
会把 %s 当成字面量字符串,不会执行参数替换。
坑2:混用拼接和参数化
一部分参数f-string拼接,一部分使用%s占位符,极易漏转义造成漏洞。
坑3:离线预处理字符串
没有数据库连接,自己写replace做转义,遇到特殊编码、反斜杠会出问题。
坑4:忘记设置 charset="utf8mb4"
如果连接使用utf8,无法存储emoji,同时转义行为会异常。
六、ORM框架下如何处理特殊字符
Django ORM / SQLAlchemy 底层全部封装参数化查询,正常写filter不需要关心单引号转义。
Django ORM示例:
# ORM内部自动参数化,不需要手动处理单引号
obj = Information.objects.filter(title="SMS group's advanced technology upgrade").first()
只有当你在ORM中手写原生SQL的时候,依然要遵守参数化规则,不要f-string拼接。
Django原生SQL:
from django.db import connection
sql = "SELECT id FROM information WHERE title = %s"
with connection.cursor() as cur:
cur.execute(sql, (title,))
七、总结
- 问题根源:手动f‑string拼接SQL,字符串内部的单引号、反斜杠破坏SQL语法。pymysql不会处理已经拼接进SQL文本的变量。
- ✅生产首选:参数化查询
cursor.execute(sql, params),自动处理特殊字符,同时防御SQL注入。 - 调试/特殊场景需要拼接:使用
conn.escape_string(),拼接时手动增加外层单引号;禁止手写replace简单替换。 - 打印完整SQL日志使用
cursor.mogrify(),只用于日志,不要拿来执行。 - 不要脱离数据库连接做离线转义,字符集会影响转义结果。
很多同学把精力放在如何把特殊字符替换干净,实际上最好的办法就是从根源避免字符串拼接SQL。
延伸阅读
很多线上SQL注入漏洞,根源都不是复杂攻击,仅仅是开发者图方便使用f-string拼接SQL,遇到带单引号的业务数据直接破防。特殊字符报错只是表象,背后隐藏的是安全风险。
记住一条铁律:业务变量永远不要直接拼接到SQL字符串本体中。
更多推荐

所有评论(0)