应使用pandas.read_sql()的chunksize参数分块读取大表,设为5000–50000整数,返回Iterator[pd.DataFrame],配合stream_results=True、显式dtype、ORDER BY+索引及追加模式导出,避免OOM和性能退化。

用 sqlalchemy + chunksize 流式读取,别用 pd.read_sql 一次性加载
直接调 pd.read_sql("SELECT * FROM big_table", engine) 很容易内存爆掉——Pandas 会把整张表拉进内存再转 DataFrame。实际中,1000 万行以上数据基本就卡死或 OOM。
正确做法是让数据库驱动分批返回结果,Pandas 自带 chunksize 参数就能干这事:
for chunk in pd.read_sql("SELECT id, name, amount FROM orders", engine, chunksize=50000):
# 每次处理一个 chunk(DataFrame),内存可控
process_chunk(chunk)
del chunk # 显式释放,尤其在循环里别省这步注意:chunksize 不是并发数,也不是线程数,它只控制每次从数据库 fetch 的行数;太小(如 100)会导致网络往返太多,太大(如 50 万)又可能撑爆单次内存。5k–50k 是较稳妥的区间,视单行数据体积调整。
导出到 CSV 时禁用 index=True,优先用 to_csv(..., mode="a") 追加写入
默认 df.to_csv("out.csv") 会重写整个文件,每 chunk 都覆盖一次,最后只剩最后一块数据。更糟的是,index=True 会额外加一列索引,导出后还得多删一列。
立即学习“Python免费学习笔记(深入)”;
批量导出要保证「不丢、不重、不慢」,关键点如下:
- 首次写入用
mode="w",并设header=True - 后续所有 chunk 用
mode="a"(append),且header=False - 显式指定
index=False,避免冗余列 - 加
encoding="utf-8-sig"防 Excel 打开乱码(Windows 用户刚需)
示例:
# 第一块
chunk0.to_csv("export.csv", mode="w", header=True, index=False, encoding="utf-8-sig")
<h1>后续块</h1><p>for chunk in rest_chunks:
chunk.to_csv("export.csv", mode="a", header=False, index=False, encoding="utf-8-sig")MySQL/PostgreSQL 连接记得配 stream_results=True 和 max_allowed_packet
即使用了 chunksize,如果底层 DBAPI 没开启流式查询,驱动仍可能缓存全部结果集。对 MySQL,需在 create_engine 里显式打开:
engine = create_engine(
"mysql+pymysql://u:p@h:3306/db",
connect_args={"autocommit": True},
# 关键:让 pymysql 不缓存全部结果
execution_options={"stream_results": True}
)PostgreSQL(psycopg2)默认支持流式,但也要确认连接参数没关掉:cursor_factory=RealDictCursor 不影响流式,但 namedtuple_cursor 类库可能破坏它。
另外,MySQL 容易报错 PacketTooLargeError,不是 Python 报的,是服务端拒绝发大包。得同步调大 MySQL 的 max_allowed_packet(比如设为 512M),否则哪怕 chunk 只有 1 万行,字段含 JSON 或 TEXT,也可能触发。
别在循环里反复创建 engine 或 connection
有人图省事,在每个 chunk 处理前都 create_engine 一次,或者 engine.connect() 开新连接——这会造成连接池耗尽、TCP TIME_WAIT 爆满、甚至数据库拒绝新连接。
正解只有一个:engine 全局创建一次,复用到底。需要事务控制?用 with engine.begin() as conn:,它自动 commit/rollback,且不独占连接。
如果你用的是 SQLAlchemy 2.0+,注意 engine.execute() 已废弃,统一走 conn.execute(text(...));老版本则确保 echo=False,不然每条 SQL 都打日志,IO 直接拖垮速度。
真正卡住性能的,往往不是 SQL 本身,而是连接管理失当和导出路径的 I/O 配置。这些细节不调好,跑再快的 SSD 也救不了。


















