private void testSQLBug(){ List<SQLBugData> sqlBugDatas = new ArrayList<>(); int pageSize = 1000; int pageNumber = 0; while (true){ List<SQLBugData> data = sqlBugDataMapper.queryData(pageSize, pageNumber); if (CollectionUtils.isEmpty(data)){ break; } //过滤掉不符合条件的数据。 filterData(data); sqlBugDatas.addAll(data); if (data.size() <= pageSize){ break; } pageNumber++; } }下面我们看一下 sql,这里用 Oracle 数据库,orm 框架用的 mybatis,sql 在 mapper.xml 文件中:
select * from sql_bug order by id offset #{pageNumber} rows fetch first #{pageSize} rows only仔细看上面的 sql,我们可以看到 Offset 这个参数传入了 pageNumber。那后果是什么呢?查询语句每次偏移量加 1,表里有 8 万条数据,相当于得查询 79000 次循环才能结束。sqlBugDatas 数据量一直累加,最终触发 OOM。
# 堆代码 duidaima.com select * from sql_bug order by id offset #{pageNumber}*#{pageSize} rows fetch first #{pageSize} rows only为什么测试没有测出来呢?测试环境数据量比较小,并没有出现 OOM 的情况。代码 review 为什么没有发现?由于交付的代码量很大,做代码 review 的同事主要关注点在业务逻辑的正确性上面,并没有精力能看到这么细节的问题。
select * from sql_bug order by id limit #{pageNumber}*#{pageSize},#{pageSize}但是使用 Offset 进行分页的写法并不推荐,因为有深度分页的性能问题,后面的页耗时会越来越多。下图是阿里开发手册关于分页场景的一个规范。
private void testSQLBug(){ List<SQLBugData> sqlBugDatas = new ArrayList<>(); String id = null; int pageSize = 0; while (true){ List<SQLBugData> data = sqlBugDataMapper.queryData(id, pageSize); if (CollectionUtils.isEmpty(data)){ break; } id = data.get(data.size()-1).getId(); //过滤掉不符合条件的数据。 filterData(data); sqlBugDatas.addAll(data); if (data.size() <= pageSize){ break; } } }配合上面代码,把 sql 也改写一下,这里使用 mysql 语法:
select * from sql_bug <if test="id != null"> where id <![CDATA[>]]> #{id} </if> order by id limit #{pageSize}也可以使用 rownum 来控制,下面再改写一下(Oracle 语法):
select * from( select * from sql_bug <if test="id != null"> where id <![CDATA[>]]> #{id} </if> order by id ) where rownum <![CDATA[<]]> #{pageSize} + 1我个人更推荐 rownum 写法,原因有 2 个:
2.国内好多公司做信创改造,国产数据库对这种语法支持更好一些。