目录
[一、PyMySQL 是什么?为什么选择它?](#一、PyMySQL 是什么?为什么选择它?)
[1.1 PyMySQL 的定义](#1.1 PyMySQL 的定义)
[1.2 为什么选择 PyMySQL?](#1.2 为什么选择 PyMySQL?)
[2.1 安装 PyMySQL](#2.1 安装 PyMySQL)
[2.2 建立数据库连接](#2.2 建立数据库连接)
[2.3 关闭连接](#2.3 关闭连接)
[3.1 游标对象(Cursor)](#3.1 游标对象(Cursor))
[3.2 查询数据(SELECT)](#3.2 查询数据(SELECT))
[3.3 插入数据(INSERT)](#3.3 插入数据(INSERT))
[3.4 更新数据(UPDATE)](#3.4 更新数据(UPDATE))
[3.5 删除数据(DELETE)](#3.5 删除数据(DELETE))
[3.6 批量操作(executemany)](#3.6 批量操作(executemany))
[4.1 游标类型详解](#4.1 游标类型详解)
[4.2 事务管理](#4.2 事务管理)
[5.1 为什么需要连接池?](#5.1 为什么需要连接池?)
[5.2 使用 DBUtils 实现连接池](#5.2 使用 DBUtils 实现连接池)
[5.3 批量操作优化建议](#5.3 批量操作优化建议)
[六、安全第一:防止 SQL 注入](#六、安全第一:防止 SQL 注入)
[6.1 什么是 SQL 注入?](#6.1 什么是 SQL 注入?)
[6.2 正确的做法:参数化查询](#6.2 正确的做法:参数化查询)
一、PyMySQL 是什么?为什么选择它?
1.1 PyMySQL 的定义
PyMySQL 是一个纯 Python 编写的 MySQL 和 MariaDB 客户端库,遵循 Python 数据库 API v2.0 规范(PEP 249)。它让 Python 程序能够轻松连接和操作 MySQL 数据库,执行各种 SQL 语句。
简单来说,PyMySQL 就是 Python 与 MySQL 之间的"翻译官"------把 Python 代码写给它,它帮你转换成 SQL 发给数据库,再把结果翻译回 Python 数据格式。
1.2 为什么选择 PyMySQL?
目前 Python 连接 MySQL 的主流驱动有三个:PyMySQL、MySQLdb(也称 MySQL-python)和 mysql-connector-python。为什么推荐 PyMySQL?
|-------------|--------------------------|----------------|------------------------|
| 对比项 | PyMySQL | MySQLdb | mysql-connector-python |
| 实现语言 | 纯 Python | C 扩展 | 纯 Python / C 两种 |
| Python 3 支持 | 完美支持 | 仅 Python 2,已停更 | 支持 |
| 安装难度 | 极低:`pip install pymysql | 高:需要 C 编译环境 | 中等 |
| 跨平台性 | 完美 | Windows 安装困难 | 良好 |
| 社区活跃度 | 非常活跃 | 已停止维护 | 官方支持但社区较冷 |
| 性能 | 对绝大多数场景足够 | 更快(C 底层) | 中等 |
| 使用成本 | 极低 | 高 | 中等 |
结论:对于绝大多数 Python 3 项目,PyMySQL 是最佳选择。它的纯 Python特性带来的安装便捷性和跨平台兼容性,远超它与 C 扩展驱动之间微小的性能差异。除非你有极致的性能需求,否则选 PyMySQL 就可以。
MySQLdb 作为 Python 2 时代的产物,年久失修且安装复杂,已基本被淘汰。因此对于现代 Python 项目,PyMySQL 已然成为主流选择。
二、快速上手:安装与第一个连接
2.1 安装 PyMySQL
一条命令搞定:
python
pip install pymysql
验证安装是否成功:
python
python
import pymysql
print(pymysql.__version__) # 输出版本号
2.2 建立数据库连接
python
python
import pymysql
# 建立数据库连接
connection = pymysql.connect(
host='localhost', # 数据库主机地址(也可用 '127.0.0.1')
user='root', # 数据库用户名
password='your_password', # 数据库密码
database='test_db', # 要连接的数据库名称
port=3306, # MySQL 默认端口
charset='utf8mb4', # 字符集,推荐 utf8mb4 以支持 emoji
cursorclass=pymysql.cursors.DictCursor # 游标类型,让结果返回字典
)
# 检查连接是否成功
if connection.open:
print("连接成功!")
connect() 常用参数说明:
|-------------|----------|------------------------|
| 参数 | 说明 | 默认值 |
| host | 数据库服务器地址 | localhost |
| user | 登录用户名 | 当前用户 |
| password | 登录密码 | 输入自己设置的 |
| database | 要连接的数据库名 | 连接自己的 |
| port | 端口号 | 3306 |
| charset | 字符集 | utf8mb4 |
| cursorclass | 游标类型 | pymysql.cursors.Cursor |
2.3 关闭连接
使用完数据库后,务必关闭连接释放资源:
python
connection.close()
三、核心操作:增删改查
3.1 游标对象(Cursor)
游标是执行 SQL 语句的"手"。执行任何 SQL 都需要通过游标:
python
python
cursor = connection.cursor()
# 执行 SQL...
cursor.close() # 用完关闭
3.2 查询数据(SELECT)
python
python
cursor = connection.cursor()
cursor.execute("SELECT id, name, age FROM users")
# 获取单条记录
one = cursor.fetchone()
# 获取所有记录
all = cursor.fetchall()
# 获取指定条数
many = cursor.fetchmany(5)
for row in all:
print(row)
三种获取方式对比:
|--------------|-------------|-----------|
| 方法 | 返回值 | 适用场景 |
| fetchone() | 单条记录(元组/字典) | 只取一条数据 |
| fetchmany(n) | 最多 n 条记录的列表 | 分批次获取大量数据 |
| fetchall() | 所有记录的列表 | 数据量较小 |
3.3 插入数据(INSERT)
python
python
sql = "INSERT INTO users (name, age, email) VALUES (%s, %s, %s)"
cursor.execute(sql, ('张三', 25, 'zhangsan@example.com'))
connection.commit() # 重要!必须提交才会写入数据库
print(f"插入行数: {cursor.rowcount}")
print(f"自增ID: {cursor.lastrowid}")
3.4 更新数据(UPDATE)
python
python
sql = "UPDATE users SET age = %s WHERE name = %s"
cursor.execute(sql, (26, '张三'))
connection.commit()
3.5 删除数据(DELETE)
python
python
sql = "DELETE FROM users WHERE name = %s"
cursor.execute(sql, ('张三',))
connection.commit()
3.6 批量操作(executemany)
当需要插入大量数据时,使用 executemany() 可以显著提升性能:
python
python
users_data = [
('李四', 20, 'lisi@example.com'),
('王五', 22, 'wangwu@example.com'),
('赵六', 23, 'zhaoliu@example.com'),
]
sql = "INSERT INTO users (name, age, email) VALUES (%s, %s, %s)"
cursor.executemany(sql, users_data)
connection.commit()
性能对比:在插入 20000 条数据的测试中,executemany() 的执行时间仅为 0.175 秒,而逐条插入则需要数秒。
四、进阶技巧:游标类型与事务管理
4.1 游标类型详解
PyMySQL 提供了多种游标类型,根据场景选择合适的使用:
|--------------|--------------------------|---------------|
| 游标类型 | 说明 | 适用场景 |
| Cursor(默认) | 返回元组,每条记录是 (值1, 值2, ...) | 简单场景 |
| DictCursor | 返回字典,键为列名,值为数据 | 推荐!代码可读性最高 |
| SSCursor | 服务端游标,不缓存结果 | 处理百万级大数据,节省内存 |
| SSDictCursor | 服务端游标 + 字典格式 | 大数据量且需要字典格式 |
使用示例:
python
python
# 普通游标(元组形式)
conn = pymysql.connect(host='localhost', user='root', password='123456', database='test')
cursor = conn.cursor()
cursor.execute("SELECT name, age FROM users")
print(cursor.fetchone()) # 输出: ('张三', 25)
# 字典游标(推荐!)
conn = pymysql.connect(
host='localhost', user='root', password='123456', database='test',
cursorclass=pymysql.cursors.DictCursor # 指定游标类型
)
cursor = conn.cursor()
cursor.execute("SELECT name, age FROM users")
print(cursor.fetchone()) # 输出: {'name': '张三', 'age': 25}
为什么推荐 DictCursor?
- 通过列名访问数据,代码自解释
- 避免因列顺序变化而出错
- 与 JSON 序列化天然兼容
4.2 事务管理
PyMySQL 默认关闭自动提交模式(autocommit=False),需要手动控制事务。
python
python
try:
# 开启事务(通过执行语句隐式开始)
cursor.execute("INSERT INTO accounts (user_id, balance) VALUES (%s, %s)", (1, 100))
cursor.execute("UPDATE accounts SET balance = balance - %s WHERE user_id = %s", (50, 2))
# 所有操作成功,提交事务
connection.commit()
print("事务提交成功")
except Exception as e:
# 出现异常,回滚事务
connection.rollback()
print(f"事务回滚: {e}")
finally:
cursor.close()
connection.close()
事务操作总结:
|------|-----------------------|--------------|
| 操作 | 方法 | 说明 |
| 提交事务 | connection.commit() | 将修改永久写入数据库 |
| 回滚事务 | connection.rollback() | 撤销当前事务中的所有操作 |
注意:不要忘记 commit()!很多新手只执行了 execute() 却不提交,导致数据根本没写进去。
五、性能优化:连接池与批量操作
5.1 为什么需要连接池?
每次数据库操作都创建和关闭连接,开销非常大。连接池可以复用数据库连接,避免频繁创建和销毁,大幅提升性能。
5.2 使用 DBUtils 实现连接池
python
python
import pymysql
from DBUtils.PooledDB import PooledDB
# 创建连接池
pool = PooledDB(
creator=pymysql, # 使用 pymysql 作为驱动
maxconnections=10, # 连接池允许的最大连接数
mincached=2, # 初始化时创建的连接数
maxcached=5, # 连接池中闲置的最大连接数
blocking=True, # 连接数达到上限时是否阻塞等待
host='localhost',
user='root',
password='your_password',
database='test_db',
charset='utf8mb4'
)
# 从连接池获取连接
connection = pool.connection()
cursor = connection.cursor()
cursor.execute("SELECT * FROM users")
results = cursor.fetchall()
# 使用完毕后归还连接(调用 close 并不会真正关闭,而是放回池中)
cursor.close()
connection.close()
DBUtils 的两种连接模式:
模式一:为每个线程创建一个独立连接,线程结束时连接归还池中。
模式二:线程之间共享连接池,适合多线程 Web 应用。
安装 DBUtils:pip install DBUtils
5.3 批量操作优化建议
-
使用 executemany() 代替循环 execute():减少网络往返次数
-
适当批量提交:每 N 条数据提交一次,而不是每条都提交
-
关闭自动提交:批量操作期间设置 autocommit=False,操作完成后再 commit()
六、安全第一:防止 SQL 注入
6.1 什么是 SQL 注入?
SQL 注入是攻击者通过构造恶意输入,绕过 SQL 语句逻辑,非法获取或篡改数据的安全漏洞。
错误示例( 绝对不要这样写):
python
python
# 用户输入:' OR '1'='1
user_input = "' OR '1'='1"
sql = f"SELECT * FROM users WHERE name = '{user_input}'"
cursor.execute(sql) # 结果变成 SELECT * FROM users WHERE name = '' OR '1'='1'
# 所有用户数据都被查出来了!
6.2 正确的做法:参数化查询
使用 execute() 的参数化功能,让数据库将参数当作纯数据对待:
python
python
# 正确写法
sql = "SELECT * FROM users WHERE name = %s AND age = %s"
cursor.execute(sql, (user_input, age_input))
# 插入操作
sql = "INSERT INTO users (name, age) VALUES (%s, %s)"
cursor.execute(sql, (name, age))
# 批量操作同样安全
cursor.executemany(sql, data_list)
为什么参数化查询能防注入?
- SQL 语句的结构和数据是分离传输的
- 数据库先编译 SQL,再将参数作为纯值代入
- 任何恶意代码都会被当作普通字符串处理,不会改变 SQL 结构
记住:永远不要用字符串拼接 SQL!永远使用参数化查询!
七、实战案例:用户管理系统
综合运用以上知识,构建一个简单的用户管理模块:
python
python
import pymysql
from pymysql.cursors import DictCursor
from contextlib import contextmanager
class UserManager:
def __init__(self, host, user, password, database):
self.conn = pymysql.connect(
host=host, user=user, password=password,
database=database, charset='utf8mb4',
cursorclass=DictCursor
)
@contextmanager
def get_cursor(self):
cursor = self.conn.cursor()
try:
yield cursor
self.conn.commit()
except Exception:
self.conn.rollback()
raise
finally:
cursor.close()
def create_user(self, name, age, email):
"""创建用户"""
sql = "INSERT INTO users (name, age, email) VALUES (%s, %s, %s)"
with self.get_cursor() as cursor:
cursor.execute(sql, (name, age, email))
return cursor.lastrowid
def get_user(self, user_id):
"""根据 ID 查询用户"""
sql = "SELECT id, name, age, email FROM users WHERE id = %s"
with self.get_cursor() as cursor:
cursor.execute(sql, (user_id,))
return cursor.fetchone()
def update_user(self, user_id, **kwargs):
"""更新用户信息"""
fields = ', '.join([f"{k} = %s" for k in kwargs.keys()])
sql = f"UPDATE users SET {fields} WHERE id = %s"
with self.get_cursor() as cursor:
cursor.execute(sql, (*kwargs.values(), user_id))
return cursor.rowcount
def delete_user(self, user_id):
"""删除用户"""
sql = "DELETE FROM users WHERE id = %s"
with self.get_cursor() as cursor:
cursor.execute(sql, (user_id,))
return cursor.rowcount
def list_users(self, limit=10, offset=0):
"""分页查询用户列表"""
sql = "SELECT id, name, age, email FROM users LIMIT %s OFFSET %s"
with self.get_cursor() as cursor:
cursor.execute(sql, (limit, offset))
return cursor.fetchall()
def close(self):
self.conn.close()
# 使用示例
if __name__ == '__main__':
manager = UserManager('localhost', 'root', 'password', 'test_db')
# 创建用户
user_id = manager.create_user('张三', 25, 'zhangsan@example.com')
print(f"创建成功,ID: {user_id}")
# 查询用户
user = manager.get_user(user_id)
print(f"查询结果: {user}")
# 更新用户
manager.update_user(user_id, age=26)
# 分页查询
users = manager.list_users(limit=5)
print(f"用户列表: {users}")
manager.close()
八、常见问题与解决方案
问题1:ModuleNotFoundError: No module named 'pymysql'
原因:PyMySQL 未安装
解决:pip install pymysql
问题2:pymysql.err.OperationalError: (2003, "Can't connect to MySQL server")
原因:MySQL 服务未启动或连接参数错误
解决:检查 MySQL 服务是否运行,确认 host、port、user、password 是否正确
问题3:插入中文变成 ??? 或报 Incorrect string value
原因:字符集设置不一致
解决:连接时设置 charset='utf8mb4', 确保数据库和表的字符集也是 utf8mb4
sql
sql
-- 修改数据库字符集
ALTER DATABASE test_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 修改表字符集
ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
问题4:修改操作没有生效
原因:忘记调用 commit()
解决:所有增、删、改操作后都要执行 connection.commit()
问题5:pymysql.err.ProgrammingError: (1064, ...)
原因:SQL 语法错误
解决:检查 SQL 语句拼写、引号、关键字是否正确
问题6:多线程环境下连接报错
原因:PyMySQL 连接本身不是线程安全的
解决:使用连接池(如 DBUtils),让每个线程从池中获取独立连接
九、总结
安装: pip install pymysql
连接: pymysql.connect(host, user, password, database)
增删改查: 通过游标执行 SQL,修改后需 commit()
参数化查询:必须使用! 防止 SQL 注入
游标类型:推荐 DictCursor,结果更直观
批量操作: 使用 executemany() 提升性能
连接池: 高并发场景必备,使用 DBUtils
事务commit():提交,rollback() 回滚
PyMySQL 是 Python 连接 MySQL 的纯 Python 驱动,安装简单、跨平台兼容、参数化查询防注入,是现代 Python 项目的首选 MySQL 客户端库。