`

【原创】iBatis分页查询的性能问题分析

阅读更多

      关于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()方法取出一页的数据。
ResultSet的类型,是在iBatis的配置文件中配置的,如:


     <select id="queryAllUser" resultMap="user" resultSetType="FORWARD_ONLY">
            select id,name from user_tab
    </select>

 

      其中resultSetType的可选值为FORWARD_ONLY | SCROLL_INSENSITIVE | SCROLL_SENSITIVE,如果没有配置,默认值为FORWARD_ONLY,FORWARD_ONLY类型的ResultSet 不支持absolute方法,所以是通过next方法定位的。一般情况下,我们都使用FORWARD_ONLY类型的ResultSet,SCROLL类型ResultSet的优点是可向前,向后滚动,并支持精确定位(absolute),但缺点是把结果集全部加载进缓存(如果查询是从1000000条开始取100条,会把前100万条数据也加载进缓存),容易造成内存溢出,性能也很差,除非必要,一般不使用。


     可见,iBatis的分页完全依赖于JDBC ResultSet的next方法或absolute方法来实现,而Hibernate在分页查询方面,比iBatis要好很多,Hibernate可以根据不同的数据库,对sql做不同的优化加工,然后再执行优化后的sql。比如,对于Oracle数据库来说,原始sql为select * form user_tab, 从1000001条开始取100条,则hibernate加工后的sql为:


  select *
  from (select row_.*, rownum rownum_
          from (SELECT * FROM user_tab) row_
         where rownum <= 1000100)
 where rownum_ > 1000000

 

写一个程序,对比一下两种方式下的查询效率。程序如下:

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者的差别是非常小的。


     综上所述,如果系统中查询的数据量很大,并且用户会选择查询非常靠后的数据,那么我们就应该替换iBatis的分页实现,如果不存在这种情况,那我们就不需要替换iBatis的分页实现,一般情况下,用户不可能去查询那么靠后的页,这也是iBatis一直不修改分页实现的原因吧。


      如果我们选择替换的话,有三种办法,一种是自己写一个类,继承iBatis的SqlExecutor,然后把这个类注入到com.ibatis.sqlmap.engine.impl.ExtendedSqlMapClient中,由于SqlExecutor是ExtendedSqlMapClient的私有变量,没有public类型的set方法,所以需要采用reflect机制注入;第二种方法是在自己的工程里写一个和iBatis的SqlExecutor的包名和类名完全一样的类,web工程中,WEB-INF/classes下的java类,先于WEB-INF/lib下jar包的加载,所以就巧妙了覆盖了iBatis的SqlExecutor类;第三种办法是弃用iBatis的分页查询方法queryForList(String sql,Object obj,int maxResult,int  skipResult),而用普通查询方法,queryForList(String sql,Object obj)。只不过把maxResult和skipResult都作为obj的变量传到sql里去。如下:

<select id="queryAllUser" resultMap="user">
select *
  from (select row_.*, rownum rownum_
          from (SELECT * FROM user_tab) row_
         where rownum <= #_maxResult#)
where rownum_ > #_skipResult#
    </select>

 

 

分享到:
评论
15 楼 C_J 2010-05-27  
回楼上

貌似是parameterClass - -!!
14 楼 lcllcl987 2010-05-26  
第三种办法是弃用iBatis的分页查询方法queryForList(String sql,Object obj,int maxResult,int  skipResult),而用普通查询方法,queryForList(String sql,Object obj)。只不过把maxResult和skipResult都作为obj的变量传到sql里去。如下:
<select id="queryAllUser" resultMap="user">
select *
  from (select row_.*, rownum rownum_
          from (SELECT * FROM user_tab) row_
         where rownum <= #_maxResult#)
where rownum_ > #_skipResult#
    </select>
-----------------------------------------
这才是正解。
鄙人一般都这样分页。
这样分页估计和hibenate的性能没什么区别,并且还可以手动优化。
13 楼 C_J 2010-05-17  
everher 写道
ibatis在生产过程中使用一般是配合代码自动生成工具使用的,如果是手写代码,其ibatis的配置工作相当繁重,而且ibatis在数据库的兼容性方便做的不是很好,所以用ibatis做分页时,为了达到其性能优势,最好采用针对不同数据库的物理分页。



能讲述下你关于“配置工作相当繁重”的理解么?
12 楼 everher 2010-05-17  
ibatis在生产过程中使用一般是配合代码自动生成工具使用的,如果是手写代码,其ibatis的配置工作相当繁重,而且ibatis在数据库的兼容性方便做的不是很好,所以用ibatis做分页时,为了达到其性能优势,最好采用针对不同数据库的物理分页。
11 楼 C_J 2010-04-28  
汗!

**ibatis原来是机械的next().之前还真没想过这样的问题,hibernate根据数据源做sql级别的优化看来是优越的,也许因为ibatis那些设计者想着能很灵活的支持sql,所以就懒得写了,呵呵。不过这种“偷懒”降低了移植性。

**至于ispring提出的安全控制,我怎么感觉说得跟这个是两码事了?
10 楼 zhenkm0507 2010-01-14  
chenlixun 写道
首先肯定一下楼主的钻研精神,哈哈。

推荐动态生成SQL(应用基础组件)、数据库端分页。它们是性能的保障!
动态生成SQL的通用性好;数据库端分页的通用性差。


没看懂什么意思,呵呵。到底推荐哪一个?
9 楼 chenlixun 2010-01-14  
首先肯定一下楼主的钻研精神,哈哈。

推荐动态生成SQL(应用基础组件)、数据库端分页。它们是性能的保障!
动态生成SQL的通用性好;数据库端分页的通用性差。
8 楼 taochenpfj 2010-01-14  
需要哪些数据就查哪些才是效率提高的最重要的吧!
7 楼 teleizeget 2010-01-14  
采用sql级别的分页查询比iBatis的分页性能好的多
6 楼 xiaozhen57520 2010-01-13  
ispring 写道
大部分应用都会有安全控制系统,因此在获取到数据后一般会对数据进行一系列的安全过滤,去从Collection中删除用户没有权限查看有数据,这个时候的以上的分页方式便不再适用,因为数据总量,一页的数据都有可能会被过滤,就会比较乱。
所以通常会分两次查询,第一次只查询出所有符合条件的主键(通常为ID,会有索引,因此性能不会很差),这个时候的主键都已经被安全系统好,都是用户可以访问的记录。然后再对这些主键进行分页,之后将分页后的ID收集起来,再去执行“真正”的数据查询,取出符合条件的记录。
不知道大家对这种分页方式有没有什么改进的地方,欢迎大家讨论一下!

我也正在做这方面的东西, 想法和这差不多, 不知道有没更好的方法控制数据。
5 楼 抛出异常的爱 2010-01-13  
zhenkm0507 写道
ispring 写道
大部分应用都会有安全控制系统,因此在获取到数据后一般会对数据进行一系列的安全过滤,去从Collection中删除用户没有权限查看有数据,这个时候的以上的分页方式便不再适用,因为数据总量,一页的数据都有可能会被过滤,就会比较乱。
所以通常会分两次查询,第一次只查询出所有符合条件的主键(通常为ID,会有索引,因此性能不会很差),这个时候的主键都已经被安全系统好,都是用户可以访问的记录。然后再对这些主键进行分页,之后将分页后的ID收集起来,再去执行“真正”的数据查询,取出符合条件的记录。
不知道大家对这种分页方式有没有什么改进的地方,欢迎大家讨论一下!




我觉得应该尽量避免分两次查询。原来用ACEGI的时候,也考虑过分页查询的时候先把数据查出来,然后再过滤,但感觉这种做法不好,还是应该想办法把权限的控制加入到sql的where条件里去。

通用性又不好保证了.
由于权限是个树...
如果用sql的话写起sql来很要命...
4 楼 dyllove98 2010-01-13  
<div class="quote_title">nathanlee 写道</div>
<div class="quote_div">
<p><span style="font-weight: bold;">iBATIS 2.x</span></p>
<p>&lt;select id="queryAllUser" resultMap="user"&gt; <br>select *<br>  from (select row_.*, rownum rownum_<br>          from (SELECT * FROM user_tab) row_<br>         where rownum &lt;= #_maxResult#)<br>where rownum_ &gt; #_skipResult#<br>    &lt;/select&gt;<br><br><strong>iBATIS 3.0</strong><br><a href="http://melin.iteye.com/blog/491713">iBatis3 Dialect 分页</a></p>
<p> </p>
</div>
<p><br>oracle分页的时候就用这个 mysql用limit</p>
3 楼 zhenkm0507 2010-01-13  
ispring 写道
大部分应用都会有安全控制系统,因此在获取到数据后一般会对数据进行一系列的安全过滤,去从Collection中删除用户没有权限查看有数据,这个时候的以上的分页方式便不再适用,因为数据总量,一页的数据都有可能会被过滤,就会比较乱。
所以通常会分两次查询,第一次只查询出所有符合条件的主键(通常为ID,会有索引,因此性能不会很差),这个时候的主键都已经被安全系统好,都是用户可以访问的记录。然后再对这些主键进行分页,之后将分页后的ID收集起来,再去执行“真正”的数据查询,取出符合条件的记录。
不知道大家对这种分页方式有没有什么改进的地方,欢迎大家讨论一下!




我觉得应该尽量避免分两次查询。原来用ACEGI的时候,也考虑过分页查询的时候先把数据查出来,然后再过滤,但感觉这种做法不好,还是应该想办法把权限的控制加入到sql的where条件里去。
2 楼 ispring 2010-01-12  
大部分应用都会有安全控制系统,因此在获取到数据后一般会对数据进行一系列的安全过滤,去从Collection中删除用户没有权限查看有数据,这个时候的以上的分页方式便不再适用,因为数据总量,一页的数据都有可能会被过滤,就会比较乱。
所以通常会分两次查询,第一次只查询出所有符合条件的主键(通常为ID,会有索引,因此性能不会很差),这个时候的主键都已经被安全系统好,都是用户可以访问的记录。然后再对这些主键进行分页,之后将分页后的ID收集起来,再去执行“真正”的数据查询,取出符合条件的记录。
不知道大家对这种分页方式有没有什么改进的地方,欢迎大家讨论一下!
1 楼 nathanlee 2010-01-12  
<p><span style="font-weight: bold;">iBATIS 2.x</span></p>
<p>&lt;select id="queryAllUser" resultMap="user"&gt; <br>select *<br>  from (select row_.*, rownum rownum_<br>          from (SELECT * FROM user_tab) row_<br>         where rownum &lt;= #_maxResult#)<br> where rownum_ &gt; #_skipResult#<br>    &lt;/select&gt;<br><br><strong>iBATIS 3.0</strong><br><a href="http://melin.iteye.com/blog/491713">iBatis3 Dialect 分页</a></p>
<p> </p>

相关推荐

    ibatis 之分页

    5. **性能优化**:在处理大数据分页时,需要关注性能问题。例如,避免使用`OFFSET`较大的分页方式,因为这会导致数据库性能下降。可以结合主键排序和范围查询优化。 6. **源码分析**:理解Ibatis的源码有助于我们更...

    ibatis分页

    标题中的“ibatis分页”指的是在使用iBATIS(一个SQL映射框架)时,如何实现数据库查询结果的分页显示。iBATIS通过XML配置文件或注解方式将Java代码与SQL语句分离,提供了更灵活的数据库操作方式。在处理大量数据时...

    ibatis分页功能

    标题"ibatis分页功能"指的就是如何在iBATIS框架中实现数据库查询的分页效果。分页不仅提高了用户体验,还能减少不必要的数据库负载。 描述中提到,分页功能是通过`page.tld`标签实现的。`tld`文件是JSP Tag Library...

    基于ibatis的分页

    总结来说,这个基于ibatis的分页项目提供了一套完整的解决方案,从数据库查询到前端展示,都已准备就绪。开发者只需理解并调用已有的组件,就能快速实现分页功能,无需从头编写大量代码。对于初学者或者希望提高开发...

    ibatis实现分页技术

    Ibatis实现分页技术主要依赖于SQL语句中的分页关键字和动态参数,通过在Mapper接口、Controller层和服务层合理封装,可以实现灵活的分页功能。如果配合使用分页插件,如PageHelper,将使分页操作更加便捷。在实际...

    ibatis_likehbm高效分页组件

    ibatis_likehbm高效分页组件ibatis_likehbm高效分页组件ibatis_likehbm高效分页组件ibatis_likehbm高效分页组件ibatis_likehbm高效分页组件ibatis_likehbm高效分页组件 ibatis_likehbm高效分页组件 ibatis_likehbm...

    Ibatis.net 分页

    Ibatis.NET提供了分页查询的实现,下面我们将深入探讨如何在Ibatis.NET中实现分页。 首先,理解分页的基本概念。分页通常涉及两个关键参数:当前页码(Page Number)和每页记录数(PageSize)。例如,如果当前页码...

    iBatis分页(基于Struts2和Freemarker)

    在IT行业中,数据库查询的效率和用户体验息息相关,尤其是在数据量庞大的场景下,分页功能显得尤为重要。本知识点将深入探讨如何在Struts2框架中结合iBatis实现基于Freemarker模板的分页功能。 首先,我们需要理解...

    对IBatis分页的改进,使ibatis支持hibernate式的物理分页

    公司的大部分项目都开始使用IBatis作为O/R Mapping了,但是在使用的过程中也发现了很多不方便和存在...想了很多办法,都没能很好的避免这个问题,无意间在javaeye上看到了《使ibatis支持hibernate式的物理分页》这篇文章,

    spring+ibatis+oracle分页缓存源码

    在IT行业中,数据库分页和缓存是两个关键的概念,特别是在构建高性能的Web应用程序时。Spring、iBatis和Oracle的结合提供了强大的数据处理能力。本文将深入探讨这些技术如何协同工作,实现高效的分页缓存策略。 ...

    Ibatis 2.3.4 数据库无关分页

    在2.3.4这个版本中,Ibatis 提供了数据库无关的分页功能,这是一种在不依赖特定数据库语法的情况下实现分页查询的方法,有助于提高代码的可移植性和维护性。 数据库无关分页的核心思想是将分页参数(如当前页数和每...

    ibatis物理分页jar

    ibatis 物理分页jar ,与官方ibatis不冲突,可直接使用。

    Xwork+iBatis分页

    在Xwork+iBatis的环境下,我们可以利用iBatis的动态SQL功能轻松实现分页查询。 1. **配置iBatis** 在iBatis的配置文件(如:mybatis-config.xml)中,我们需要为数据库连接、事务管理等进行基本配置。同时,为每个...

    例1:struts2+spring+ibatis 实现分页

    你可以定义一个Mapper接口和XML配置文件,编写SQL查询来获取指定页码的数据,并通过iBatis的参数映射功能传入分页参数。 文件列表中的`.classpath`和`.project`是Eclipse或类似的IDE的项目配置文件,它们定义了项目...

    对IBatis分页的改进,使ibatis支持hibernate式的物理分页.doc

    总之,通过以上方法,我们可以实现在不修改iBatis源码的前提下,让iBatis支持类似Hibernate的物理分页,提高查询效率,尤其在大数据量的场景下,这种优化尤为关键。不过,需要注意的是,不同的数据库系统对物理分页...

    struts2+spring+ibatis+oracle+分页搜索+上传附件实例

    在这个实例中,iBatis作为数据访问层,负责与Oracle数据库交互,执行分页查询和插入、更新、删除操作。它通过XML或注解方式定义SQL语句,使得数据库操作更加直观和灵活。 4. **Oracle**:Oracle数据库是企业级的...

    spring+ibatis+jsp集成实现数据库查询分页

    通过这样的集成,我们可以在不牺牲性能和灵活性的前提下,实现优雅的数据库查询和分页功能。Spring提供了强大的依赖注入和管理机制,iBatis简化了SQL操作,而JSP则作为用户界面的呈现工具。这三者结合,为开发高质量...

    Ibatis SQLServerDialect 2008 分页

    Ibatis SQLServerDialect 2008 分页 可实现SQLServerDialect 分页 支持ibatis3

    iBatis分页源代码解析.chm

    iBatis分页源代码解析.chm,ibatis介绍等

    ibatis分页技术

    在Ibatis中,除了分页技术,还常遇到N+1查询问题和DTO(Data Transfer Object)设计。为了解决N+1问题,即由于懒加载机制导致的多次数据库访问,通常会关闭load-lazy属性,并采用以下策略: - **多结果集映射**:...

Global site tag (gtag.js) - Google Analytics