MySQL 中如果我 select * from 一个有 1000 万行的表,内存会飙升么?
- 标签:后端、场景题
🏛 业务架构师
结论:通常不会飙升,但会以另一种方式出事。
机制上:MySQL 返回结果集是流式/分批传输,不是一次把千万行全读进内存——服务端按 net_buffer 分批发给客户端,客户端驱动也有 FetchSize 控制(默认行为因驱动而异,MySQL JDBC 默认会把整个结果集读入客户端内存——这一点是关键差异)。
所以分两种情况:
- 客户端 JDBC 默认全量拉取:应用侧内存会飙升——千万行 × 每行几百字节,轻松几个 GB,年轻代疯狂 GC 甚至 OOM。这是"select * 大表把服务拖挂"的常见形态;
- 开启流式(
statement.setFetchSize(Integer.MIN_VALUE)或 useCursorFetch)或 MyBatis 游标:内存平稳,但长连接长时间占用——另一个坑:事务/连接被占几分钟,连接池耗尽殃及全站。
业务架构的处置方案(这类需求的正确打开方式):
- 先质疑需求:谁要千万行全量数据?多半是导出/跑批——在线接口禁止大表全扫,需求转离线通道;
- 导出走异步任务:提交任务 → 后台分页游标(
where id > ?每批 1 万)流式写文件/对象存储 → 完成后通知下载链接——用户拿结果的时间从"接口超时"变"几分钟异步",体验与资源双达标; - 跑批走从库/专用实例:分析流量与交易流量物理隔离,别让数据同事的查询和大促下单抢同一个库;
- 超大规模转 ClickHouse/Spark 这类 OLAP 引擎。
一句话:这题考的是对"数据在哪里、以什么速度流动"的敏感度——大结果集的可怕不在内存一个维度,在于它同时踩踏网络、连接、GC 三个资源。
🏗 系统架构师
要分"哪里内存飙升",不能一句话回答:
MySQL 服务端:不会把 1000 万行全部载入内存。InnoDB 是按页(16KB)读取,全表扫描时逐页读入 Buffer Pool 并按 LRU 淘汰——老页不断被挤出,内存占用稳定在 buffer_pool_size 上限,不会无限飙升;但代价是:扫描期间 Buffer Pool 被脏数据冲刷,缓存命中率骤降(其他业务查询被迫走磁盘,整个库的性能被拖累),以及 undo 版本链、IO 打满的连带影响。这才是无 where 全表查询的真危害——不是 OOM,而是"缓存污染 + IO 风暴"。
应用端(JDBC):这才是真正会内存飙升的地方。MySQL 默认一次把结果集全部拉到客户端,select * 1000 万行 × 每行 1KB = 10GB,JVM 直接 OOM。解法:
statement.setFetchSize(1000)+ MySQL 驱动需useCursorFetch=true(或 Integer.MIN_VALUE 的流式模式)分批拉取;- 分页游标批处理:
where id > ? limit 1000循环; - MyBatis 用 ResultHandler / Cursor 流式处理,而不是 List 全收。
架构结论:线上禁用无界 select *;批量导出走流式 + 异步任务;从库/数仓隔离这类重查询。这题考的是对"存储引擎分页机制 + 驱动物化行为"两层内存模型的理解。
💻 全栈开发专家
分两头:MySQL 服务端不会飙,你自己的应用服务器会。
MySQL 侧:SELECT * FROM t(无 where)是全表扫描,Server 层逐行从存储引擎取,边扫边通过网络发给客户端,不会把 1000 万行一次性载入自身内存(buffer pool 只是页缓存的复用),所以 MySQL 内存不会因此"飙升",但会长时间占用:CPU/IO、一个连接、binlog 无写入、undo 无压力——主要影响是扫描耗时(几分钟)和锁表风险(RR 下对全表加读锁视图)。
客户端侧才是真正的 OOM 点:JDBC 默认把结果集全部读进内存——1000 万行 × 1KB ≈ 10GB,应用直接 OutOfMemoryError。这是我见过最典型的"导个数据把服务导挂"。
正确做法:
java
// MySQL 流式读取:fetchSize = Integer.MIN_VALUE(MySQL 驱动特殊约定)
Statement st = conn.createStatement(
ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
st.setFetchSize(Integer.MIN_VALUE);
ResultSet rs = st.executeQuery("SELECT * FROM big_table");
while (rs.next()) { writeBatch(rs); } // 逐行/攒批写出,内存恒定流式的约束(踩过才知道):连接在结果集读完前不能执行其他语句;要用独立连接;攒 500~1000 行批量落盘/下发,吞吐和内存才平衡。
更工程化的方案:数据导出走 mysqldump --where / DataX / canal,别用应用服务硬扛。