回答重点
内存不会飙升。
因为MSQL 在执行简单SELECT * FROM查询时,不会一次性将所有1000万行数据加载到内存中,而是通过逐批次处理的方式来控制内存使用。也就是说 MySQL 是边查边发送数据给客户端,
分批的大小与 net_buffer length 有关,默认16384字节(16 KB)。
所以实际上获取数据和发送数据的流程是这样的:
- 获取一行,写入到 net_buffer 中。
- 重复获取行,直到 net_buffer 写满,调用网络接口发出去。
- 发送成功后 清空 net_buffer 。
- 再继续取下一行,并写入 net_buffer,继续上述的操作。
这里还需要注意一点,发送的数据是需要被客户端读取的,如果客户端读取的慢,导致本地网络栈(socketsend bufer)写满了,那么当前数据的写入会被暂停。
综上,SELECT * FROM一个有1000 万行的表,不会导致内存飙升。
扩展知识
注意 ORDER BY 和 GROUP BY
正常不需要排序和分组的查询不会占用过多的内存和影响 MySQLServer 的执行,但是如果涉及到分组和排序,那么就需要使用额外的内存(或外部空间)来处理全量数据,可能会占用额外的内存并影响查询的效率。
客户端的处理方式
虽然 MVSQL会分批返回数据,但是客户端需要做一定的处理,不能全量保存教据,否则可能会导致内存溢出。
客户端需要使用流式处理或者游标来查询。
- 在编写查询代码时,使用流式读取方式(如JDBC中的 ResultSet.FETCH_SIZE),这会让数据库逐步返回数据,客户端按需处理。
// 获取数据库连接并开启流式处理
try (Connection connection = DriverManager.getConnection(URL, USER, PASSWORD)) {
// 禁用自动提交,避免结果集完全缓存到内存
connection.setAutoCommit(false);
// 创建一个可以进行流式处理的 Statement
try (PreparedStatement statement = connection.prepareStatement(query,
ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) {
// 设置流式处理的关键:MySQL 特定配置
statement.setFetchSize(Integer.MIN_VALUE);
// 执行查询
try (ResultSet resultSet = statement.executeQuery()) {
while (resultSet.next()) {
// 获取每行数据,这里假设有 "id" 和 "name" 两列
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);
try (SqlSession session = sqlSessionFactory.openSession()) {
// 设置 fetchSize 为 Integer.MIN_VALUE 启用流式查询
session.getConfiguration().setDefaultFetchSize(Integer.MIN_VALUE);
// 获取 Mapper
LargeTableMapper mapper = session.getMapper(LargeTableMapper.class);
// 使用 ResultHandler 处理数据,逐行读取
mapper.selectAll(resultContext -> {
LargeTableRecord record = resultContext.getResultObject();
System.out.printf("ID: %d, Name: %s%n", record.getId(), record.getName());
// 可以在这里对数据进行处理,避免一次性加载到内存
});
}
- MyBatis 游标查询示例,重点就是使用cursor接口
try (SqlSession session = sqlSessionFactory.openSession()) {
// 获取 Mapper
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());
// 可以在这里对数据进行处理,避免一次性加载到内存
}
}
}