用户操作
[即时聊天] [发私信] [加为好友]
wanghailongID:buaawhl
102521次访问,排名919,好友0人,关注者1人。
buaawhl的文章
原创 49 篇
翻译 0 篇
转载 0 篇
评论 152 篇
最近评论
bruce_lau:看看这个解决方案,对象的缓存和jive差不多,列表也是选id,但是对列表还有缓存,还可以根据字段作散列列表缓存,这样列表的缓存命中率非常高,是一个比较好的数据库缓存解决方案。参见http://shedewang.com/akaladocs/api/com/akala/dbcache/core/BaseManager.html
yipeng6213:个人觉得fastm真的很不错,我之前的模板是用velocity写的,我现在想把它转换成fastm的,但其中velocity模板中的if elseif else 怎么 用fastm替换呢?请问有没有具体的实例 给我参考一下,急!
lidaoguang00109:谢谢您的努力.
这个FastM我使用过了,觉得很不错.
容易上手,特别是可以直接用HTML观察模版的布局形态,有利于界面效果的评估.
现在有一个问题一直困扰我.
就是如果实现一个Table,每一个列被点击后都能排序,用FastM如何实现呢?每一次ValueSet DOM都要重新设置吗?
vkuja2003:楼主做学问的方法很值得学习
noia_zhou:我拜读了你的持久层和cache设计的一些文章,觉得想法很不错。我觉得现在的持久层框架还是挺复杂的,如hibernate等,单看看它10几兆的开发包就知道了,我的初步想法是利用po跟table的字段一致性,通过反射po得到要执行的sql。这样做的好处是少了很多表结构的配置文件。然后做一个简单dao来完成。这里我的想法是做两个dao,一个BaseDao,BaseChaheDao。其中两者都是 继……
文章分类
收藏
    相册
    友情blog
    http://smsbim.bokee.com/
    存档
    软件项目交易
    订阅我的博客
    XML聚合  FeedSky
    订阅到鲜果
    订阅到Google
    订阅到抓虾
    订阅到BlogLines
    订阅到Yahoo
    订阅到GouGou
    订阅到飞鸽
    订阅到Rojo
    订阅到newsgator
    订阅到netvibes

    原创 分页 & QueryKey & 定长预取收藏

    新一篇: 线程运行栈信息的获取 | 旧一篇: 数据库对象的缓存策略

    分页 & QueryKey & 定长预取

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

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

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

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

    如果单纯用JDBCResultSet中取出一个指定范围(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方法。这里举MysqlOracle的例子。假设查询语句为

    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

     

    Oraclelimit 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的效率很关键。主要是hashCodeequals两个方法的效率。

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

    举个例子。Key1key2 hashCode一样,都和key3hashCode不一样。

    [ 101 ] -> key1 -> key2

    [ 666 ] -> key3

     

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

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

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

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

    但是parametersrange都相等的时候,我们还是无可避免的要比较SQLStringequals方法如下:

    // 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做为QueryKeySQL。这样两个SQLreference如果相等,那么可以迅速判断出两个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”

    };

     

    这个时候,比较sql1sql2和sql3的效率就会大大提高,虽然sql1 sql2两个数组的长度相等,还是要一个元素一个元素的比较,但由于里面大量用到了String常量,相同的String常量具有相同的reference,所以5步下来,就可以判断出sql1sql2数组的元素是完全相等的;4步下来,加上第一个字符的比较,就可以判断sql1sql3的第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大小为10Query请求,取出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就可以。

     

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

     

     

    发表于 @ 2005年01月08日 14:56:00|评论(loading...)|编辑

    新一篇: 线程运行栈信息的获取 | 旧一篇: 数据库对象的缓存策略

    评论

    #shaokun305 发表于2005-02-22 09:04:00  IP:
    TrackBack来自《http://blog.csdn.net/buaawhl/archive/2005/01/08/245005.aspx》

    Ping Back来自:blog.csdn.net
    #buaawhl 发表于2005-05-31 08:37:00  IP:
    TrackBack来自《Java Web开发构想(5) -- 7.O/R; 8.总结》

    Ping Back来自:blog.csdn.net
    #lovefanx 发表于2005-06-01 23:40:00  IP: 61.186.252.*
    你说的这个把sql串变成字符串数组来比较的方法,还是头回听说,确实很长见识:)
    #buaawhl 发表于2005-06-02 11:41:00  IP: 61.186.252.*

    thanks. :-)
    随着对Hibernate, JDO的逐步深入了解,还有对象数据库Cache' 和 Objectivity的了解,Lightor的缓存策略还在发展中。
    (1)为了进一步提高命中率,减少不必要的缓存清空,或者减少缓存清空的粒度,
    (2) 为了让相关的数据库对象、页面资源对象都放在一起,共同清空。
    Lightor引入了多级缓存机制。

    带来的好处是,性能提高;Cluster支持良好;命中率高。
    带来的坏处是,Cache控制代码对 业务逻辑代码的侵入性高,编程复杂。所以,要控制好,一般把Cache 控制代码局限在DAO层以内,不扩散到DAO层以外。当然,有的时候,为了更好的性能/命中率,Cache 控制代码有可能需要进入业务逻辑,这个就一定要做好代码的包装:提供一个没有缓存控制的业务方法;提供一个有缓存控制的包装方法。对于一些通用的缓存控制,可以采用AOP实现。
    发表评论  


    当前用户设置只有注册用户才能发表评论。如果你没有登录,请点击登录
    Csdn Blog version 3.1a
    Copyright © buaawhl