MySQL 千万级数据查询完整解析:直接 SELECT * 后果 + 流式读取 + 分页优化 + 安全导出
1. 从 MySQL 内核角度:能不能一次读 1000W?
能读,但不是 “一次性加载到内存”。
InnoDB 处理 SELECT * FROM big_table 的真实流程:
- 服务器开启结果集流
- 每次从磁盘读取 1 个页(默认 16KB)
- 从页里解析出若干行数据
- 通过网络逐批发给客户端
- 读完一页再读下一页,直到 1000W 条全部读完发完
它不会:
- 先把 1000W 条全 load 进内存
- 也不会一次性拼成一个巨大数据包
所以技术上是可以读完 1000W 条的,只是时间问题。
2. 那为什么不能这么干?
虽然能读,但会触发生产级事故:
(1)长时间占用连接,事务快照不释放
- InnoDB 在 RR 隔离级别下,查询开始会生成一个一致性视图
- 只要查询没结束,undo log 不能清理
- 结果:回滚段膨胀、主从延迟飙升、更新语句被阻塞
(2)网络与客户端直接崩
- 1000W 条持续往客户端吐数据
- Navicat、JDBC 连接会OOM 崩溃
- 网卡被打满,影响整个应用
(3)MySQL 本身被拖垮
- 全表扫描,磁盘 IO 跑满
- 大量数据进入网络缓冲区,内存占用飙升
- 其他业务 SQL 排队、超时
3. 一句话区分:能读 vs 能用
-
能不能一次性读取 1000W? → 能,MySQL 支持流式读取超大结果集。
-
能不能在生产上这么执行? → 绝对不能,等于自杀。
4. 那 MySQL 到底怎么安全读 1000W?
用分批流式读取:
sql
SELECT * FROM t WHERE id > ? LIMIT 1000;
循环执行,直到读完。 这才是 MySQL 处理大数据量的标准姿势。
5、千万级表直接执行 SELECT * 会发生什么?
在 MySQL 中对一张1000 万条数据的表执行不带任何条件、无 LIMIT 的 SELECT *,技术上 MySQL 可以完成全表读取,但会引发一系列生产级风险,绝非简单的 “查询慢”:
- 全表扫描,磁盘 IO 瞬间打满 InnoDB 会逐页(默认 16KB)从磁盘加载数据,不走任何索引,磁盘 IO 利用率接近 100%,服务器整体性能急剧下降。
- 结果集过大,极易引发内存溢出 MySQL JDBC 默认全量拉取结果集,驱动会尝试将 1000 万条数据全部加载到客户端内存,直接导致应用 OOM 崩溃;服务端若内存不足,还会生成磁盘临时表,进一步加剧性能损耗。
- 一致性视图长期持有,阻塞业务写入 InnoDB 默认 RR 隔离级别,大查询会长期持有一致性视图,导致 undo log 无法回收、回滚段膨胀,同时阻塞其他事务的写入操作,引发锁等待、接口超时。
- 网络带宽被占满,服务整体不可用 千万级数据持续通过网络传输至客户端,网卡流量打满,不仅当前查询卡死,还会影响同服务器其他业务的正常通信。
- 主从延迟飙升,数据同步异常 全表扫描占用大量数据库资源,导致主库 binlog 同步延迟,从库数据滞后,严重时破坏数据一致性。
简言之,生产环境直接 SELECT * 查 1000 万数据,等同于数据库 “雪崩”,会直接拖垮整个服务。
6、JDBC 开启流式读取,避免千万级数据客户端 OOM
MySQL JDBC 默认全量拉取数据,想要实现服务端分批推送、客户端逐条处理的真正流式读取,需同时满足 4 个核心配置,缺一不可:
- JDBC URL 添加关键参数 在连接串中开启服务端游标并设置默认拉取条数:
jdbc:mysql://ip:port/db?useCursorFetch=true&defaultFetchSize=1000useCursorFetch=true:开启 MySQL 服务端游标,支持分批读取defaultFetchSize=1000:设置单次拉取数据量(可根据业务调整)
- 设置 Statement 为只读、向前只进模式
// 连接设为只读 conn.setReadOnly(true); PreparedStatement pstmt = conn.prepareStatement( sql, ResultSet.TYPE_FORWARD_ONLY, // 结果集只向前遍历 ResultSet.CONCUR_READ_ONLY // 只读模式 ); // 单次拉取1000条 pstmt.setFetchSize(1000); - 逐条处理结果集,不批量装载内存 避免将所有数据存入 List 等集合,直接边读边处理:
java
运行
ResultSet rs = pstmt.executeQuery(); while (rs.next()) { // 逐条解析、清洗、写入文件/目标库,不缓存全量数据 } - 关键注意事项
- 仅支持 InnoDB 引擎,查询期间连接会被长期占用,建议使用独立连接而非连接池;
- 不建议使用旧版
setFetchSize(Integer.MIN_VALUE)一次性流式方案,易引发连接阻塞。
通过该方式,客户端内存仅维持单次拉取的千级数据量,即便读取 1000 万条数据也不会 OOM。
7、深度分页 LIMIT 5000000,10 为何远慢于主键分批查询?
1. 深度分页的执行缺陷
sql
-- 深度分页
SELECT * FROM table LIMIT 5000000,10;
MySQL 执行该语句时,必须先扫描并丢弃前 500 万条数据,逐行计数直到定位到第 5000001 条,再返回后续 10 条。这意味着大量 IO、CPU 资源浪费在无效扫描上,偏移量越大,耗时呈指数级增长。
2. 主键分批的执行优势
sql
-- 主键分批
SELECT * FROM table WHERE id > 5000000 LIMIT 10;
主键 id 是 InnoDB 聚簇索引,MySQL 可直接通过索引定位到 id > 5000000 的位置,仅读取 10 条数据即结束查询,耗时为毫秒级。
3. 核心差距总结
- 深度分页:从头遍历 + 丢弃海量数据,越往后越慢;
- 主键分批:索引直接定位,无无效扫描,性能相差几十倍甚至上百倍。
8、1000 万数据导出、清洗的生产安全方案
针对千万级数据的导出与清洗,核心原则是分批读取、控制压力、支持断点续跑,避免全表扫描拖垮数据库,推荐 4 种标准方案:
方案 1:主键分段分批(最通用、最稳妥)
- 先查询表最大 ID:
SELECT MAX(id) FROM table; - 从
lastId=0开始循环查询,每次通过主键筛选分批:SELECT * FROM table WHERE id > ? LIMIT 1000; - 每处理完一批,更新
lastId为当前批次最大 ID,直至遍历完所有数据。 优势:无深度分页、对数据库压力小、可暂停 / 断点续跑。
方案 2:mysqldump 一致性导出
适合整表无锁导出,关键参数保障流式读取:
mysqldump -uroot -p 库名 表名 --where="id>0" --quick --single-transaction > data.sql
--quick:逐行读取数据,不加载至内存;--single-transaction:基于 InnoDB 一致性快照,不加锁,不影响业务。
方案 3:专业工具 mydumper
比 mysqldump 更快,支持并行导出,适合超大数据量,可控制并发与限速,降低对数据库的影响。
方案 4:企业级 ETL 工具
使用 DataX、FlinkX、Kettle 等工具,内置主键分段、流式读取、限速重试等能力,无需手动开发,适合大规模数据同步与清洗。
严禁操作
- 禁止直接
SELECT *全表拉取; - 禁止使用深度分页
LIMIT 1000000,10处理大数据; - 禁止将千万级数据一次性装载至内存集合;
- 禁止在业务高峰期执行全表大数据操作。
更多推荐



所有评论(0)