根本原因是客户端默认启用mysql_store_result模式导致整表结果集全载入内存;须用--quick(mysqldump)或SSCursor(Python)、fetchone()等流式读取方式,禁用缓存。

导出大表时MySQL客户端直接OOM,不是数据太大,是默认缓存惹的祸
MySQL客户端(比如 mysqldump 或 Python 的 pymysql)执行 SELECT * 导出时,只要没显式禁用缓冲,就会把整张表结果集全 load 到内存里再吐出去——哪怕你只是想写到 CSV 文件。这不是“导出慢”,而是“导出即崩溃”。根本原因不是数据量本身,而是客户端默认启用 mysql_store_result 模式。
mysqldump 必须加 --skip-extended-insert 和 --quick
mysqldump 默认会把所有 INSERT 合并成一条长语句(extended-insert),同时缓存整张表结果(不 --quick 就走 mysql_store_result)。这两者叠加,导出 1000 万行就可能吃掉数 GB 内存。
-
--quick强制使用流式读取(mysql_use_result),边查边写,内存占用恒定在几 MB 级别 -
--skip-extended-insert让每行生成独立 INSERT,避免单条语句超长触发max_allowed_packet限制或解析开销 - 额外建议:
--single-transaction配合--quick用(InnoDB 表),避免锁表;但别加--lock-tables,它会阻塞写入且加剧内存压力
Python pymysql / mysqlclient 导出必须关 buffer
用 pymysql 或 mysqlclient 自己写导出脚本时,cursor.execute("SELECT ...") 后默认会把全部结果缓存在 Python 进程内存里。必须显式关闭缓冲:
-
pymysql:创建连接时加cursorclass=pymysql.cursors.SSCursor(SS = ServerSide),或执行前调用cursor.unbuffered() -
mysqlclient:用cursorclass=MySQLdb.cursors.SSCursor - 千万别用
fetchall();改用fetchone()或fetchmany(size=1000)控制每次读取量 - 如果还报
PacketTooLarge,说明服务端max_allowed_packet不够——但它只影响单条语句或单个字段长度,和流式导出无关;调大到256M即可(需服务端配置+重启或动态 SET)
max_allowed_packet 对导出的影响被严重高估
max_allowed_packet 控制的是「单次网络包最大尺寸」,不是「总导出数据上限」。它只在以下场景真正起作用:
- 导入 SQL 文件时某条 INSERT 超长(比如含超大 BLOB 字段)
- 执行含长字符串参数的存储过程或 PREPARE 语句
- 客户端发送超长查询(如 WHERE IN (1,2,...10w个))
而流式导出(--quick / SSCursor)每次只传一行或一批小结果,完全绕过这个限制。把它从 4M 改到 1G 对导出稳定性几乎无提升,反而可能掩盖真实问题——比如误以为调大它就能扛住全表缓存。
真正卡住导出的,永远是「是否缓存全部结果」这个开关,而不是 packet 大小。流式读取打开,1TB 表也能跑;关着,100 万行就 OOM。


















