MySQL 中 如果我 select * from 一个有 1000 万行的表,内存会飙升么?
MySQL 中 如果我 select * from 一个有 1000 万行的表,内存会飙升么?
回答重点
内存不会飙升。
因为 MySQL 在执行简单 SELECT * FROM 查询时,不会一次性将所有 1000 万行数据加载到内存中,而是通过逐批次处理的方式来控制内存使用。也就是说 MySQL 是边查边发送数据给客户端。
分批的大小与 net_buffer_length 有关,默认 16384 字节(16 KB)。
所以实际上获取数据和发送数据的流程是这样的:
- 获取一行,写入到
net_buffer中。 - 重复获取行,直到
net_buffer写满,调用网络接口发出去。 - 发送成功后 清空
net_buffer。 - 再继续取下一行,并写入
net_buffer,继续上述的操作。
这里还需要注意一点,发送的数据是需要被客户端读取的,如果客户端读取的慢,导致本地网络栈(socket send buffer)写满了,那么当前数据的写入会被暂停。
综上,**SELECT * FROM 一个有 1000 万行的表,不会导致内存飙升**。
扩展知识
注意 ORDER BY 和 GROUP BY
正常不需要排序和分组的查询不会占用过多的数据和影响 MySQL Server 的执行,但是如果涉及到分组和排序,那么就需要使用额外的内存(或外部空间)来处理全量数据,可能会占用额外的内存并影响查询的效率。
客户端的处理方式
虽然 MySQL 会分批返回数据,但是客户端需要做一定的处理,不能全量保存数据,否则可能会导致内存溢出。
客户端需要使用流式处理或者游标来查询。
- 在编写查询代码时,使用流式读取方式(如 JDBC 中的 ResultSet.FETCH_SIZE),这会让数据库逐步返回数据,客户端按需处理。
JDBC 简单示例,重点就是 statement.setFetchSize(Integer.MIN_VALUE);
1 | // 获取数据库连接并开启流式处理 |
MyBatis 简单示例,重点就是 session.getConfiguration().setDefaultFetchSize(Integer.MIN_VALUE);
1 | try (SqlSession session = sqlSessionFactory.openSession()) { |
MyBatis 游标查询示例,重点就是使用 Cursor 接口
1 | try (SqlSession session = sqlSessionFactory.openSession()) { |
一次性查询大量数据对 MySQL BufferPool 的影响
BufferPool 实现了一个冷热分区的 LRU
Comments