锁定老帖子 主题:【原创】iBatis分页查询的性能问题分析
精华帖 (0) :: 良好帖 (7) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2010-01-09
最后修改:2010-01-26
关于iBatis的分页性能问题,网上的讨论也很多,经过验证,我的结论是:只有在表的数据量很大,并且是从很后面的一个位置取一页数据的时候(比如从1000000条开始取100条),性能问题才比较明显。分析如下。
首先看一下iBatis的分页代码。iBatis中,具体负责执行sql的类是com.ibatis.sqlmap.engine.execution.SqlExecutor。负责分页查询的方法是executeQuery —>handleMultipleResults —> handleResults。handleResults方法的源码如下:
private void handleResults(RequestScope request, ResultSet rs, int skipResults, int maxResults, RowHandlerCallback callback) throws SQLException { try { request.setResultSet(rs); ResultMap resultMap = request.getResultMap(); if (resultMap != null) { // Skip Results if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) { if (skipResults > 0) { rs.absolute(skipResults); } } else { for (int i = 0; i < skipResults; i++) { if (!rs.next()) { return; } } } // Get Results int resultsFetched = 0; while ((maxResults == SqlExecutor.NO_MAXIMUM_RESULTS || resultsFetched < maxResults) && rs.next()) { Object[] columnValues = resultMap.resolveSubMap(request, rs).getResults(request, rs); callback.handleResultObject(request, columnValues, rs); resultsFetched++; } } } finally { request.setResultSet(null); } } 从代码中可以看出iBatis分页查询的逻辑是首先判断ResulteSet的类型,如果ResultSet的类型是ResultSet.TYPE_FORWARD_ONLY,则使用ResultSet对象的next()方法,一步一步地移动游标到要取的第一条记录的位置,然后再采用next()方法取出一页的数据;如果ResultSet的类型不是ResultSet.TYPE_FORWARD_ONLY,则采用ResultSet对象的absolute()方法,移动游标到要取的第一条记录的位置,然后再采用next()方法取出一页的数据。
其中resultSetType的可选值为FORWARD_ONLY | SCROLL_INSENSITIVE | SCROLL_SENSITIVE,如果没有配置,默认值为FORWARD_ONLY,FORWARD_ONLY类型的ResultSet 不支持absolute方法,所以是通过next方法定位的。一般情况下,我们都使用FORWARD_ONLY类型的ResultSet,SCROLL类型ResultSet的优点是可向前,向后滚动,并支持精确定位(absolute),但缺点是把结果集全部加载进缓存(如果查询是从1000000条开始取100条,会把前100万条数据也加载进缓存),容易造成内存溢出,性能也很差,除非必要,一般不使用。
写一个程序,对比一下两种方式下的查询效率。程序如下: public class Test{ public static void main(String[] args) throws Exception { Class.forName("oracle.jdbc.driver.OracleDriver"); Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:db", "db", "xwdb"); long a = System.currentTimeMillis(); testPageQuery1(conn); //testPageQuery2(conn); long b = System.currentTimeMillis(); System.out.println(b-a); } public static void testPageQuery1(Connection conn) throws Exception{ String sql = "SELECT * FROM user_tab "; Statement stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); ResultSet rs = stmt.executeQuery(sql); int j=0; //游标移动到1000001条数据的位置 while(rs.next() && j++<1000000){ } int i=0; //依次取出100条数据 while(rs.next() && i++<100){ } } public static void testPageQuery2(Connection conn) throws Exception{ String sql = "SELECT * FROM user_tab "; StringBuffer pagingSelect = new StringBuffer( sql.length()+100 ); pagingSelect.append("select * from ( select row_.*, rownum rownum_ from ( "); pagingSelect.append(sql); pagingSelect.append(" ) row_ where rownum <= 1000100) where rownum_ > 1000000"); Statement stmt = conn .createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); ResultSet rs = stmt.executeQuery(pagingSelect.toString()); while(rs.next()){ } } } testPageQuery1方法是用ibatis采用的分页方法查询,testPageQuery2是用Hibernate采用的分页方法查询,发现testPageQuery1需要执行十几秒,而testPageQuery2仅需要执行零点几秒,差异很大。而如果改成从1000条开始取100条,甚至更靠前,则2者的差别是非常小的。
<select id="queryAllUser" resultMap="user">
声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2010-01-12
iBATIS 2.x <select id="queryAllUser" resultMap="user">
|
|
返回顶楼 | |
发表时间:2010-01-12
大部分应用都会有安全控制系统,因此在获取到数据后一般会对数据进行一系列的安全过滤,去从Collection中删除用户没有权限查看有数据,这个时候的以上的分页方式便不再适用,因为数据总量,一页的数据都有可能会被过滤,就会比较乱。
所以通常会分两次查询,第一次只查询出所有符合条件的主键(通常为ID,会有索引,因此性能不会很差),这个时候的主键都已经被安全系统好,都是用户可以访问的记录。然后再对这些主键进行分页,之后将分页后的ID收集起来,再去执行“真正”的数据查询,取出符合条件的记录。 不知道大家对这种分页方式有没有什么改进的地方,欢迎大家讨论一下! |
|
返回顶楼 | |
发表时间:2010-01-13
ispring 写道 大部分应用都会有安全控制系统,因此在获取到数据后一般会对数据进行一系列的安全过滤,去从Collection中删除用户没有权限查看有数据,这个时候的以上的分页方式便不再适用,因为数据总量,一页的数据都有可能会被过滤,就会比较乱。
所以通常会分两次查询,第一次只查询出所有符合条件的主键(通常为ID,会有索引,因此性能不会很差),这个时候的主键都已经被安全系统好,都是用户可以访问的记录。然后再对这些主键进行分页,之后将分页后的ID收集起来,再去执行“真正”的数据查询,取出符合条件的记录。 不知道大家对这种分页方式有没有什么改进的地方,欢迎大家讨论一下! 我觉得应该尽量避免分两次查询。原来用ACEGI的时候,也考虑过分页查询的时候先把数据查出来,然后再过滤,但感觉这种做法不好,还是应该想办法把权限的控制加入到sql的where条件里去。 |
|
返回顶楼 | |
发表时间:2010-01-13
nathanlee 写道
iBATIS 2.x <select id="queryAllUser" resultMap="user">
|
|
返回顶楼 | |
发表时间:2010-01-13
zhenkm0507 写道 ispring 写道 大部分应用都会有安全控制系统,因此在获取到数据后一般会对数据进行一系列的安全过滤,去从Collection中删除用户没有权限查看有数据,这个时候的以上的分页方式便不再适用,因为数据总量,一页的数据都有可能会被过滤,就会比较乱。
所以通常会分两次查询,第一次只查询出所有符合条件的主键(通常为ID,会有索引,因此性能不会很差),这个时候的主键都已经被安全系统好,都是用户可以访问的记录。然后再对这些主键进行分页,之后将分页后的ID收集起来,再去执行“真正”的数据查询,取出符合条件的记录。 不知道大家对这种分页方式有没有什么改进的地方,欢迎大家讨论一下! 我觉得应该尽量避免分两次查询。原来用ACEGI的时候,也考虑过分页查询的时候先把数据查出来,然后再过滤,但感觉这种做法不好,还是应该想办法把权限的控制加入到sql的where条件里去。 通用性又不好保证了. 由于权限是个树... 如果用sql的话写起sql来很要命... |
|
返回顶楼 | |
发表时间:2010-01-13
ispring 写道 大部分应用都会有安全控制系统,因此在获取到数据后一般会对数据进行一系列的安全过滤,去从Collection中删除用户没有权限查看有数据,这个时候的以上的分页方式便不再适用,因为数据总量,一页的数据都有可能会被过滤,就会比较乱。
所以通常会分两次查询,第一次只查询出所有符合条件的主键(通常为ID,会有索引,因此性能不会很差),这个时候的主键都已经被安全系统好,都是用户可以访问的记录。然后再对这些主键进行分页,之后将分页后的ID收集起来,再去执行“真正”的数据查询,取出符合条件的记录。 不知道大家对这种分页方式有没有什么改进的地方,欢迎大家讨论一下! 我也正在做这方面的东西, 想法和这差不多, 不知道有没更好的方法控制数据。 |
|
返回顶楼 | |
发表时间:2010-01-14
采用sql级别的分页查询比iBatis的分页性能好的多
|
|
返回顶楼 | |
发表时间:2010-01-14
需要哪些数据就查哪些才是效率提高的最重要的吧!
|
|
返回顶楼 | |
发表时间:2010-01-14
最后修改:2010-01-14
首先肯定一下楼主的钻研精神,哈哈。
推荐动态生成SQL(应用基础组件)、数据库端分页。它们是性能的保障! 动态生成SQL的通用性好;数据库端分页的通用性差。 |
|
返回顶楼 | |