分享
 
 
 

分页 & QueryKey & 定长预取

王朝java/jsp·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

分页 & QueryKey & 定长预取

数据库分页查询一般分为两步,

(1)根据查询条件,count 记录总数

(2)根据当前页的数据范围(起始位置offset, 每页数据个数span),从符合查询条件的记录集 取出对应范围的数据。

一、根据范围取数据的方法

如果单纯用JDBC从ResultSet中取出一个指定范围(offset, span)的数据,可以采用这样的方法。

ps = con.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);

ps.setMaxRows(offset + span);

rs = ps.executeQuery();

rs.absolute(offset);

while(rs.next())...

数据量大的时候,页数很多,offset很大,这种方法不太适合。这时候,需要使用各数据库的native SQL特性。

我们来看Hibernate dialect package的类,支持了各种数据库的getLimitString方法。这里举Mysql和Oracle的例子。假设查询语句为

Select * from message where forum_id = ? and created_time > ? order by created_time desc

那么,Mysql 的limit SQL为

Select * from message where forum_id = ? and created_time > ? order by created_time desc

limit ?, ?

后面的两个limit ?, ? 分别为 offset, span。

Oracle的limit SQL为

select * from ( select row_.*, rownum rownum_ from (

select * from message where forum_id = ? and created_time > ? order by created_time desc

) row_ where rownum <= ?) where rownum_ > ?

后面的两个limit ?, ? 分别为 offset + span, span。

二、缓存 & QueryKey

count语句可以根据查询语句自动生成,比如

Select count(*) from (

Select * from message where forum_id = ? and created_time > ? order by created_time desc

)

这样的自动count语句有些浪费,用了子查询不说,还保留了没有必要的order by。最好还是另外提供一个count语句。

Select count(*) from message where forum_id = ? and created_time > ?

在多页翻动的情况下,这个count语句要被反复执行。为了提高效率,我把这个count结果保存在全局缓存中,不仅本Session用户可以重复使用,其他用户在根据同样条件翻找message的时候,也可以重复使用这个结果。

我在持久层中使用通用的QueryKey做为缓存键值。

QueryKey分成三个部分,SQL, Parameters, Range。比如:

Query Key:

SQL : Select count(*) from message where forum_id = ? and created_time > ?

Parameters : [buaawhl, time long value]

Range: (0, 1)

这个QueryKey的效率很关键。主要是hashCode和equals两个方法的效率。

我们知道,当key放在Map等Hash数据结构中,首先hashCode,然后用equals比较hashCode后面的一串key。

举个例子。Key1和key2 的hashCode一样,都和key3的hashCode不一样。

[ 101 ] -> key1 -> key2

[ 666 ] -> key3

可以看到,hashCode,equals,这两个方法都是每次查找缓存都要调用的方法。尤其是equals方法更是重中之重,很可能需要被调用多次。

hashCode的优化实现相对来说比较简单,只要根据QueryKey中各部分的不同,尽量实现hashCode取值的扩散化,降低hashCode的重复率就可以了。

关键是equals的实现方案。这里有个原则,越小的结构越先比较,可以提高比较速度。

QueryKey中的parameters和range比较好办。每次equals比较的时候,先比较range,如果不相等,返回false; 如果相等,再比较Parameters,如果有一个parameter value不相等,返回false。这样,我们可以用很短的时间开销 过滤掉一大批不相等的QueryKey。

但是parameters和range都相等的时候,我们还是无可避免的要比较SQL。String的equals方法如下:

// from jdk src

//这个方法没有比较hashCode,直接比较长度和字符

public boolean equals(Object anObject) {

if (this == anObject) {

return true;

}

if (anObject instanceof String) {

String anotherString = (String)anObject;

int n = count;

if (n == anotherString.count) {

char v1[] = value;

char v2[] = anotherString.value;

int i = offset;

int j = anotherString.offset;

while (n-- != 0) {

if (v1[i++] != v2[j++])

return false;

}

return true;

}

}

return false;

}

我们看到,两个相同的长String具有不同的reference,那么比较起来是相当消耗时间的。所以说,字符串比较,不怕不同,就怕相同。大部分情况下,不同的String的长度不同,或者前几个字符串开始就不相同,很快就能够得出比较结果。

当然也有这种情况,两个SQL String都很长,而且长度相等,而且前面大部分字符相同的时候,到了后面才有字符的不同。比如,

Select * from message where forum_id = ? and created_time > ? order by created_time desc

Select * from message where forum_id = ? and created_time > ? order by updated_time desc

这两个String的长度相等,前面大部分也相等,只有走到cre 和 upd 的时候,才能比较出不相同。如果两个字符串内容一样,那更是要走到头,才能判断出两个字符串完全一样了。

我的第一个做法就是,尽量使用static final String做为QueryKey的SQL。这样两个SQL的reference如果相等,那么可以迅速判断出两个SQL相同。

这个做法只能处理事先定义好的SQL语句,但实际需求中,存在很多需要动态拼接SQL的情况,不可能做到所有相同的SQL具有相同的reference。

我又采取了第二个做法:分而治之,把一个SQL String拆分成多个SQL常量的数组;泛化SQL的类型,SQL不限制为String类型,也可以是String[]类型。

比如。

String[] sql1 = {

“Select * from message where forum_id = ?”,

“ and created_time > ?”,

“ order by ”,

“created_time”,

“desc”

};

String[] sql2 = {

“Select * from message where forum_id = ?”,

“ and created_time > ?”,

“ order by ”,

“created_time”,

“desc”

};

String[] sql3 = {

“Select * from message where forum_id = ?”,

“ and created_time > ?”,

“ order by ”,

“updated_time”,

“desc”

};

这个时候,比较sql1和sql2和sql3的效率就会大大提高,虽然sql1 和 sql2两个数组的长度相等,还是要一个元素一个元素的比较,但由于里面大量用到了String常量,相同的String常量具有相同的reference,所以5步下来,就可以判断出sql1和sql2数组的元素是完全相等的;4步下来,加上第一个字符的比较,就可以判断sql1和sql3的第4个元素是不相等的。

我们看到,做法1和做法2,能够提高SQL的比较效率,大部分情况下,也许比parameters的比较还快。

三、定长预取

多用户访问同一页面的可能性比较大的情况下,比如,论坛的某些热门话题,很可能被多人同时翻阅。这时候,如果把根据范围取出的数据对象List也按照QueryKey存入缓存中,那么就可以大大提高响应速度,减轻数据服务器负担,当然,你的Web Server的内存负担也大大增加了。:-)

我们进一步考虑下面两种情况:

1. 用户自定义页面记录数

一般来说,用户可以自定义自己的每页显示记录个数,比如,有些用户喜欢每页20条,有的喜欢每页10条。

假设用户A翻到一个论坛的第一页,显示1 – 20条信息;用户B翻到同一个论坛的第一页,显示1 – 10条信息。这个时候,缓存的命中率是很低的。用户A和用户B无法共享缓存信息。因为他们的range(的span)总是不同,QueryKey永远不可能相同。

2. 记录很多、每页记录数过少

假设一个论坛里面有1000条信息,每页显示10条,那么共有100页。如果用户一页一页的翻动,每次程序发出一个span大小为10的Query请求,取出10条记录,根据QueryKey缓存起来。由于页面记录数过少,每次数据库查询的效率很低,缓存命中率也很低。

为了提高缓存命中率,并且顺便实现数据预取功能,我们可以采取 同一定长Span的方案。比如,还是上面的例子,我们在程序中设定统一Span大小为100。

当用户A请求1 – 10的记录的时候,程序判断这个落在 1 – 100的范围内,那么用range (1, 100)获取100条记录,把前面的10条返回给用户。当用户A翻了一页,请求11 – 20的记录的时候,程序判断还是落在 1 – 100的范围内,而且已经存在于缓存中,那么直接把对应的11 – 20条返回给用户A就可以。

当用户B 请求1 – 20的记录的时候,程序判断这个落在 1 – 100的范围内,而且已经存在于缓存中,那么直接把对应的1 – 20条返回给用户B就可以。

可以看到,这种定长预取方案能够大大提高数据库查询的效率和缓存的命中率。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有