`
myprincejava
  • 浏览: 29682 次
  • 性别: Icon_minigender_1
  • 来自: 来自太平洋
社区版块
存档分类
最新评论

发疯Hibernate分页问题,性能优化!

阅读更多

最近写了一个Hibernate分页,之前用游标来实现得到count 总行数..用HQL语句查询!可是现在问题出现了,但数据达到海量的时候,出现比较慢,现在要进行优化:

1.不能使用游标得到count总行数!

ScrollableResults rs = query.scroll(ScrollMode.SCROLL_INSENSITIVE);
  rs.last();
  return (rs.getRowNumber() + 1);

2.不能使用select count(*) from .....这样要得到count 和list 分布操作很麻烦!查询和count 都要同步!不想两次操作...

假如更改字段都要更改...不想这么做!要公用....

3.不能使用query.list().size()

 int count=query.list().size();

除了这三种方法实现得到count,不知道还能用什么方法去实现!相信javaeye上面大师们应该会出现这样情况吧!遇到过的提提建议...

分享到:
评论
36 楼 icewubin 2008-07-27  
lsy 写道
好多回复都跑题了,楼主的问题不是分页,而是如何查询一个海量数据表的总数,Lucas Lee已经给出了很好的答案。


那样的回答是不错,可以把那那种方案的海量级别进一步提高,但是各种“海量”都是有各自极限的,我的意思是思路可以拓展嘛。

再说了,那个方案只是测试了MySql啊,楼主没说什么数据库啊。我前面也提到,SqlServer2000的分页查询就很烂(不是计算总数烂),很多数据库行为不一样的。跑题有时候也没啥不好,只要别跑得太远。

尽管我说了这么多“废话”,Lucas Lee的回复的含金量还是非常非常高的。
35 楼 icewubin 2008-07-27  
tangbo530 写道
count(id) 会好一点点


有时候你的一句hql(sql)不包含id的。
34 楼 tangbo530 2008-07-26  
count(id) 会好一点点
33 楼 zhxp791008 2008-07-26  
1、如果你不用考虑查询条件得到总记录数,用一个触发器,在一个单独的表中记录总记录数。
2、如果总记录数是根据不同的条件得到值不一样,单独整个查询缓存搞定。
3、其它,可能唯有select count(*)。
32 楼 lsy 2008-07-26  
好多回复都跑题了,楼主的问题不是分页,而是如何查询一个海量数据表的总数,Lucas Lee已经给出了很好的答案。
31 楼 spiritfrog 2008-07-26  
icewubin 写道
nihongye 写道
myprincejava 写道
nihongye 写道
很简单,大数据量,不要计算总数

你问题没看明白?现在就是要分页..怎么可能不得到count...

一个只足够翻页的方法,获取多于显示记录数量一的记录集,以此判断是否存在下一页。
计算总数耗费数据库资源,匹配的结果数量一多,建立的索引都浪费了。



总数可以缓存啊,刚才说了5秒缓存性能足够好了。

如果真的是很严重的性能问题,需求就不对,我只是说一个通用的情况,很严重的性能问题往往是需求的问题,而不是技术问题。用户自己真的需要每次翻页都实时的知道当前记录的精确总数么?然后一页一页去翻(1000多页?),这个UI交互模式的需求分析就是有问题的。

开拓思路,例如我如果用LiveGrid(Ext的一个扩展实现,操作方式类似于GoogleReader),总数只会取一次,相当于缓存了总数,你当然可以说这个总数实时性不高,当然没有完美的方案,都是有点缺陷的,要看这些缺陷是否值得去做,当前例子就是“实时性”真的那么重要么?0秒延时和5秒延时区别真的那么大么?很多情况下,1小时延时都被人吹成实时呢。

很多体育比赛实况转播不仅技术上有几秒的延时(卫星传送距离太远需要时间),更多的是故意的5分钟延时,这还不是中国特色,国外也很多的。

记录多的话,确实可以考虑这样, 牺牲一点实时性而已, 对海量数据而言倒能接受。
30 楼 spiritfrog 2008-07-26  
nihongye 写道
myprincejava 写道
nihongye 写道
很简单,大数据量,不要计算总数

你问题没看明白?现在就是要分页..怎么可能不得到count...

一个只足够翻页的方法,获取多于显示记录数量一的记录集,以此判断是否存在下一页。
计算总数耗费数据库资源,匹配的结果数量一多,建立的索引都浪费了。

这样就等于只能上一页和下一页了, 总页数不知道。如果确实页面只需要有上一页和下一页,倒也是不错的方案,避免了求总记录数。

29 楼 zgxzowen 2008-07-26  
icewubin 写道
linkobe 写道
springside的那个分页总数在表关联时是有bug的,比如一个商品有两个价格,这两表关联起来是有两个数据,但返回的却是一个商品,这是实践中发现过的。



有bug么自己改一下呀,SpringSide的bug多了去了,或者说不实际的地方多了去了,但是不影响他的很高的参考价值。


同感,我在项目就用他的分页,但是自己改了好多东西,参考价值和使用价值都很高,有些时候自己想不到的东西,可以参考下开源项目的优秀代码,能给自己很大启发.
28 楼 icewubin 2008-07-26  
linkobe 写道
springside的那个分页总数在表关联时是有bug的,比如一个商品有两个价格,这两表关联起来是有两个数据,但返回的却是一个商品,这是实践中发现过的。



有bug么自己改一下呀,SpringSide的bug多了去了,或者说不实际的地方多了去了,但是不影响他的很高的参考价值。
27 楼 icewubin 2008-07-26  
helyho 写道
建议用存储过程分页吧
hibernate分页多少多会带来相对较大的性能损失的


例如Oracle的Hibernate方言实现就是非常高效的,充分利用Oracle的特点,不比存储过程慢多少,如果都自己写存储过程,如果有切换数据库的需求就麻烦了。

不过话说回来,SqlServer的方言的分页实现效率是有问题的,强烈建议大家不要使用SqlServer2000做复杂工程。
26 楼 icewubin 2008-07-26  
myprincejava 写道
请问icewubin
  你里面那个 HqlUtils是写一个方法还是?我导入包没有这个?谢谢能告知....


非常不好意思,那个是我自己改写的,拷贝错了,SpringSide2中原文如下:

/**
 * 分页查询函数,使用hql.
 *
 * @param pageNo 页号,从1开始.
 */
public Page pagedQuery(String hql, int pageNo, int pageSize, Object... values) {
	Assert.hasText(hql);
	Assert.isTrue(pageNo >= 1, "pageNo should start from 1");
	// Count查询
	String countQueryString = " select count (*) " + removeSelect(removeOrders(hql));
	List countlist = getHibernateTemplate().find(countQueryString, values);
	long totalCount = (Long) countlist.get(0);

	if (totalCount < 1)
		return new Page();
	// 实际查询返回分页对象
	int startIndex = Page.getStartOfPage(pageNo, pageSize);
	Query query = createQuery(hql, values);
	List list = query.setFirstResult(startIndex).setMaxResults(pageSize).list();

	return new Page(startIndex, totalCount, pageSize, list);
}



仅供参考我的HqlUtils.java中一方法代码如下,其实就是上面一段代码中引用的removeSelect和removeOrders方法:
    /**
     * 去除hql的select 子句
     * @param hql
     * @return hql
     */
    public static String removeSelect(String hql) {
        Assert.hasText(hql);
        int beginPos = hql.toLowerCase().indexOf("from");
        Assert.isTrue(beginPos != -1, " hql : " + hql + " must has a keyword 'from'");
        return hql.substring(beginPos);
    }
    /**
     * 去除hql的orderby 子句
      * @param hql
     * @return hql
     */
    public static String removeOrders(String hql) {
        Assert.hasText(hql);
        Pattern pattern = Pattern.compile("order\\s*by[\\w|\\W|\\s|\\S]*"
            , Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(hql);
        StringBuffer buf = new StringBuffer();
        while (matcher.find()) {
            matcher.appendReplacement(buf, "");
        }
        matcher.appendTail(buf);
        return buf.toString();
    }


25 楼 icewubin 2008-07-26  
Lucas Lee 写道
我刚才试了一下,在MySQL5下,select count(*) 和select count(id)是完全一样的。
表里有300万+条记录。
花了0.8秒,但是我用explain查看查询计划时,却发现它没有用primiary的index,于是强制它使用主键索引,结果花费了7秒!
我仔细研究了一下默认时使用的索引,居然是用的一个bit(1)上的索引,这个字段就是一个标志位。

想了一下,估计因为这个索引最小,基于bit(1),而主键是基于int(4)的,并且所有索引都能用于计算count(*),因为总是一条记录对应一个索引元素。

因此有一个小技巧提供给楼主:如果你的表中有类似标志位(比如是否逻辑删除)的字段,那么在其上建立一个索引,会把count(*)的速度提高数倍。当然最好用bit(1)类型,而不是int或char(1)保存标志位,那样会更慢。


不一定的,有些数据库对select count(*)做特殊优化的。
24 楼 icewubin 2008-07-26  
nihongye 写道
myprincejava 写道
nihongye 写道
很简单,大数据量,不要计算总数

你问题没看明白?现在就是要分页..怎么可能不得到count...

一个只足够翻页的方法,获取多于显示记录数量一的记录集,以此判断是否存在下一页。
计算总数耗费数据库资源,匹配的结果数量一多,建立的索引都浪费了。



总数可以缓存啊,刚才说了5秒缓存性能足够好了。

如果真的是很严重的性能问题,需求就不对,我只是说一个通用的情况,很严重的性能问题往往是需求的问题,而不是技术问题。用户自己真的需要每次翻页都实时的知道当前记录的精确总数么?然后一页一页去翻(1000多页?),这个UI交互模式的需求分析就是有问题的。

开拓思路,例如我如果用LiveGrid(Ext的一个扩展实现,操作方式类似于GoogleReader),总数只会取一次,相当于缓存了总数,你当然可以说这个总数实时性不高,当然没有完美的方案,都是有点缺陷的,要看这些缺陷是否值得去做,当前例子就是“实时性”真的那么重要么?0秒延时和5秒延时区别真的那么大么?很多情况下,1小时延时都被人吹成实时呢。

很多体育比赛实况转播不仅技术上有几秒的延时(卫星传送距离太远需要时间),更多的是故意的5分钟延时,这还不是中国特色,国外也很多的。
23 楼 icewubin 2008-07-26  
linux.sir 写道
用IBATIS吧,感觉简单,


ibatis一样要解决分页总数问题。
22 楼 helyho 2008-07-26  
建议用存储过程分页吧
hibernate分页多少多会带来相对较大的性能损失的
21 楼 linux.sir 2008-07-26  
用IBATIS吧,感觉简单,
20 楼 myprincejava 2008-07-26  
nihongye 写道
myprincejava 写道
nihongye 写道
很简单,大数据量,不要计算总数

你问题没看明白?现在就是要分页..怎么可能不得到count...

一个只足够翻页的方法,获取多于显示记录数量一的记录集,以此判断是否存在下一页。
计算总数耗费数据库资源,匹配的结果数量一多,建立的索引都浪费了。

考虑周密...也是不错选择!请看看在第二页我贴的代码...是要用两个对象才能实现得到count and list?还是...?
假如是两个对象HQL,进行查询的时候都需要把传进来的值设置到query.setString(),query1.setString() 这样不是重复操作?有没什么好思路得到值...
19 楼 nihongye 2008-07-26  
myprincejava 写道
nihongye 写道
很简单,大数据量,不要计算总数

你问题没看明白?现在就是要分页..怎么可能不得到count...

一个只足够翻页的方法,获取多于显示记录数量一的记录集,以此判断是否存在下一页。
计算总数耗费数据库资源,匹配的结果数量一多,建立的索引都浪费了。
18 楼 LucasLee 2008-07-26  
我刚才试了一下,在MySQL5下,select count(*) 和select count(id)是完全一样的。
表里有300万+条记录。
花了0.8秒,但是我用explain查看查询计划时,却发现它没有用primiary的index,于是强制它使用主键索引,结果花费了7秒!
我仔细研究了一下默认时使用的索引,居然是用的一个bit(1)上的索引,这个字段就是一个标志位。

想了一下,估计因为这个索引最小,基于bit(1),而主键是基于int(4)的,并且所有索引都能用于计算count(*),因为总是一条记录对应一个索引元素。

因此有一个小技巧提供给楼主:如果你的表中有类似标志位(比如是否逻辑删除)的字段,那么在其上建立一个索引,会把count(*)的速度提高数倍。当然最好用bit(1)类型,而不是int或char(1)保存标志位,那样会更慢。
17 楼 linkobe 2008-07-26  
springside的那个分页总数在表关联时是有bug的,比如一个商品有两个价格,这两表关联起来是有两个数据,但返回的却是一个商品,这是实践中发现过的。

相关推荐

Global site tag (gtag.js) - Google Analytics