用OO方法解一道算术题

本篇主要为说明使用面向对象的分析和设计方法可以帮助更快地认识事物,更快地排除编程设计过程一个个拦路虎。

用OO方法解一道算术题:

http://www.jdon.com/artichect/oo_math.htm

我认为将startIndex作为key的组成部分是没有必要的,因为如果startIndex最终会作为sql的一部分:select * from a limit...,而且,如果使用MS SqlServer,startIndex就不起作用了。Hibernate就是把sql、参数、分页信息等等作为缓存key,所以Hibernate的批量查询缓存的实用性并不高。缓存中的数据块应该从第一条记录开始,到MaxResults,如果分页超过了这个范围,则调整MaxResults并重新执行查询。还有,分页的时候不要从数据库中取,而是在缓存中的数据块中进行分页,下面是我的key的实现:
public class CachedQueryKey implements Serializable {
private String queryString;
private Object[] args;
//....getters/setters,hasCode,equals,toString
}
数据块采用List作为数据内容也是不合理的,因为根据查询内容的不同会产生许多VO,还不如使用RowSet,我实现了一个动态Bean:
public interface Rows extends Serializable{
/**
* 将ResultSet对象的一行导入。
*/
public void addRow(final ResultSet rs) throws SQLException;

/**
* 将Row对象导入
*/
public void addRow(final Row row);

/**
* 遍历ResultSet对象,并全部导入
*/
public void addRows(final ResultSet rs) throws SQLException;

/**
* 遍历行集的所有行,返回Iterator接口。实现者可以采用内部类实现Iterator接口。
*/
public Iterator iterator();

/**
* 返回指定行
*/
public Row get(int index);

/**
* 如果行集中没有数据,返回true
*/
public boolean isEmpty();

/**
* 返回行集的最大预设容量(number of row)
*/
public int getMaxRows();

/**
* 预设行集的容量(number of row)
*/
public void setMaxRows(int maxRows);

/**
* 返回行集的实际容量
*/
public int getRows();

/**
* 取得行集中某一行的指定列。
* @param colName 列名
* @param rowIndex 行索引,第一行为0,第二行为1,...
*/
public Object get(String colName, int rowIndex);

/**
* 返回指定范围的子行集。
* @param startIndex 起始索引,必须>=0 <=getRows
* @param maxRows 子集的容量
*/
public Rows sub(int startIndex, int maxRows);

/**
* 将行集的每一行转换为指定类型的java bean对象,并以List的形式返回。
* @param type 指定的类型,必须是标准的JavaBean
*/
public List toList(Class type);
}
这个是接口,实现类必须注意不能使用Map作为每一行,因为Keys会占用过多的内存,如果追求效率,可以使用数组Object[][]

由于很多应用要求实时性较高,所以缓存的使用也是可选的,我使用动态代理实现,这样,如果不想使用缓存就不要代理即可。
还有,分页算法应该和查询以及缓存的应用分开,作为一个单独的Aspact使用,分页算法最重要的是取得真实查询结果的总行数,这个数据是分页的基础,它也需要缓存。

切磋一下:
>数据块采用List作为数据内容也是不合理的,因为根据查询内容的不同会产生许多VO,还不如使用RowSet,我实现了一个动态Bean

数据块中存放的是主键集合,不是VO;不能使用属于持久层的对象在全层之间传来传去,你的自己动态Bean不错。
这里List和Block都是一个临时变量,在Jdon框架中,在全层之间传递是PageIterator,类似你的Rows


>我认为将startIndex作为key的组成部分是没有必要的
真正到数据块查询(dataBaseBlock)的startIndex不是查询条件的startIndex,而是经过计算:
int blockID = start / count;
int blockStart = blockID * count;
中的blockStart,
这样能够保证从数据库或缓存获取数据块时,表明这个数据块是符合我们起点要求的数据块。

如果不使用起点作为Key一部分,那么可能缓存不起作用,因为同一个查询条件下,多个页面的区别就是起点不一样,这样我们可以将包涵每个页面的数据块缓存。

另外,“缓存中的数据块应该从第一条记录开始”不知你是指符合查询条件的第一条吗?还是当前页面的第一条呢?我前面是指当前页面第一条。
你可能也是这个意思,你说"分页超过了这个范围,则调整MaxResults并重新执行查询",
这是一种实现思路,只是我现在提供遍历的这个类PageIterator是一个简单的POJO,是一个DTO,没有自己操作数据块能力,这也给我带来实现难度。

所以有时想,MF提出的非贫血模型多好,自己不但带数据,还可以在任何时刻操作数据库,但是它是不是象Hibernate的Open in View模式,将持久层的潘多拉盒子打开,在各层到处是飞舞的数据库影子。

可喜的是有cats_tiger 这样同志者和我一样在做这样的探索,而我在google上搜索看到更多是在数据库端做功夫,这里有一篇文章试图通过存储过程等数据库操作实现批量查询的案例:
海量数据库的查询优化及分页算法方案:

http://www.pconline.com.cn/pcedu/empolder/db/sql/0501/538958_6.html

但是在设计上这是一个错误的方向,向数据库要性能要潜力的余地已经很小了。

更重要的是,造成代码可维护性和可拓展性很差,更可怕的是:几乎所有搞数据库系统的人都是这种思路,思维不改;而且他们对选择其他道路还在怀疑观望,这种思维下的程序代码质量能高吗?

真应该让更多人明白:数据库时代已经过去了。



老大,既然这是一个错误的方向,那么拿这篇文章举的一个相关例子,在一个有上亿条记录的人口数据库里边,不做数据库优化的情况下,如何能够按某个条件(比如人名或出生日期),做到快速查出相关记录呢?这里的纪录一般可是关系到多个表的哦。

我最近刚做了一个sql的优化(实际上是hsql),优化前查询速度是80多秒,优化后查询速度是80多毫秒,请问老大为什么说向数据库要性能要潜力的余地已经很小了呢?

请用缓存实现上边说的人口数据库的例子~这里的查询都是随机的,一次查出来的数据一般也都在一页之内。一页以上大家就在加条件限制范围了,没人会一页一页翻着看哦。

》化前查询速度是80多秒,优化后查询速度是80多毫秒
提高了1000倍,但是80毫秒就到头了,业务数据是不断增加的,这种方式是没有弹性的,可伸缩性不大。

那么我们使用其他手段是否可以达到这种目的,我相信也会达到的。

关键是使用这个方法hsql,可能破坏代码的质量,最后导致核心逻辑掌握在少数人手里,这是危险的,不能规模复制或生产。

当然,我不否认,在实际情况下,在时间紧迫下,可以采取这样措施。

无论如何,我们探讨的是一个可以规模复制的解决之道,也就是适合大多数“笨人”的解道。

不但追求性能卓越;而且代码质量优异,后者更应该是程序员追求的目标,如果yuxie有意,我愿意和你一起研究这个上亿数据优化,我刚刚测试过Jdon framework 1.4,10万数据读写速度都没有变化,到后面越来越快,就象读10个数据一样。

>我愿意和你一起研究这个上亿数据优化
老大说的是中间件这一端的优化吗(还不能涉及sql优化,以免技术掌握在少数人手里)?8好意思,那我想说的是,当数据量比较大,又可能涉及多个表连接的时候,又涉及基本无规律的查询,这时候不管你框架怎么优化都没用……

原因很简单。缓存是什么?缓存的只可能是经常访问到的数据,在访问无规律的情况下,使用缓存只能降低性能,我最看不惯的就是老大经常提起的缓存之上论。这时候数据库是根本,不重视就会吃苦头。您如果一直坚持自己的观点给人培训那实在太不责任了。

至于您说的那10万条记录越来越快……我无语……现实的情况可不是单表数据的列表显示那么简单……如果您的客户需求都这么简单……还需要程序员干什么……

呵呵,yuxie兄好像有些生气了,我们只是探讨一些观点,不代表我否定在数据库优化上做,不但数据库优化要做,更深层次的优化也要做,两者结合当然更好啦,也就是说DBA和设计师一起努力来提高系统性能。

我刚刚贴了google的搜索原理:
http://www.jdon.com/jive/thread.jsp?forum=91&thread=24454
文章中反复谈了优化数据结构的重要性,当然也有这句:

“我们必须有一个巧妙的算法来决定哪些旧网页需要重新抓取,哪些新网页需要被抓取。这个目标已经由实现了。受需求驱动,用代理cache创建搜索数据库是一个有前途的研究领域。”

找到算法:尽量不用到频繁到性能差的数据源获取数据,这个思想在实践中值得借鉴。

大家保留各自观点吧。

>如果不使用起点作为Key一部分,那么可能缓存不起作用,因为同一个查询条件下,多个页面的区别就是起点不一样,这样我们可以将包涵每个页面的数据块缓存。
这个是关键,我是把符合条件的1~1000条数据保存在缓存中,分页在缓存中进行(使用Rows.sub方法),这样就减少了数据库操作,这个是与Hibernate和banq老大的不同的地方。那么,如果每页显示50条,而我需要20页之后的数据呢?重新查询并扩大范围就可以了。还有就是多数人在查询的时候,是不会关心几千条数据的。
将每个页面都作为一个缓存块,其缺点是:1)分页的时候需要查询数据库
2)不同的缓存块的过期时间不同,如果数据库内容发生变化,会导致混乱,除非同时更新所有缓存快。

>数据块中存放的是主键集合
那是不是每次使用某的时候还是要查询数据库呀?跟Jive的做法类似。

>另外,“缓存中的数据块应该从第一条记录开始”不知你是指符合查询条件的第一条吗?
我是指符合条件的第一条。缓存中存放内容包括:符合条件第一条到MaxResults条数据;符合条件的数据的实际数量。

另外,数据库的调优也很重要,一条索引就可以提高几百倍的性能。另外,SQL的调优也非常重要,例如使用exists就比in快的多。

缓存比较适合经常使用的数据和分页查询的情况,对于很多"边查询边修改"的用例就不合适了。