Python库PyMySQL的使用指南
写在前面:这篇文章是我结合PyMySQL官方文档和实际项目经验整理而成的。如果你正在用Python操作MySQL数据库,希望这篇文章能帮你少走一些弯路。
一、背景:为什么需要PyMySQL?
1.1 Python与数据库的故事
做过Web开发的同学都知道,几乎所有的应用都需要和数据库打交道。Python作为一门"优雅"的语言,自然也少不了和MySQL这个"老大哥"配合。
在Python的世界里,连接MySQL主要有这几种选择:
| 方案 | 特点 | 适用场景 |
|---|---|---|
| MySQLdb | 老牌库,C扩展实现,性能好 | 只支持Python 2,已逐渐淘汰 |
| mysql-connector-python | MySQL官方出品,纯Python实现 | 通用场景,但性能一般 |
| PyMySQL | 纯Python实现,兼容MySQLdb接口 | Python 3推荐方案 |
| SQLAlchemy | ORM框架,底层可选用PyMySQL | 大型项目,需要ORM支持 |
打个比方:如果把Python比作一个厨师,那PyMySQL就是他手里的菜刀------不是最花哨的工具,但绝对是最实用、最顺手的那一把。
1.2 PyMySQL的优势
为什么推荐PyMySQL?简单总结几个关键词:
- 纯Python实现:不需要编译C扩展,安装简单,跨平台无忧
- 兼容性好:接口和MySQLdb几乎一样,迁移成本低
- 活跃维护:GitHub上持续更新,社区活跃
- Python 3原生支持:专门为Python 3设计和优化
二、问题:我们面临哪些挑战?
在实际开发中,操作数据库经常会遇到这些问题:
2.1 连接管理
python
# 这样的代码你是不是很熟悉?
conn = pymysql.connect(host='localhost', user='root', password='123456')
cursor = conn.cursor()
cursor.execute("SELECT * FROM users")
data = cursor.fetchall()
# 如果这里抛异常了,conn和cursor就泄漏了...
cursor.close()
conn.close()
问题:手动管理连接容易忘记关闭,导致连接泄漏。
2.2 SQL注入风险
python
# 危险写法:字符串拼接
name = request.GET['name']
sql = f"SELECT * FROM users WHERE name = '{name}'"
# 如果name是 "'; DROP TABLE users; --",后果不堪设想
问题:直接拼接SQL字符串,容易被SQL注入攻击。
2.3 事务处理
python
# 转账场景:A给B转100元
cursor.execute("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
# 如果这里程序崩溃了,A的钱扣了,B的钱没加...
cursor.execute("UPDATE accounts SET balance = balance + 100 WHERE id = 2")
conn.commit()
问题:没有正确的事务管理,数据一致性无法保证。
三、方案:PyMySQL如何解决这些问题?
3.1 安装PyMySQL
先说安装,非常简单:
bash
# 方式一:直接安装
pip install PyMySQL
# 方式二:指定版本安装
pip install PyMySQL==1.1.0
# 方式三:使用国内镜像(推荐,速度快)
pip install PyMySQL -i https://pypi.tuna.tsinghua.edu.cn/simple
环境要求:
- Python 3.7+
- MySQL 5.7+ 或 MariaDB 10.2+
安装完成后,验证一下:
python
import pymysql
print(pymysql.__version__) # 输出版本号,说明安装成功
3.2 核心概念:连接与游标
在开始写代码之前,先理解两个核心概念:
连接(Connection):就像是你和数据库之间的一条电话线。你需要先拨通电话(建立连接),才能和数据库"对话"。
游标(Cursor):就像是你在对话中使用的"遥控器"。你通过游标发送指令(执行SQL),获取结果(fetch数据)。
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Python │ ───> │ Connection │ ───> │ MySQL │
│ 程序 │ <─── │ (连接) │ <─── │ 数据库 │
└─────────────┘ └─────────────┘ └─────────────┘
│
│ 使用
▼
┌─────────────┐
│ Cursor │
│ (游标) │
└─────────────┘
四、实现:从入门到实战
4.1 建立数据库连接
基础版:
python
import pymysql
# 建立连接
connection = pymysql.connect(
host='localhost', # 数据库地址
port=3306, # 端口号,默认3306
user='root', # 用户名
password='your_password', # 密码
database='test_db', # 数据库名
charset='utf8mb4' # 字符编码,推荐utf8mb4支持emoji
)
print("连接成功!")
connection.close()
推荐版(使用上下文管理器):
python
import pymysql
# 使用 with 语句,自动管理连接的关闭
with pymysql.connect(
host='localhost',
user='root',
password='your_password',
database='test_db',
charset='utf8mb4'
) as connection:
with connection.cursor() as cursor:
cursor.execute("SELECT VERSION()")
version = cursor.fetchone()
print(f"MySQL版本: {version[0]}")
# 离开 with 块后,连接自动关闭
为什么推荐 with 语句? 就像你用完水龙头要关掉一样,with 语句保证了即使代码出错,连接也会被正确关闭。
4.2 查询数据
基础查询:
python
import pymysql
with pymysql.connect(
host='localhost',
user='root',
password='your_password',
database='test_db',
charset='utf8mb4'
) as conn:
with conn.cursor() as cursor:
# 执行查询
cursor.execute("SELECT id, name, email FROM users")
# 获取一条记录
one_row = cursor.fetchone()
print(f"单条记录: {one_row}")
# 获取所有记录
all_rows = cursor.fetchall()
for row in all_rows:
print(f"ID: {row[0]}, 姓名: {row[1]}, 邮箱: {row[2]}")
使用DictCursor(推荐):
python
import pymysql
from pymysql.cursors import DictCursor
with pymysql.connect(
host='localhost',
user='root',
password='your_password',
database='test_db',
charset='utf8mb4',
cursorclass=DictCursor # 设置游标类型为字典
) as conn:
with conn.cursor() as cursor:
cursor.execute("SELECT id, name, email FROM users")
users = cursor.fetchall()
for user in users:
# 返回的是字典,用键名访问,更直观
print(f"ID: {user['id']}, 姓名: {user['name']}, 邮箱: {user['email']}")
小贴士 :使用 DictCursor 后,返回的数据是字典格式,用 user['name'] 比 user[1] 更易读,代码可维护性更好。
4.3 插入数据
单条插入:
python
import pymysql
from pymysql.cursors import DictCursor
with pymysql.connect(
host='localhost',
user='root',
password='your_password',
database='test_db',
charset='utf8mb4'
) as conn:
with conn.cursor() as cursor:
# 使用参数化查询,防止SQL注入
sql = "INSERT INTO users (name, email, age) VALUES (%s, %s, %s)"
cursor.execute(sql, ('张三', 'zhangsan@example.com', 25))
# 提交事务
conn.commit()
print(f"插入成功,影响行数: {cursor.rowcount}")
批量插入:
python
import pymysql
with pymysql.connect(
host='localhost',
user='root',
password='your_password',
database='test_db',
charset='utf8mb4'
) as conn:
with conn.cursor() as cursor:
# 准备批量数据
users_data = [
('李四', 'lisi@example.com', 28),
('王五', 'wangwu@example.com', 32),
('赵六', 'zhaoliu@example.com', 24),
('孙七', 'sunqi@example.com', 29),
]
# 使用 executemany 批量插入
sql = "INSERT INTO users (name, email, age) VALUES (%s, %s, %s)"
affected_rows = cursor.executemany(sql, users_data)
conn.commit()
print(f"批量插入成功,影响行数: {affected_rows}")
4.4 更新和删除
更新数据:
python
import pymysql
with pymysql.connect(
host='localhost',
user='root',
password='your_password',
database='test_db',
charset='utf8mb4'
) as conn:
with conn.cursor() as cursor:
# 更新用户年龄
sql = "UPDATE users SET age = %s WHERE name = %s"
cursor.execute(sql, (26, '张三'))
conn.commit()
print(f"更新成功,影响行数: {cursor.rowcount}")
删除数据:
python
import pymysql
with pymysql.connect(
host='localhost',
user='root',
password='your_password',
database='test_db',
charset='utf8mb4'
) as conn:
with conn.cursor() as cursor:
# 删除指定用户
sql = "DELETE FROM users WHERE name = %s"
cursor.execute(sql, ('张三',))
conn.commit()
print(f"删除成功,影响行数: {cursor.rowcount}")
4.5 事务管理(重要!)
场景:银行转账,A给B转100元
python
import pymysql
from pymysql import MySQLError
def transfer_money(from_id, to_id, amount):
"""转账函数:保证数据一致性"""
with pymysql.connect(
host='localhost',
user='root',
password='your_password',
database='test_db',
charset='utf8mb4'
) as conn:
try:
with conn.cursor() as cursor:
# 第一步:检查余额
cursor.execute("SELECT balance FROM accounts WHERE id = %s", (from_id,))
result = cursor.fetchone()
if not result or result[0] < amount:
print("余额不足!")
return False
# 第二步:从A账户扣款
cursor.execute(
"UPDATE accounts SET balance = balance - %s WHERE id = %s",
(amount, from_id)
)
# 第三步:给B账户加款
cursor.execute(
"UPDATE accounts SET balance = balance + %s WHERE id = %s",
(amount, to_id)
)
# 提交事务
conn.commit()
print(f"转账成功:{from_id} -> {to_id},金额:{amount}")
return True
except MySQLError as e:
# 发生错误,回滚事务
conn.rollback()
print(f"转账失败,已回滚: {e}")
return False
# 使用示例
transfer_money(from_id=1, to_id=2, amount=100)
事务的ACID特性:
- 原子性(Atomicity):要么全部成功,要么全部失败
- 一致性(Consistency):事务前后数据保持一致
- 隔离性(Isolation):多个事务互不干扰
- 持久性(Durability):提交后数据永久保存
4.6 参数化查询(防SQL注入)
错误示范:
python
# ❌ 危险!字符串拼接
name = "'; DROP TABLE users; --"
sql = f"SELECT * FROM users WHERE name = '{name}'"
# 执行结果:SELECT * FROM users WHERE name = ''; DROP TABLE users; --'
# 后果:users表被删除!
正确做法:
python
# ✅ 安全!参数化查询
name = "'; DROP TABLE users; --"
sql = "SELECT * FROM users WHERE name = %s"
cursor.execute(sql, (name,))
# PyMySQL会自动转义特殊字符,SQL注入无法生效
记住:永远不要用字符串拼接来构建SQL语句!参数化查询是你的"护身符"。
4.7 游标类型详解
PyMySQL提供了多种游标类型,适用于不同场景:
python
import pymysql
from pymysql.cursors import (
Cursor, # 默认游标,返回元组
DictCursor, # 字典游标,返回字典
SSCursor, # 服务端游标,适合大数据量
SSDictCursor # 服务端字典游标
)
# 1. 默认游标 - 返回元组
with pymysql.connect(host='localhost', user='root', password='pwd', database='test') as conn:
with conn.cursor() as cursor:
cursor.execute("SELECT * FROM users")
row = cursor.fetchone()
print(row) # (1, '张三', 'zhangsan@example.com')
# 2. 字典游标 - 返回字典(推荐)
with pymysql.connect(host='localhost', user='root', password='pwd', database='test',
cursorclass=DictCursor) as conn:
with conn.cursor() as cursor:
cursor.execute("SELECT * FROM users")
row = cursor.fetchone()
print(row) # {'id': 1, 'name': '张三', 'email': 'zhangsan@example.com'}
# 3. 服务端游标 - 适合处理大量数据
with pymysql.connect(host='localhost', user='root', password='pwd', database='test') as conn:
with conn.cursor(SSCursor) as cursor:
cursor.execute("SELECT * FROM large_table")
for row in cursor: # 逐行读取,不会一次性加载到内存
process(row)
五、进阶:生产环境最佳实践
5.1 连接池配置
在生产环境中,频繁创建和销毁连接是很耗资源的。使用连接池可以显著提升性能:
python
from dbutils.pooled_db import PooledDB
import pymysql
# 创建连接池
pool = PooledDB(
creator=pymysql, # 使用PyMySQL作为连接创建者
maxconnections=20, # 最大连接数
mincached=5, # 初始化时创建的最小连接数
maxcached=10, # 最大空闲连接数
blocking=True, # 连接池满时是否阻塞等待
maxusage=None, # 单个连接的最大复用次数
setsession=[], # 开始会话前执行的命令
ping=0, # ping MySQL服务端,0=不ping
# 数据库连接参数
host='localhost',
port=3306,
user='root',
password='your_password',
database='test_db',
charset='utf8mb4'
)
def get_user_by_id(user_id):
"""从连接池获取连接查询用户"""
conn = pool.connection() # 从池中获取连接
try:
with conn.cursor(pymysql.cursors.DictCursor) as cursor:
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
return cursor.fetchone()
finally:
conn.close() # 连接归还到池中,而非真正关闭
# 使用
user = get_user_by_id(1)
print(user)
连接池参数说明:
| 参数 | 说明 | 推荐值 |
|---|---|---|
maxconnections |
最大连接数 | 根据并发量设置,一般10-50 |
mincached |
最小空闲连接 | 2-5 |
maxcached |
最大空闲连接 | 5-10 |
blocking |
池满时是否阻塞 | True(避免连接失败) |
5.2 超时配置
python
import pymysql
connection = pymysql.connect(
host='localhost',
user='root',
password='your_password',
database='test_db',
# 超时配置(单位:秒)
connect_timeout=10, # 连接超时
read_timeout=30, # 读取超时
write_timeout=30, # 写入超时
# 自动提交(根据需求设置)
autocommit=False, # False表示手动管理事务
charset='utf8mb4'
)
5.3 异常处理
python
import pymysql
from pymysql import MySQLError
from pymysql.err import (
OperationalError, # 连接错误
ProgrammingError, # SQL语法错误
IntegrityError, # 数据完整性错误
DataError, # 数据错误
NotSupportedError # 不支持的操作
)
def safe_query(sql, params=None):
"""安全的查询函数,包含完善的异常处理"""
try:
with pymysql.connect(
host='localhost',
user='root',
password='your_password',
database='test_db',
charset='utf8mb4'
) as conn:
with conn.cursor(pymysql.cursors.DictCursor) as cursor:
cursor.execute(sql, params)
return cursor.fetchall()
except OperationalError as e:
print(f"数据库连接错误: {e}")
# 可以在这里实现重连逻辑
return None
except ProgrammingError as e:
print(f"SQL语法错误: {e}")
return None
except IntegrityError as e:
print(f"数据完整性错误(如主键冲突): {e}")
return None
except DataError as e:
print(f"数据错误(如字段长度超限): {e}")
return None
except MySQLError as e:
print(f"其他数据库错误: {e}")
return None
except Exception as e:
print(f"未知错误: {e}")
return None
# 使用示例
users = safe_query("SELECT * FROM users WHERE age > %s", (25,))
if users:
for user in users:
print(user)
5.4 封装数据库工具类
将常用操作封装成工具类,提高代码复用性:
python
import pymysql
from pymysql.cursors import DictCursor
from contextlib import contextmanager
class MySQLHelper:
"""MySQL数据库操作助手类"""
def __init__(self, host='localhost', port=3306, user='root',
password='', database='', charset='utf8mb4'):
self.config = {
'host': host,
'port': port,
'user': user,
'password': password,
'database': database,
'charset': charset,
'cursorclass': DictCursor
}
@contextmanager
def get_connection(self):
"""获取数据库连接的上下文管理器"""
conn = pymysql.connect(**self.config)
try:
yield conn
finally:
conn.close()
def query(self, sql, params=None):
"""执行查询语句,返回结果列表"""
with self.get_connection() as conn:
with conn.cursor() as cursor:
cursor.execute(sql, params)
return cursor.fetchall()
def query_one(self, sql, params=None):
"""执行查询语句,返回单条结果"""
with self.get_connection() as conn:
with conn.cursor() as cursor:
cursor.execute(sql, params)
return cursor.fetchone()
def execute(self, sql, params=None):
"""执行增删改语句,返回影响行数"""
with self.get_connection() as conn:
with conn.cursor() as cursor:
result = cursor.execute(sql, params)
conn.commit()
return result
def execute_many(self, sql, params_list):
"""批量执行增删改语句,返回影响行数"""
with self.get_connection() as conn:
with conn.cursor() as cursor:
result = cursor.executemany(sql, params_list)
conn.commit()
return result
def execute_with_transaction(self, operations):
"""在事务中执行多个操作
Args:
operations: 一个列表,每个元素是 (sql, params) 元组
"""
with self.get_connection() as conn:
try:
with conn.cursor() as cursor:
for sql, params in operations:
cursor.execute(sql, params)
conn.commit()
return True
except Exception as e:
conn.rollback()
print(f"事务执行失败: {e}")
return False
# 使用示例
if __name__ == '__main__':
db = MySQLHelper(
host='localhost',
user='root',
password='your_password',
database='test_db'
)
# 查询所有用户
users = db.query("SELECT * FROM users")
print(f"用户列表: {users}")
# 查询单个用户
user = db.query_one("SELECT * FROM users WHERE id = %s", (1,))
print(f"用户详情: {user}")
# 插入数据
affected = db.execute(
"INSERT INTO users (name, email) VALUES (%s, %s)",
('新用户', 'new@example.com')
)
print(f"插入了 {affected} 条记录")
# 事务操作
operations = [
("UPDATE accounts SET balance = balance - 100 WHERE id = %s", (1,)),
("UPDATE accounts SET balance = balance + 100 WHERE id = %s", (2,)),
]
success = db.execute_with_transaction(operations)
print(f"事务{'成功' if success else '失败'}")
六、结果:完整的实战案例
6.1 用户管理系统
下面是一个完整的用户管理系统的示例:
python
"""
用户管理系统 - 完整示例
演示PyMySQL在实际项目中的使用
"""
import pymysql
from pymysql.cursors import DictCursor
from datetime import datetime
class UserManager:
"""用户管理类"""
def __init__(self, db_config):
self.db_config = db_config
def _get_connection(self):
"""获取数据库连接"""
return pymysql.connect(**self.db_config, cursorclass=DictCursor)
def init_db(self):
"""初始化数据库表"""
with self._get_connection() as conn:
with conn.cursor() as cursor:
cursor.execute("""
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(200) UNIQUE NOT NULL,
age INT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
""")
conn.commit()
print("数据库表初始化完成")
def create_user(self, name, email, age=None):
"""创建用户"""
with self._get_connection() as conn:
with conn.cursor() as cursor:
sql = "INSERT INTO users (name, email, age) VALUES (%s, %s, %s)"
cursor.execute(sql, (name, email, age))
conn.commit()
return cursor.lastrowid
def get_user(self, user_id):
"""获取用户信息"""
with self._get_connection() as conn:
with conn.cursor() as cursor:
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
return cursor.fetchone()
def get_all_users(self, page=1, page_size=10):
"""分页获取所有用户"""
offset = (page - 1) * page_size
with self._get_connection() as conn:
with conn.cursor() as cursor:
# 获取总数
cursor.execute("SELECT COUNT(*) as total FROM users")
total = cursor.fetchone()['total']
# 获取分页数据
cursor.execute(
"SELECT * FROM users ORDER BY id DESC LIMIT %s OFFSET %s",
(page_size, offset)
)
users = cursor.fetchall()
return {
'total': total,
'page': page,
'page_size': page_size,
'data': users
}
def update_user(self, user_id, **kwargs):
"""更新用户信息"""
if not kwargs:
return 0
# 构建更新语句
set_clause = ', '.join([f"{k} = %s" for k in kwargs.keys()])
values = list(kwargs.values())
values.append(user_id)
with self._get_connection() as conn:
with conn.cursor() as cursor:
sql = f"UPDATE users SET {set_clause} WHERE id = %s"
cursor.execute(sql, values)
conn.commit()
return cursor.rowcount
def delete_user(self, user_id):
"""删除用户"""
with self._get_connection() as conn:
with conn.cursor() as cursor:
cursor.execute("DELETE FROM users WHERE id = %s", (user_id,))
conn.commit()
return cursor.rowcount
def search_users(self, keyword):
"""搜索用户"""
with self._get_connection() as conn:
with conn.cursor() as cursor:
sql = """
SELECT * FROM users
WHERE name LIKE %s OR email LIKE %s
"""
like_keyword = f"%{keyword}%"
cursor.execute(sql, (like_keyword, like_keyword))
return cursor.fetchall()
def batch_create_users(self, users_data):
"""批量创建用户"""
with self._get_connection() as conn:
with conn.cursor() as cursor:
sql = "INSERT INTO users (name, email, age) VALUES (%s, %s, %s)"
affected = cursor.executemany(sql, users_data)
conn.commit()
return affected
# 使用示例
if __name__ == '__main__':
# 数据库配置
db_config = {
'host': 'localhost',
'port': 3306,
'user': 'root',
'password': 'your_password',
'database': 'test_db',
'charset': 'utf8mb4'
}
# 创建用户管理器实例
manager = UserManager(db_config)
# 初始化数据库
manager.init_db()
# 创建用户
user_id = manager.create_user('张三', 'zhangsan@example.com', 25)
print(f"创建用户成功,ID: {user_id}")
# 获取用户
user = manager.get_user(user_id)
print(f"用户信息: {user}")
# 更新用户
affected = manager.update_user(user_id, name='张三丰', age=30)
print(f"更新了 {affected} 条记录")
# 分页查询
result = manager.get_all_users(page=1, page_size=5)
print(f"共 {result['total']} 个用户,当前页: {result['data']}")
# 搜索用户
users = manager.search_users('张')
print(f"搜索结果: {users}")
# 批量创建
batch_data = [
('李四', 'lisi@example.com', 28),
('王五', 'wangwu@example.com', 32),
('赵六', 'zhaoliu@example.com', 24),
]
affected = manager.batch_create_users(batch_data)
print(f"批量创建了 {affected} 个用户")
七、总结与延伸阅读
7.1 核心要点回顾
| 要点 | 说明 |
|---|---|
使用 with 语句 |
自动管理连接和游标的生命周期 |
| 参数化查询 | 永远不要拼接SQL字符串,防止SQL注入 |
| 事务管理 | 涉及多表操作时,使用事务保证数据一致性 |
| DictCursor | 使用字典游标,代码更易读 |
| 连接池 | 生产环境使用连接池提升性能 |
| 异常处理 | 完善的异常处理,避免程序崩溃 |
7.2 常见问题FAQ
Q1: PyMySQL和mysql-connector-python选哪个?
A: 如果你追求简单易用和兼容性,选PyMySQL;如果你需要MySQL官方支持,选mysql-connector-python。两者功能相似,性能差异不大。
Q2: 出现 "ModuleNotFoundError: No module named 'pymysql'" 怎么办?
A: 检查是否安装了PyMySQL:
bash
pip install PyMySQL
如果使用虚拟环境,确保在正确的虚拟环境中安装。
Q3: 连接数据库时报错 "Access denied" 怎么办?
A: 检查以下几点:
- 用户名和密码是否正确
- 用户是否有访问指定数据库的权限
- MySQL服务是否允许远程连接(如果是远程访问)
Q4: 中文乱码怎么解决?
A: 确保连接时设置 charset='utf8mb4',同时数据库和表的编码也要设置为 utf8mb4。
7.3 性能优化建议
- 使用连接池:避免频繁创建和销毁连接
- 批量操作 :使用
executemany而不是循环执行execute - 合理使用索引:为经常查询的字段添加索引
- 分页查询 :大数据量使用
LIMIT分页,避免一次性加载 - 服务端游标 :处理大数据集时使用
SSCursor
7.4 延伸阅读
7.5 写在最后
PyMySQL是一个小而美的库,它没有ORM框架那么"高大上",但胜在简单、直接、高效。对于大多数中小型项目来说,PyMySQL完全够用。
记住几个关键原则:
- 安全第一:永远使用参数化查询
- 资源管理 :用
with语句管理连接 - 事务意识:涉及多表操作时,记得用事务
- 性能优化:生产环境使用连接池
希望这篇文章对你有帮助。如果你有任何问题或建议,欢迎交流讨论!
作者的话:技术文章的价值在于分享和帮助他人成长。如果这篇文章帮你解决了问题,或者让你学到了新知识,那我的目的就达到了。祝你编程愉快!