pymysql 连接需显式指定 host='127.0.0.1'、port=3306、charset='utf8mb4',禁用 autocommit 并用 %s 参数化查询防注入,增删改后必须 commit(),推荐 dictcursor 或 cursor.description 获取字段名。

PyMySQL 是纯 Python 实现的 MySQL 客户端,对初学者友好,但直接上手容易卡在连接失败、SQL 注入、事务没提交、中文乱码这几个地方。
如何正确建立 PyMySQL 连接并避免常见报错
连接失败通常不是密码错了,而是 host、port 或字符集没对上。默认 localhost 在某些系统(如 Docker 或 WSL)里会走 socket 而非 TCP,建议显式写 127.0.0.1;端口别依赖默认值,明确传 port=3306;中文支持必须加 charset='utf8mb4'(注意是 utf8mb4,不是 utf8)。
推荐写法:
import pymysql
conn = pymysql.connect(
host='127.0.0.1',
port=3306,
user='root',
password='your_pass',
database='testdb',
charset='utf8mb4',
autocommit=False # 先关掉自动提交,方便控制事务
)
- 如果报
Access denied for user,先用命令行 mysql -u root -p 验证账号是否真能登录 - 如果报
Can't connect to MySQL server,检查 MySQL 是否在监听 3306(netstat -an | grep 3306) - 不设
charset可能导致UnicodeEncodeError或存进数据库的中文变问号
为什么 execute() 传参要用 %s 而不是字符串拼接
直接用 f-string 或 + 拼 SQL 是高危操作,会导致 SQL 注入。PyMySQL 的 execute() 支持参数化查询,占位符统一用 %s(不管字段类型),参数以元组或字典传入,底层自动转义。
安全写法:
cursor.execute("INSERT INTO users (name, age) VALUES (%s, %s)", ("张三", 25))
# 或用字典
cursor.execute("SELECT * FROM users WHERE name = %(name)s", {"name": "李四"})
- 错误示范:
"INSERT INTO ... VALUES ('" + name + "', " + str(age) + ")"—— 用户输入' OR 1=1 --就完蛋 -
%s是 PyMySQL 协议约定,不是 Python 的字符串格式化,不能用{}或f"" - 批量插入用
executemany(),传入列表:cursor.executemany("INSERT...", [("a",1), ("b",2)])
增删改后数据没生效?记得 commit() 和 close()
PyMySQL 默认不自动提交(除非初始化时设 autocommit=True),INSERT/UPDATE/DELETE 执行完只是“暂存”,不 commit() 就不会落盘。查(SELECT)不需要 commit,但游标要 fetchone() 或 fetchall() 才能拿到结果。
- 增删改后必须调
conn.commit(),否则程序一退出就回滚 - 出异常时应
conn.rollback(),否则脏数据可能卡在事务里 -
cursor.close()和conn.close()建议用try/finally或上下文管理器(with conn.cursor() as cursor:) - 忘记
close()会导致连接泄漏,MySQL 有最大连接数限制(max_connections),连多了直接拒绝新请求
SELECT 返回的是元组还是字典?怎么取字段名
默认 cursor 返回的是元组(('Alice', 28)),按 SELECT 字段顺序排列,没法靠名字取值。想用字段名访问(如 row['name']),得换游标类型:
# 方法一:用 DictCursor
cursor = conn.cursor(pymysql.cursors.DictCursor)
cursor.execute("SELECT name, age FROM users")
row = cursor.fetchone() # → {'name': 'Alice', 'age': 28}
<h1>方法二:用 description 获取字段名(通用,不用换游标)</h1><p>cursor.execute("SELECT name, age FROM users")
rows = cursor.fetchall()
columns = [col[0] for col in cursor.description] # ['name', 'age']
for row in rows:
print(dict(zip(columns, row)))</p>
-
DictCursor稍慢一点,内存占用略高,但开发调试阶段值得用 -
cursor.description是兼容性最强的方式,所有游标类型都支持 -
fetchone()返回单行,fetchall()返回全部,大数据量别无脑fetchall(),容易 OOM
真正麻烦的不是语法,是连接池没配、长事务没设超时、错误没捕获、中文字段没设 collation —— 这些问题往往在线上才暴露,但本地跑通 CRUD 只是第一步。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











