1. Python与MySQL交互基础
PyMySQL是Python中用于连接和操作MySQL数据库的纯Python实现库。相比MySQLdb,它完全用Python编写,兼容Python 3.x,安装简单且功能完善。在实际项目中,PyMySQL因其轻量级和易用性成为开发者首选的MySQL驱动之一。
MySQL作为最流行的关系型数据库之一,广泛应用于Web开发、数据分析等领域。Python通过PyMySQL与MySQL交互,可以实现数据持久化存储、复杂查询分析等功能。这种组合特别适合需要快速开发原型或中小型项目的场景。
注意:在生产环境中,建议使用连接池管理数据库连接,避免频繁创建和销毁连接带来的性能开销。可以使用DBUtils等库实现连接池功能。
1.1 PyMySQL核心特性
PyMySQL提供了完整的MySQL客户端功能,包括:
- 支持MySQL 5.5+和MariaDB
- 支持SSL安全连接
- 支持Unicode编码(utf8mb4)
- 支持事务操作
- 支持存储过程和函数调用
- 支持批量操作和预处理语句
这些特性使得PyMySQL能够满足大多数MySQL操作需求。在实际使用中,PyMySQL的性能表现也相当不错,特别是在合理使用连接池和批量操作的情况下。
2. 环境准备与基础配置
2.1 安装PyMySQL
安装PyMySQL非常简单,只需要使用pip命令:
bash复制pip install pymysql
对于需要特定版本的情况,可以指定版本号安装:
bash复制pip install pymysql==1.0.2
提示:建议在虚拟环境中安装PyMySQL,避免与其他项目的依赖冲突。可以使用venv或conda创建Python虚拟环境。
2.2 基础连接配置
下面是一个完整的数据库连接配置类实现:
python复制import pymysql
from typing import List, Dict, Optional
from datetime import datetime
class MySQLDB:
def __init__(self, host='localhost', port=3306, user='root',
password='', db='', charset='utf8mb4'):
self.conn_params = {
'host': host,
'port': port,
'user': user,
'password': password,
'db': db,
'charset': charset,
'cursorclass': pymysql.cursors.DictCursor # 返回字典格式结果
}
self.conn = None
self.cursor = None
def connect(self):
"""建立数据库连接"""
try:
self.conn = pymysql.connect(**self.conn_params)
self.cursor = self.conn.cursor()
print("数据库连接成功")
except pymysql.Error as e:
print(f"数据库连接失败: {e}")
raise
这个配置类提供了以下功能:
- 可定制的连接参数(主机、端口、用户名等)
- 使用字典格式返回查询结果(cursorclass=pymysql.cursors.DictCursor)
- 统一的错误处理机制
在实际项目中,建议将数据库连接参数存储在配置文件中或环境变量中,而不是硬编码在代码里。
3. 数据库与表操作
3.1 数据库管理
在MySQLDB类中添加数据库管理方法:
python复制def create_database(self, db_name: str, charset='utf8mb4'):
"""创建数据库"""
try:
sql = f"CREATE DATABASE IF NOT EXISTS {db_name} DEFAULT CHARACTER SET {charset}"
self.cursor.execute(sql)
self.conn.select_db(db_name) # 切换到新创建的数据库
print(f"数据库 {db_name} 创建成功")
except pymysql.Error as e:
print(f"创建数据库失败: {e}")
raise
def drop_database(self, db_name: str):
"""删除数据库"""
try:
sql = f"DROP DATABASE IF EXISTS {db_name}"
self.cursor.execute(sql)
print(f"数据库 {db_name} 删除成功")
except pymysql.Error as e:
print(f"删除数据库失败: {e}")
raise
3.2 表管理
下面是一个创建用户表的示例:
python复制def create_user_table(self):
"""创建用户表"""
try:
sql = """
CREATE TABLE IF NOT EXISTS users (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '自增主键',
username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名',
password VARCHAR(100) NOT NULL COMMENT '密码',
email VARCHAR(100) UNIQUE COMMENT '邮箱',
age INT COMMENT '年龄',
is_active BOOLEAN DEFAULT TRUE COMMENT '是否激活',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_username (username),
INDEX idx_email (email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
"""
self.cursor.execute(sql)
self.conn.commit()
print("用户表创建成功")
except pymysql.Error as e:
self.conn.rollback()
print(f"创建表失败: {e}")
raise
这个表设计考虑了以下最佳实践:
- 使用自增主键
- 为常用查询字段添加索引
- 设置合适的字段类型和长度
- 添加注释说明字段用途
- 使用utf8mb4字符集支持完整Unicode
- 添加created_at和updated_at时间戳
- 设置合理的默认值
4. CRUD操作实现
4.1 插入操作
单条插入
python复制def insert_user(self, username: str, password: str, email: str = None,
age: int = None) -> int:
"""插入单个用户"""
try:
sql = """
INSERT INTO users(username, password, email, age)
VALUES (%s, %s, %s, %s)
"""
self.cursor.execute(sql, (username, password, email, age))
self.conn.commit()
user_id = self.cursor.lastrowid
print(f"用户 {username} 插入成功,ID: {user_id}")
return user_id
except pymysql.Error as e:
self.conn.rollback()
print(f"插入用户失败: {e}")
raise
批量插入
python复制def batch_insert_users(self, users: List[Dict]):
"""批量插入用户"""
try:
sql = """
INSERT INTO users(username, password, email, age)
VALUES (%s, %s, %s, %s)
"""
values = [(u['username'], u['password'],
u.get('email'), u.get('age')) for u in users]
self.cursor.executemany(sql, values)
self.conn.commit()
print(f"批量插入 {len(users)} 个用户成功")
except pymysql.Error as e:
self.conn.rollback()
print(f"批量插入用户失败: {e}")
raise
提示:批量插入比循环单条插入效率高得多,特别是数据量较大时。实测插入1000条记录,批量插入比单条插入快10倍以上。
4.2 查询操作
基础查询
python复制def get_user_by_id(self, user_id: int) -> Optional[Dict]:
"""根据ID查询用户"""
try:
sql = "SELECT * FROM users WHERE id = %s"
self.cursor.execute(sql, (user_id,))
return self.cursor.fetchone()
except pymysql.Error as e:
print(f"查询用户失败: {e}")
raise
def get_users_by_condition(self, min_age: int = None,
max_age: int = None,
is_active: bool = None) -> List[Dict]:
"""根据条件查询用户"""
try:
sql = "SELECT * FROM users WHERE 1=1"
params = []
if min_age is not None:
sql += " AND age >= %s"
params.append(min_age)
if max_age is not None:
sql += " AND age <= %s"
params.append(max_age)
if is_active is not None:
sql += " AND is_active = %s"
params.append(is_active)
self.cursor.execute(sql, tuple(params))
return self.cursor.fetchall()
except pymysql.Error as e:
print(f"查询用户失败: {e}")
raise
分页查询
python复制def get_users_with_pagination(self, page: int = 1,
page_size: int = 10,
order_by: str = 'id',
desc: bool = False) -> List[Dict]:
"""分页查询用户"""
try:
offset = (page - 1) * page_size
direction = 'DESC' if desc else 'ASC'
sql = f"""
SELECT * FROM users
ORDER BY {order_by} {direction}
LIMIT %s OFFSET %s
"""
self.cursor.execute(sql, (page_size, offset))
return self.cursor.fetchall()
except pymysql.Error as e:
print(f"分页查询失败: {e}")
raise
4.3 更新操作
python复制def update_user(self, user_id: int, **kwargs) -> bool:
"""更新用户信息"""
try:
if not kwargs:
return False
set_clause = []
params = []
for field, value in kwargs.items():
set_clause.append(f"{field} = %s")
params.append(value)
params.append(user_id)
sql = f"UPDATE users SET {', '.join(set_clause)} WHERE id = %s"
result = self.cursor.execute(sql, tuple(params))
self.conn.commit()
return result > 0
except pymysql.Error as e:
self.conn.rollback()
print(f"更新用户失败: {e}")
raise
4.4 删除操作
python复制def delete_user(self, user_id: int) -> bool:
"""删除用户"""
try:
sql = "DELETE FROM users WHERE id = %s"
result = self.cursor.execute(sql, (user_id,))
self.conn.commit()
return result > 0
except pymysql.Error as e:
self.conn.rollback()
print(f"删除用户失败: {e}")
raise
5. 高级功能实现
5.1 事务处理
事务是确保数据一致性的关键机制。下面是一个转账操作的示例:
python复制def transfer_money(self, from_account: int, to_account: int, amount: float) -> bool:
"""转账操作"""
try:
# 开始事务
self.conn.begin()
# 检查转出账户余额
check_sql = "SELECT balance FROM accounts WHERE id = %s FOR UPDATE"
self.cursor.execute(check_sql, (from_account,))
from_balance = self.cursor.fetchone()['balance']
if from_balance < amount:
raise ValueError("余额不足")
# 扣除转出账户金额
deduct_sql = "UPDATE accounts SET balance = balance - %s WHERE id = %s"
self.cursor.execute(deduct_sql, (amount, from_account))
# 增加转入账户金额
add_sql = "UPDATE accounts SET balance = balance + %s WHERE id = %s"
self.cursor.execute(add_sql, (amount, to_account))
# 记录交易
log_sql = """
INSERT INTO transactions(from_account, to_account, amount, created_at)
VALUES (%s, %s, %s, NOW())
"""
self.cursor.execute(log_sql, (from_account, to_account, amount))
# 提交事务
self.conn.commit()
return True
except Exception as e:
# 回滚事务
self.conn.rollback()
print(f"转账失败: {e}")
raise
5.2 存储过程调用
MySQL存储过程可以提高复杂操作的执行效率。下面是如何调用存储过程的示例:
python复制def call_get_user_stats(self, min_age: int) -> Dict:
"""调用获取用户统计信息的存储过程"""
try:
sql = "CALL get_user_stats(%s)"
self.cursor.execute(sql, (min_age,))
result = self.cursor.fetchone()
# 存储过程可能返回多个结果集
while self.cursor.nextset():
pass
return result
except pymysql.Error as e:
print(f"调用存储过程失败: {e}")
raise
6. 性能优化与安全实践
6.1 性能优化技巧
- 使用连接池:频繁创建和销毁连接开销很大。可以使用DBUtils实现连接池:
python复制from dbutils.pooled_db import PooledDB
pool = PooledDB(
creator=pymysql,
maxconnections=10,
mincached=2,
host='localhost',
user='root',
password='',
db='test',
charset='utf8mb4'
)
def get_connection():
return pool.connection()
-
批量操作:尽量使用executemany进行批量插入/更新,而不是循环执行单条SQL。
-
合理使用索引:为常用查询条件创建索引,但不要过度索引。
-
只查询需要的字段:避免使用SELECT *,只查询必要的字段。
-
使用服务器端游标:大数据量查询时使用SSCursor:
python复制from pymysql.cursors import SSCursor
conn = pymysql.connect(..., cursorclass=SSCursor)
6.2 安全最佳实践
- 永远使用参数化查询:防止SQL注入攻击的最有效方法。
错误做法:
python复制sql = f"SELECT * FROM users WHERE username = '{username}'"
正确做法:
python复制sql = "SELECT * FROM users WHERE username = %s"
self.cursor.execute(sql, (username,))
-
最小权限原则:数据库用户只授予必要的最小权限。
-
加密敏感数据:密码等敏感信息应该加盐哈希存储,不要明文存储。
-
使用SSL连接:生产环境应该启用SSL加密连接:
python复制conn = pymysql.connect(
...,
ssl={'ca': '/path/to/ca.pem'}
)
- 定期备份:建立数据库定期备份机制。
7. 常见问题与解决方案
7.1 连接问题
问题1:无法连接到MySQL服务器
解决方案:
- 检查MySQL服务是否运行
- 检查主机、端口是否正确
- 检查用户名密码是否正确
- 检查防火墙设置是否允许连接
- 检查MySQL是否配置了允许远程连接
问题2:连接超时
解决方案:
- 增加连接超时时间:
python复制conn = pymysql.connect(..., connect_timeout=10)
- 使用连接池复用连接
- 检查网络状况
7.2 编码问题
问题:插入或查询中文出现乱码
解决方案:
- 确保连接使用utf8mb4字符集:
python复制conn = pymysql.connect(..., charset='utf8mb4')
- 确保数据库和表使用utf8mb4字符集
- 确保Python源代码文件使用UTF-8编码
7.3 性能问题
问题:查询速度慢
解决方案:
- 为查询条件添加合适的索引
- 优化SQL语句,避免全表扫描
- 只查询需要的字段
- 考虑添加缓存层(如Redis)
- 对于复杂查询,考虑使用存储过程
7.4 事务问题
问题:事务不生效
解决方案:
- 确保表使用支持事务的引擎(如InnoDB)
- 确保正确使用commit()提交事务
- 检查是否有自动提交设置被开启
8. 完整示例项目
下面是一个完整的用户管理系统示例:
python复制import pymysql
from typing import List, Dict, Optional
from datetime import datetime
class UserManager:
def __init__(self, host='localhost', port=3306, user='root',
password='', db='user_management'):
self.db_config = {
'host': host,
'port': port,
'user': user,
'password': password,
'db': db,
'charset': 'utf8mb4',
'cursorclass': pymysql.cursors.DictCursor
}
self.conn = None
def __enter__(self):
"""支持with语句"""
self.connect()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""支持with语句"""
self.close()
def connect(self):
"""连接数据库"""
try:
self.conn = pymysql.connect(**self.db_config)
print("数据库连接成功")
except pymysql.Error as e:
print(f"数据库连接失败: {e}")
raise
def close(self):
"""关闭连接"""
if self.conn:
self.conn.close()
print("数据库连接已关闭")
def initialize_database(self):
"""初始化数据库"""
try:
with self.conn.cursor() as cursor:
# 创建用户表
cursor.execute("""
CREATE TABLE IF NOT EXISTS users (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE,
age INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_username (username),
INDEX idx_email (email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
""")
# 创建日志表
cursor.execute("""
CREATE TABLE IF NOT EXISTS user_logs (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
action VARCHAR(20) NOT NULL,
details TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
""")
self.conn.commit()
print("数据库初始化成功")
except pymysql.Error as e:
self.conn.rollback()
print(f"数据库初始化失败: {e}")
raise
def add_user(self, username: str, password: str,
email: str = None, age: int = None) -> int:
"""添加用户"""
try:
with self.conn.cursor() as cursor:
# 插入用户
sql = """
INSERT INTO users(username, password, email, age)
VALUES (%s, %s, %s, %s)
"""
cursor.execute(sql, (username, password, email, age))
user_id = cursor.lastrowid
# 记录日志
log_sql = """
INSERT INTO user_logs(user_id, action, details)
VALUES (%s, 'create', %s)
"""
cursor.execute(log_sql, (user_id, f"创建用户 {username}"))
self.conn.commit()
print(f"用户 {username} 添加成功,ID: {user_id}")
return user_id
except pymysql.Error as e:
self.conn.rollback()
print(f"添加用户失败: {e}")
raise
def get_user(self, user_id: int) -> Optional[Dict]:
"""获取用户信息"""
try:
with self.conn.cursor() as cursor:
sql = """
SELECT u.*,
(SELECT COUNT(*) FROM user_logs WHERE user_id = u.id) AS log_count
FROM users u
WHERE u.id = %s
"""
cursor.execute(sql, (user_id,))
return cursor.fetchone()
except pymysql.Error as e:
print(f"获取用户信息失败: {e}")
raise
def search_users(self, keyword: str = None,
min_age: int = None, max_age: int = None,
page: int = 1, page_size: int = 10) -> Dict:
"""搜索用户"""
try:
with self.conn.cursor() as cursor:
sql = "SELECT * FROM users WHERE 1=1"
count_sql = "SELECT COUNT(*) AS total FROM users WHERE 1=1"
params = []
if keyword:
sql += " AND (username LIKE %s OR email LIKE %s)"
count_sql += " AND (username LIKE %s OR email LIKE %s)"
params.extend([f"%{keyword}%", f"%{keyword}%"])
if min_age is not None:
sql += " AND age >= %s"
count_sql += " AND age >= %s"
params.append(min_age)
if max_age is not None:
sql += " AND age <= %s"
count_sql += " AND age <= %s"
params.append(max_age)
# 获取总数
cursor.execute(count_sql, tuple(params))
total = cursor.fetchone()['total']
# 分页查询
offset = (page - 1) * page_size
sql += " ORDER BY id LIMIT %s OFFSET %s"
params.extend([page_size, offset])
cursor.execute(sql, tuple(params))
users = cursor.fetchall()
return {
'total': total,
'page': page,
'page_size': page_size,
'users': users
}
except pymysql.Error as e:
print(f"搜索用户失败: {e}")
raise
def update_user_password(self, user_id: int, new_password: str) -> bool:
"""更新用户密码"""
try:
with self.conn.cursor() as cursor:
# 更新密码
sql = "UPDATE users SET password = %s WHERE id = %s"
result = cursor.execute(sql, (new_password, user_id))
# 记录日志
if result:
log_sql = """
INSERT INTO user_logs(user_id, action, details)
VALUES (%s, 'update_password', '密码已更新')
"""
cursor.execute(log_sql, (user_id,))
self.conn.commit()
return result > 0
except pymysql.Error as e:
self.conn.rollback()
print(f"更新密码失败: {e}")
raise
def delete_user(self, user_id: int) -> bool:
"""删除用户"""
try:
with self.conn.cursor() as cursor:
# 先获取用户信息用于日志
user_sql = "SELECT username FROM users WHERE id = %s"
cursor.execute(user_sql, (user_id,))
user = cursor.fetchone()
if not user:
return False
# 删除用户
delete_sql = "DELETE FROM users WHERE id = %s"
result = cursor.execute(delete_sql, (user_id,))
# 记录日志
if result:
log_sql = """
INSERT INTO user_logs(user_id, action, details)
VALUES (%s, 'delete', %s)
"""
cursor.execute(log_sql,
(user_id, f"删除用户 {user['username']}"))
self.conn.commit()
return result > 0
except pymysql.Error as e:
self.conn.rollback()
print(f"删除用户失败: {e}")
raise
# 使用示例
if __name__ == "__main__":
with UserManager(password='securepassword') as manager:
# 初始化数据库
manager.initialize_database()
# 添加用户
user_id = manager.add_user("testuser", "Test@123", "test@example.com", 25)
# 查询用户
user = manager.get_user(user_id)
print("用户信息:", user)
# 搜索用户
result = manager.search_users(keyword="test")
print("搜索结果:", result)
# 更新密码
manager.update_user_password(user_id, "New@123")
# 再次查询
user = manager.get_user(user_id)
print("更新后的用户信息:", user)
# 删除用户
manager.delete_user(user_id)
这个示例展示了:
- 完整的数据库连接管理
- 数据库初始化
- 用户CRUD操作
- 分页搜索功能
- 操作日志记录
- 事务处理
- 使用with语句管理资源
在实际开发中,你可以基于这个示例进行扩展,添加更多业务逻辑和功能模块。