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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| try (Connection connection = DriverManager.getConnection(URL, USER, PASSWORD)) { connection.setAutoCommit(false);
try (PreparedStatement statement = connection.prepareStatement(query, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) { statement.setFetchSize(Integer.MIN_VALUE);
try (ResultSet resultSet = statement.executeQuery()) { while (resultSet.next()) { int id = resultSet.getInt("id"); String name = resultSet.getString("name");
System.out.printf("ID: %d, Name: %s%n", id, name); } } } } catch (SQLException e) { e.printStackTrace(); }
|
MyBatis 简单示例,重点就是 session.getConfiguration().setDefaultFetchSize(Integer.MIN_VALUE);
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| try (SqlSession session = sqlSessionFactory.openSession()) { session.getConfiguration().setDefaultFetchSize(Integer.MIN_VALUE);
LargeTableMapper mapper = session.getMapper(LargeTableMapper.class);
mapper.selectAll(resultContext -> { LargeTableRecord record = resultContext.getResultObject(); System.out.printf("ID: %d, Name: %s%n", record.getId(), record.getName()); }); }
|
MyBatis 游标查询示例,重点就是使用 Cursor 接口
1 2 3 4 5 6 7 8 9 10 11 12
| try (SqlSession session = sqlSessionFactory.openSession()) { LargeTableMapper mapper = session.getMapper(LargeTableMapper.class);
try (Cursor<LargeTableRecord> cursor = mapper.selectAllWithCursor()) { for (LargeTableRecord record : cursor) { System.out.printf("ID: %d, Name: %s%n", record.getId(), record.getName()); } } }
|
一次性查询大量数据对 MySQL BufferPool 的影响
BufferPool 实现了一个冷热分区的 LRU