1. PyMySQL简介与安装配置
PyMySQL是一个纯Python实现的MySQL客户端库,完全兼容Python DB-API 2.0规范(PEP 249)。与MySQL官方提供的MySQL Connector/Python相比,PyMySQL不需要任何外部依赖,这使得它在各种Python环境中都能轻松部署使用。
1.1 环境准备
在开始使用PyMySQL之前,你需要确保系统中已安装以下组件:
- Python环境:PyMySQL支持CPython 3.9及以上版本,以及最新的PyPy 3.x版本。建议使用Python 3.9+以获得最佳兼容性
- MySQL服务器:支持MySQL 5.7+和MariaDB 10.2+等主流版本
提示:可以通过
python --version和mysql --version命令分别检查Python和MySQL的版本
1.2 安装PyMySQL
安装PyMySQL非常简单,使用pip即可完成:
bash复制# 基础安装
pip install PyMySQL
# 如果需要使用sha256_password或caching_sha2_password认证方式
pip install PyMySQL[rsa]
# 如果需要支持MariaDB的ed25519认证
pip install PyMySQL[ed25519]
在实际项目中,我强烈建议将PyMySQL添加到项目的requirements.txt文件中,这样可以确保所有开发者和部署环境使用相同的版本:
code复制PyMySQL==1.1.0
1.3 验证安装
安装完成后,可以通过Python交互式环境验证是否安装成功:
python复制import pymysql
print(pymysql.__version__) # 应该输出安装的版本号
如果没有任何错误提示,说明PyMySQL已经正确安装。
2. 建立数据库连接
2.1 基础连接配置
使用PyMySQL连接MySQL数据库的基本方式如下:
python复制import pymysql
connection = pymysql.connect(
host='localhost', # 数据库服务器地址
user='username', # 数据库用户名
password='password', # 数据库密码
database='dbname', # 数据库名称
port=3306, # 端口号,默认3306
charset='utf8mb4', # 字符集
cursorclass=pymysql.cursors.DictCursor # 返回字典形式的游标
)
在实际项目中,我建议将这些连接参数存储在配置文件中,而不是硬编码在代码中。例如使用config.py:
python复制# config.py
DB_CONFIG = {
'host': 'localhost',
'user': 'username',
'password': 'password',
'database': 'dbname',
'charset': 'utf8mb4',
'cursorclass': pymysql.cursors.DictCursor
}
然后在主代码中引用:
python复制from config import DB_CONFIG
connection = pymysql.connect(**DB_CONFIG)
2.2 连接池管理
对于需要频繁连接数据库的应用,建议使用连接池来提高性能。PyMySQL本身不提供连接池功能,但可以通过第三方库如DBUtils实现:
python复制from dbutils.pooled_db import PooledDB
import pymysql
pool = PooledDB(
creator=pymysql,
maxconnections=10,
mincached=2,
**DB_CONFIG
)
# 使用连接池
connection = pool.connection()
注意:连接池的大小应根据实际应用负载进行调整,过大的连接数可能导致数据库性能下降
2.3 连接异常处理
数据库连接可能会出现各种异常,良好的异常处理是必不可少的:
python复制try:
connection = pymysql.connect(**DB_CONFIG)
with connection.cursor() as cursor:
cursor.execute("SELECT VERSION()")
result = cursor.fetchone()
print("Database version:", result)
except pymysql.MySQLError as e:
print(f"Error connecting to MySQL: {e}")
finally:
if connection and connection.open:
connection.close()
常见的MySQL错误代码包括:
- 2003: 无法连接到数据库服务器
- 1045: 访问被拒绝(错误的用户名或密码)
- 1049: 未知数据库
3. 基本CRUD操作
3.1 创建表
在PyMySQL中执行DDL语句(如CREATE TABLE)与执行普通查询类似:
python复制create_table_sql = """
CREATE TABLE IF NOT EXISTS `users` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(100) NOT NULL,
`email` VARCHAR(255) NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
"""
with connection.cursor() as cursor:
cursor.execute(create_table_sql)
connection.commit()
提示:使用IF NOT EXISTS可以避免表已存在时的错误
3.2 插入数据
插入数据时,建议使用参数化查询来防止SQL注入:
python复制insert_sql = "INSERT INTO `users` (`name`, `email`) VALUES (%s, %s)"
try:
with connection.cursor() as cursor:
cursor.execute(insert_sql, ('张三', 'zhangsan@example.com'))
cursor.execute(insert_sql, ('李四', 'lisi@example.com'))
connection.commit()
except pymysql.IntegrityError as e:
print(f"插入失败,可能是唯一键冲突: {e}")
connection.rollback()
批量插入可以使用executemany方法提高效率:
python复制users = [
('王五', 'wangwu@example.com'),
('赵六', 'zhaoliu@example.com')
]
with connection.cursor() as cursor:
cursor.executemany(insert_sql, users)
connection.commit()
3.3 查询数据
PyMySQL提供了多种获取查询结果的方法:
python复制# 查询单条记录
fetch_one_sql = "SELECT * FROM `users` WHERE `id` = %s"
with connection.cursor() as cursor:
cursor.execute(fetch_one_sql, (1,))
user = cursor.fetchone()
print(user) # 返回字典或元组,取决于cursorclass
# 查询多条记录
fetch_all_sql = "SELECT * FROM `users` LIMIT 10"
with connection.cursor() as cursor:
cursor.execute(fetch_all_sql)
users = cursor.fetchall()
for user in users:
print(user)
# 分批获取大量数据
big_query_sql = "SELECT * FROM `large_table`"
with connection.cursor() as cursor:
cursor.execute(big_query_sql)
while True:
batch = cursor.fetchmany(size=1000)
if not batch:
break
process_batch(batch) # 自定义处理函数
3.4 更新和删除数据
更新和删除操作需要注意事务处理:
python复制# 更新数据
update_sql = "UPDATE `users` SET `name` = %s WHERE `id` = %s"
try:
with connection.cursor() as cursor:
affected_rows = cursor.execute(update_sql, ('张三丰', 1))
print(f"更新了{affected_rows}行")
connection.commit()
except Exception as e:
connection.rollback()
print(f"更新失败: {e}")
# 删除数据
delete_sql = "DELETE FROM `users` WHERE `id` = %s"
try:
with connection.cursor() as cursor:
affected_rows = cursor.execute(delete_sql, (1,))
print(f"删除了{affected_rows}行")
connection.commit()
except Exception as e:
connection.rollback()
print(f"删除失败: {e}")
4. 高级特性与最佳实践
4.1 事务管理
PyMySQL默认不会自动提交事务,需要显式调用commit():
python复制try:
with connection.cursor() as cursor:
# 操作1
cursor.execute("INSERT INTO table1 VALUES (...)")
# 操作2
cursor.execute("UPDATE table2 SET ...")
connection.commit() # 只有所有操作都成功才提交
except Exception as e:
connection.rollback() # 任何一个操作失败就回滚
print(f"事务执行失败: {e}")
也可以使用上下文管理器简化事务处理:
python复制from contextlib import contextmanager
@contextmanager
def transaction(conn):
try:
yield
conn.commit()
except Exception:
conn.rollback()
raise
with transaction(connection):
with connection.cursor() as cursor:
cursor.execute("...")
cursor.execute("...")
4.2 存储过程调用
PyMySQL支持调用MySQL存储过程:
python复制call_proc_sql = "CALL get_user_by_id(%s)"
with connection.cursor() as cursor:
cursor.execute(call_proc_sql, (1,))
results = cursor.fetchall()
for row in results:
print(row)
4.3 批量操作优化
对于大批量数据操作,可以考虑以下优化:
- 批量插入优化:
python复制# 禁用自动提交
connection.autocommit(False)
# 批量插入
insert_sql = "INSERT INTO `large_table` VALUES (%s, %s, %s)"
data = [(i, f"name{i}", f"email{i}@example.com") for i in range(10000)]
with connection.cursor() as cursor:
for i in range(0, len(data), 1000): # 每1000条提交一次
batch = data[i:i+1000]
cursor.executemany(insert_sql, batch)
connection.commit()
- LOAD DATA INFILE:
对于非常大的数据集,使用LOAD DATA INFILE比INSERT快得多:
python复制load_data_sql = """
LOAD DATA LOCAL INFILE '/path/to/data.csv'
INTO TABLE `large_table`
FIELDS TERMINATED BY ','
LINES TERMINATED BY '\n'
"""
with connection.cursor() as cursor:
cursor.execute(load_data_sql)
connection.commit()
4.4 ORM集成
虽然PyMySQL可以直接操作数据库,但在大型项目中,使用ORM(如SQLAlchemy)可能更合适:
python复制from sqlalchemy import create_engine
# 使用PyMySQL作为SQLAlchemy的驱动
engine = create_engine('mysql+pymysql://user:password@localhost/dbname')
# 执行原生SQL
with engine.connect() as conn:
result = conn.execute("SELECT * FROM users")
for row in result:
print(row)
5. 常见问题与性能优化
5.1 连接问题排查
问题1:无法连接到数据库
可能原因及解决方案:
- 服务器地址或端口错误 - 检查host和port参数
- 防火墙阻止 - 检查服务器防火墙设置
- MySQL未授权远程连接 - 检查用户权限
问题2:连接超时
python复制# 增加连接超时参数
connection = pymysql.connect(
**DB_CONFIG,
connect_timeout=10, # 连接超时时间(秒)
read_timeout=30 # 读取超时时间(秒)
)
5.2 字符编码问题
MySQL的utf8编码实际上是utf8mb3,不支持完整的Unicode字符(如emoji)。推荐使用utf8mb4:
python复制connection = pymysql.connect(
**DB_CONFIG,
charset='utf8mb4', # 使用完整的UTF-8编码
init_command='SET NAMES utf8mb4'
)
5.3 性能优化建议
- 使用连接池:如前所述,使用DBUtils等库实现连接池
- 合理使用索引:确保查询条件中的字段有适当索引
- 批量操作:使用executemany代替循环执行单个INSERT
- 服务器端游标:对于大结果集,使用SScursor减少内存使用
python复制# 使用服务器端游标
with connection.cursor(pymysql.cursors.SSCursor) as cursor:
cursor.execute("SELECT * FROM very_large_table")
for row in cursor:
process_row(row)
5.4 安全注意事项
- 永远使用参数化查询:
python复制# 错误做法 - SQL注入风险
cursor.execute(f"SELECT * FROM users WHERE name = '{user_input}'")
# 正确做法
cursor.execute("SELECT * FROM users WHERE name = %s", (user_input,))
- 最小权限原则:数据库用户只应拥有必要的最小权限
- 敏感信息保护:不要在代码或配置文件中硬编码密码
6. 实际项目中的应用示例
6.1 Web应用中的数据库操作
在Flask Web应用中集成PyMySQL的典型模式:
python复制from flask import Flask, g
import pymysql
app = Flask(__name__)
def get_db():
if 'db' not in g:
g.db = pymysql.connect(**DB_CONFIG)
return g.db
@app.teardown_appcontext
def close_db(e=None):
db = g.pop('db', None)
if db is not None:
db.close()
@app.route('/users')
def list_users():
db = get_db()
with db.cursor() as cursor:
cursor.execute("SELECT * FROM users")
users = cursor.fetchall()
return {'users': users}
6.2 数据迁移脚本
使用PyMySQL编写数据迁移脚本的示例:
python复制def migrate_data(source_config, target_config):
source_conn = pymysql.connect(**source_config)
target_conn = pymysql.connect(**target_config)
try:
with source_conn.cursor() as source_cursor, \
target_conn.cursor() as target_cursor:
# 读取源数据
source_cursor.execute("SELECT * FROM old_table")
batch_size = 1000
while True:
rows = source_cursor.fetchmany(batch_size)
if not rows:
break
# 转换数据格式
transformed = [transform_row(row) for row in rows]
# 写入目标数据库
target_cursor.executemany(
"INSERT INTO new_table VALUES (%s, %s, %s)",
transformed
)
target_conn.commit()
finally:
source_conn.close()
target_conn.close()
6.3 数据库监控脚本
使用PyMySQL实现简单的数据库监控:
python复制def monitor_database(conn, interval=60):
while True:
try:
with conn.cursor() as cursor:
# 检查连接数
cursor.execute("SHOW STATUS LIKE 'Threads_connected'")
threads = cursor.fetchone()
# 检查慢查询
cursor.execute("SHOW STATUS LIKE 'Slow_queries'")
slow_queries = cursor.fetchone()
print(f"当前连接数: {threads['Value']}, 慢查询数: {slow_queries['Value']}")
except pymysql.Error as e:
print(f"监控出错: {e}")
time.sleep(interval)
7. 与其它Python MySQL驱动的比较
7.1 PyMySQL vs MySQL Connector/Python
| 特性 | PyMySQL | MySQL Connector/Python |
|---|---|---|
| 实现方式 | 纯Python | C扩展 |
| 性能 | 中等 | 高 |
| 依赖 | 无 | 需要编译或预编译二进制 |
| Python版本支持 | 3.9+ | 更广泛的Python版本 |
| 功能完整性 | 完整 | 更完整 |
| 社区支持 | 活跃 | Oracle官方支持 |
7.2 选择建议
- 选择PyMySQL:当需要纯Python解决方案、部署环境受限或使用较新Python版本时
- 选择MySQL Connector:当需要最佳性能、官方支持或使用较旧Python版本时
在实际项目中,我发现PyMySQL在大多数场景下已经足够好,特别是对于不追求极致性能的应用。它的纯Python实现使得部署更加简单,特别是在容器化环境中。
8. 调试与日志记录
8.1 启用PyMySQL日志
PyMySQL内置了简单的日志记录功能,可以通过Python的logging模块启用:
python复制import logging
# 配置日志
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger('pymysql')
# 现在所有PyMySQL操作都会记录日志
connection = pymysql.connect(**DB_CONFIG)
8.2 查询性能分析
可以使用MySQL的EXPLAIN命令分析查询性能:
python复制def analyze_query(sql, params=None):
with connection.cursor() as cursor:
cursor.execute(f"EXPLAIN {sql}", params or ())
result = cursor.fetchall()
print("查询执行计划:")
for row in result:
print(f"ID: {row['id']}, 类型: {row['select_type']}, 表: {row['table']}")
print(f"可能的索引: {row['possible_keys']}, 实际使用的索引: {row['key']}")
print(f"扫描行数: {row['rows']}, 额外信息: {row['Extra']}\n")
8.3 慢查询日志
在MySQL配置中启用慢查询日志可以帮助识别性能问题:
ini复制# my.cnf 或 my.ini
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 1
然后可以通过PyMySQL定期检查慢查询日志:
python复制def check_slow_queries(conn):
with conn.cursor() as cursor:
cursor.execute("""
SELECT * FROM mysql.slow_log
ORDER BY start_time DESC
LIMIT 10
""")
slow_queries = cursor.fetchall()
for query in slow_queries:
print(f"慢查询: {query['query']}")
print(f"执行时间: {query['query_time']}秒")
print(f"时间: {query['start_time']}\n")
9. 版本兼容性与升级
9.1 PyMySQL版本兼容性
PyMySQL主要版本更新可能会引入一些不兼容的变化。在升级前应该:
- 查看CHANGELOG了解变更内容
- 在测试环境中验证应用兼容性
- 逐步在生产环境中部署
9.2 MySQL 8.0的新特性支持
PyMySQL对MySQL 8.0的新特性提供了良好支持,包括:
- caching_sha2_password认证:需要安装PyMySQL[rsa]扩展
- 窗口函数:完全支持
- JSON增强功能:支持JSON字段操作
python复制# MySQL 8.0 JSON操作示例
with connection.cursor() as cursor:
cursor.execute("""
SELECT
user->>'$.name' as name,
user->>'$.email' as email
FROM users_json
WHERE user->>'$.age' > %s
""", (30,))
results = cursor.fetchall()
9.3 升级策略
对于生产环境,建议采用以下升级策略:
- 先在测试环境验证新版本
- 使用数据库迁移工具(如Alembic)管理模式变更
- 考虑使用蓝绿部署减少停机时间
- 确保有完整的回滚计划
10. 扩展与高级用法
10.1 自定义类型转换
PyMySQL允许注册自定义的类型转换器:
python复制import pymysql
from datetime import datetime
def parse_mysql_time(value):
if value is None:
return None
return datetime.strptime(value.decode('utf-8'), '%Y-%m-%d %H:%M:%S')
# 注册类型转换器
pymysql.converters.conversions[pymysql.FIELD_TYPE.DATETIME] = parse_mysql_time
# 现在所有DATETIME字段都会通过parse_mysql_time函数转换
10.2 异步支持
虽然PyMySQL本身是同步的,但可以通过aiomysql实现异步操作:
python复制import asyncio
import aiomysql
async def async_query():
conn = await aiomysql.connect(**DB_CONFIG)
async with conn.cursor() as cursor:
await cursor.execute("SELECT * FROM users")
result = await cursor.fetchall()
print(result)
conn.close()
asyncio.run(async_query())
10.3 分布式事务
对于需要跨多个数据库的事务,可以使用XA事务:
python复制# 开始XA事务
with connection.cursor() as cursor:
cursor.execute("XA START 'transaction_id'")
try:
cursor.execute("INSERT INTO table1 VALUES (...)")
cursor.execute("UPDATE table2 SET ...")
cursor.execute("XA END 'transaction_id'")
cursor.execute("XA PREPARE 'transaction_id'")
cursor.execute("XA COMMIT 'transaction_id'")
except Exception:
cursor.execute("XA END 'transaction_id'")
cursor.execute("XA ROLLBACK 'transaction_id'")
raise
11. 测试与Mock
11.1 单元测试中的数据库测试
使用PyMySQL进行单元测试时,可以考虑以下模式:
python复制import unittest
import pymysql
class TestDatabase(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.connection = pymysql.connect(**TEST_DB_CONFIG)
cls.setup_test_data()
@classmethod
def tearDownClass(cls):
cls.connection.close()
@classmethod
def setup_test_data(cls):
with cls.connection.cursor() as cursor:
cursor.execute("CREATE TABLE IF NOT EXISTS test_users (...)")
cursor.execute("TRUNCATE TABLE test_users")
cursor.executemany("INSERT INTO test_users VALUES (...)", TEST_DATA)
cls.connection.commit()
def test_user_count(self):
with self.connection.cursor() as cursor:
cursor.execute("SELECT COUNT(*) FROM test_users")
count = cursor.fetchone()[0]
self.assertEqual(count, len(TEST_DATA))
11.2 使用Mock测试
对于不需要真实数据库连接的测试,可以使用unittest.mock:
python复制from unittest.mock import Mock, patch
def test_database_operation():
mock_conn = Mock()
mock_cursor = Mock()
mock_conn.cursor.return_value = mock_cursor
mock_cursor.fetchall.return_value = [{'id': 1, 'name': 'Test'}]
with patch('pymysql.connect', return_value=mock_conn):
result = get_users()
assert result == [{'id': 1, 'name': 'Test'}]
mock_cursor.execute.assert_called_once_with("SELECT * FROM users")
12. 性能基准测试
12.1 基本性能测试
可以使用timeit模块测试PyMySQL的基本操作性能:
python复制import timeit
def test_insert_performance():
setup = """
import pymysql
conn = pymysql.connect(**DB_CONFIG)
conn.autocommit(True)
cursor = conn.cursor()
"""
stmt = """
cursor.execute("INSERT INTO test_table VALUES (%s, %s)", (1, 'test'))
"""
times = timeit.repeat(stmt, setup, number=1000, repeat=5)
print(f"平均插入时间: {sum(times)/len(times)/1000:.6f}秒/次")
12.2 批量操作性能比较
比较单条插入与批量插入的性能差异:
python复制def compare_bulk_insert():
# 单条插入
single_time = timeit.timeit(
stmt="cursor.execute('INSERT INTO test_table VALUES (%s, %s)', (i, 'test'))",
setup="""
import pymysql
conn = pymysql.connect(**DB_CONFIG)
conn.autocommit(True)
cursor = conn.cursor()
i = 0
""",
number=1000
)
# 批量插入
bulk_time = timeit.timeit(
stmt="cursor.executemany('INSERT INTO test_table VALUES (%s, %s)', [(i, 'test') for i in range(1000)])",
setup="""
import pymysql
conn = pymysql.connect(**DB_CONFIG)
conn.autocommit(True)
cursor = conn.cursor()
""",
number=1
)
print(f"单条插入1000次: {single_time:.3f}秒")
print(f"批量插入1000条: {bulk_time:.3f}秒")
在实际测试中,我发现批量插入通常比单条插入快10-50倍,具体取决于网络延迟和服务器配置。
13. 安全最佳实践
13.1 连接安全
- 使用SSL加密连接:
python复制connection = pymysql.connect(
**DB_CONFIG,
ssl={
'ca': '/path/to/ca.pem',
'cert': '/path/to/client-cert.pem',
'key': '/path/to/client-key.pem'
}
)
- 避免密码硬编码:
python复制import os
from dotenv import load_dotenv
load_dotenv() # 从.env文件加载环境变量
connection = pymysql.connect(
host=os.getenv('DB_HOST'),
user=os.getenv('DB_USER'),
password=os.getenv('DB_PASSWORD'),
database=os.getenv('DB_NAME')
)
13.2 数据安全
- 敏感数据加密:
python复制from cryptography.fernet import Fernet
# 加密函数
def encrypt_data(data, key):
fernet = Fernet(key)
return fernet.encrypt(data.encode())
# 解密函数
def decrypt_data(encrypted_data, key):
fernet = Fernet(key)
return fernet.decrypt(encrypted_data).decode()
# 使用示例
key = Fernet.generate_key()
encrypted = encrypt_data('secret', key)
decrypted = decrypt_data(encrypted, key)
- 审计日志:
python复制def audit_log(action, user_id, details):
with connection.cursor() as cursor:
cursor.execute("""
INSERT INTO audit_log
(action, user_id, details, ip_address, user_agent)
VALUES (%s, %s, %s, %s, %s)
""", (action, user_id, str(details), request.remote_addr, request.user_agent))
connection.commit()
14. 监控与维护
14.1 数据库健康检查
定期运行健康检查脚本:
python复制def check_database_health(conn):
metrics = {}
with conn.cursor() as cursor:
# 检查连接数
cursor.execute("SHOW STATUS LIKE 'Threads_connected'")
metrics['connections'] = cursor.fetchone()['Value']
# 检查运行时间
cursor.execute("SHOW STATUS LIKE 'Uptime'")
metrics['uptime'] = int(cursor.fetchone()['Value'])
# 检查慢查询
cursor.execute("SHOW STATUS LIKE 'Slow_queries'")
metrics['slow_queries'] = cursor.fetchone()['Value']
# 检查锁等待
cursor.execute("SHOW STATUS LIKE 'Innodb_row_lock_waits'")
metrics['row_lock_waits'] = cursor.fetchone()['Value']
return metrics
14.2 自动化备份
使用PyMySQL实现简单的数据库备份:
python复制import subprocess
from datetime import datetime
def backup_database(config, backup_dir):
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
backup_file = f"{backup_dir}/backup_{timestamp}.sql"
command = [
'mysqldump',
f"--host={config['host']}",
f"--user={config['user']}",
f"--password={config['password']}",
config['database'],
f"--result-file={backup_file}"
]
try:
subprocess.run(command, check=True)
print(f"备份成功: {backup_file}")
return backup_file
except subprocess.CalledProcessError as e:
print(f"备份失败: {e}")
return None
15. 常见错误与解决方案
15.1 连接错误
错误:pymysql.err.OperationalError: (2003, "Can't connect to MySQL server")
可能原因:
- MySQL服务未运行
- 网络问题或防火墙阻止
- 错误的连接参数
解决方案:
- 检查MySQL服务状态
- 使用telnet测试端口连通性
- 验证连接参数
15.2 编码错误
错误:pymysql.err.InternalError: (1366, "Incorrect string value")
可能原因:尝试存储不支持的字符
解决方案:
- 确保使用utf8mb4字符集
- 检查数据是否包含无效字符
- 考虑在插入前清理数据
15.3 超时错误
错误:pymysql.err.OperationalError: (2013, 'Lost connection to MySQL server during query')
可能原因:
- 查询时间过长
- 网络不稳定
- 服务器端超时设置过短
解决方案:
- 增加read_timeout参数
- 优化查询性能
- 调整MySQL服务器的wait_timeout参数
15.4 事务错误
错误:pymysql.err.InternalError: (1205, 'Lock wait timeout exceeded')
可能原因:长时间运行的事务锁定了资源
解决方案:
- 减少事务持续时间
- 优化查询以减少锁定时间
- 考虑使用较低的隔离级别
16. 实际案例:电商系统数据库操作
16.1 订单处理流程
python复制def create_order(user_id, items):
try:
with connection.cursor() as cursor:
# 开始事务
connection.begin()
# 1. 创建订单主记录
cursor.execute("""
INSERT INTO orders (user_id, status, total_amount)
VALUES (%s, 'pending', 0)
""", (user_id,))
order_id = cursor.lastrowid
# 2. 添加订单项并计算总金额
total = 0
for item in items:
# 获取商品价格
cursor.execute("SELECT price FROM products WHERE id = %s", (item['product_id'],))
product = cursor.fetchone()
if not product:
raise ValueError(f"商品不存在: {item['product_id']}")
# 计算单项总价
item_total = product['price'] * item['quantity']
total += item_total
# 添加订单项
cursor.execute("""
INSERT INTO order_items
(order_id, product_id, quantity, price, total)
VALUES (%s, %s, %s, %s, %s)
""", (order_id, item['product_id'], item['quantity'], product['price'], item_total))
# 3. 更新订单总金额
cursor.execute("""
UPDATE orders SET total_amount = %s WHERE id = %s
""", (total, order_id))
# 提交事务
connection.commit()
return order_id
except Exception as e:
connection.rollback()
print(f"创建订单失败: {e}")
raise
16.2 库存管理
python复制def update_inventory(product_id, quantity_change):
try:
with connection.cursor() as cursor:
# 检查当前库存
cursor.execute("""
SELECT quantity FROM inventory
WHERE product_id = %s FOR UPDATE
""", (product_id,))
inventory = cursor.fetchone()
if not inventory:
raise ValueError(f"商品库存记录不存在: {product_id}")
new_quantity = inventory['quantity'] + quantity_change
if new_quantity < 0:
raise ValueError("库存不足")
# 更新库存
cursor.execute("""
UPDATE inventory SET quantity = %s
WHERE product_id = %s
""", (new_quantity, product_id))
# 记录库存变更
cursor.execute("""
INSERT INTO inventory_history
(product_id, change, new_quantity, reason)
VALUES (%s, %s, %s, %s)
""", (product_id, quantity_change, new_quantity, 'sale'))
connection.commit()
return new_quantity
except Exception as e:
connection.rollback()
print(f"更新库存失败: {e}")
raise
17. 性能调优实战
17.1 索引优化案例
问题场景:用户查询缓慢
python复制# 优化前
def get_user_orders(user_id):
with connection.cursor() as cursor:
cursor.execute("""
SELECT * FROM orders
WHERE user_id = %s
ORDER BY created_at DESC
""", (user_id,))
return cursor.fetchall()
# 执行计划分析
analyze_query("SELECT * FROM orders WHERE user_id = %s ORDER BY created_at DESC", (1,))
如果执行计划显示type=ALL(全表扫描),说明需要添加索引:
sql复制-- 添加复合索引
ALTER TABLE orders ADD INDEX idx_user_created (user_id, created_at);
优化后再次检查执行计划,应该显示type=ref或range,使用索引扫描。
17.2 查询重写案例
问题场景:统计每日订单数量缓慢
python复制# 优化前 - 使用GROUP BY
def get_daily_orders():
with connection.cursor() as cursor:
cursor.execute("""
SELECT DATE(created_at) as day, COUNT(*) as count
FROM orders
GROUP BY DATE(created_at)
ORDER BY day
""")
return cursor.fetchall()
# 优化后 - 使用预计算的统计表
def get_daily_orders_fast():
with connection.cursor() as cursor:
cursor.execute("""
SELECT day, order_count as count
FROM daily_order_stats
ORDER BY day
""")
return cursor.fetchall()
对于频繁执行的聚合查询,考虑使用物化视图或预计算表。
18. 未来发展与替代方案
18.1 PyMySQL的未来路线
根据PyMySQL的GitHub仓库和社区讨论,未来版本可能会:
- 增强对MySQL 8.0新特性的支持
- 改进异步支持
- 优化性能,特别是大数据量操作
- 提供更完善的类型系统支持
18.2 替代方案评估
除了PyMySQL,Python生态中还有其他MySQL连接方案:
- MySQL Connector/Python:官方驱动,性能更好但需要编译
- aiomysql:基于PyMySQL的异步版本
- SQLAlchemy:ORM层,可以使用PyMySQL作为底层驱动
- Django ORM:Django框架的内置ORM,支持多种数据库后端
选择建议:
- 需要纯Python解决方案:PyMySQL
- 需要最佳性能:MySQL Connector/Python
- 异步应用:aiomysql
- ORM需求:SQLAlchemy + PyMySQL
19. 社区资源与学习路径
19.1 学习资源推荐
-
官方文档:
- PyMySQL: https://pymysql.readthedocs.io/
- MySQL: https://dev.mysql.com/doc/
-
书籍:
- "Python数据库编程实战"
- "高性能MySQL"
3
