一、问题现象与根源分析
当开发者使用Python的pymysql库执行大数据量查询时,调用fetchall()方法经常遭遇MemoryError异常。典型场景包括:
- 查询结果包含百万级记录
- 返回BLOB/TEXT类型大字段
- 连接未设置游标类型
根本原因在于fetchall()的全量加载机制:
# 问题代码示例
import pymysql
conn = pymysql.connect(host='localhost', user='root', password='', db='test')
cursor = conn.cursor()
cursor.execute("SELECT * FROM large_table") # 百万行数据
results = cursor.fetchall() # 一次性加载所有数据到内存
二、5种解决方案对比
1. 使用SS游标(服务端游标)
通过SSCursor实现流式读取:
from pymysql.cursors import SSCursor
cursor = conn.cursor(SSCursor)
cursor.execute("SELECT * FROM large_table")
while True:
row = cursor.fetchone() # 每次只加载一行
if not row: break
2. 分块批处理(Chunking)
结合LIMIT和OFFSET实现分批加载:
chunk_size = 1000
for offset in range(0, total_rows, chunk_size):
cursor.execute(f"SELECT * FROM large_table LIMIT {offset}, {chunk_size}")
chunk = cursor.fetchall()
3. 使用DictCursor优化内存
当只需要部分字段时:
from pymysql.cursors import DictCursor
cursor = conn.cursor(DictCursor)
cursor.execute("SELECT id, name FROM large_table")
三、性能基准测试
| 方法 | 100万行耗时 | 内存峰值 |
|---|---|---|
| fetchall | 12s | 2.8GB |
| SSCursor | 25s | 50MB |
四、最佳实践建议
- 预估数据量:先执行
COUNT(*)查询 - 字段精简:避免
SELECT * - 连接参数:设置
init_command='SET SESSION max_allowed_packet=64MB'
五、进阶方案
对于超大数据集可考虑:
- 使用
LOAD DATA INFILE直接导出 - 采用Apache Spark等分布式处理框架