通过去年做的一个项目我体会到,数据量大了以后,看似简单的问题都变得让人寝食难安,在解决问题的过程中收集了不少好文章,集中在此,方便自己查阅。
oracle 9i 数据库设计指引全集(1) oracle 9i 数据库设计指引全集(2) oracle 9i 数据库设计指引全集(3)
oracle 9i 数据库设计指引全集(4) oracle 分区表的建立方法 关于oracle表空间的规划方法
关于Oracle数据库中索引的维护经验谈 Oracle 分区索引详解 何时使用绑定变量性能反而差
先说一下项目的情况,做的是一个治安卡口软件,在进出城市的主要路口安装摄像机,记录过往车辆的相关信息(车牌号码、通过时间、车速等等),把采集到的车辆信息传至中心服务器存储,供相关业务部门进行布控、查询、违法处罚等,业务很简单,就是数据量巨大(想想一个路口一天要过多少车,10个路口,20个......)业主要求保留近3个月的记录.
系统在运行了过程中,随着数据量的增加,问题不断的出现,2000万、4000万......每次都是一次折磨
一个功能是按照一些条件对过往车辆进行查询,由于过往车辆表的数据库太大,查询的响应时间成了大问题
下面是遇到的一些问题:
1. 限制一次可以查询的最多记录数
我的办法是根据计算记录总数,得到记录总数后生成分页导航,再载入当前页数据。
利用rownum进行分页,分页的办法:
select *
from (selet rownum as rn,t.* from t where ...)
where rn > pageleft and rn <=pageright
在开发阶段由于测试环境的数据量很小,没出现什么问题,到了实际环境问题来了,用户查询一段时间通过的车辆,计算个记录总数就需要很长时间,我自己都不能忍受,更不用说用户了。
找点理由:即使我把这几十万、上百万的记录返回给了,有什么用呢?用户需要的是尽量精确的记录,返回的记录数越少越有价值,用户不会在几十万条记录中一条一条的去找。所以可以限制一次能够查询的记录总数,比如只返回前1万条;
郁闷的同时我也在想:google是怎么做分页?
从google的搜索结果中我注意到两点:
1.顶部给出的查询结果总数不是精确值,而是“约有…” ,不是即时计算的。
2.如果查询1000条后的记录,修改url中的start后的参数:
http://www.google.cn/search?hl=zh-CN&newwindow=1&q=dudu&start=1001&sa=N
会出现“对不起,Google 为所有查询的结果数都不会超过 1000 个。 (您所请求的结果在第 1001 个之后。)”
了解到了这些,我理直气壮的跟用户说:我要限制一次能够返回的记录的数量。
于是修改为:
select *
from (selet rownum as rn,t.* from t where ... and rownum < maxRow )
where rn > pageleft and rn <=pageright
修改后的查询比以前快了一些,我想了想,其实不用rownum<maxRow 只要
select *
from (selet rownum as rn,t.* from t where ... and rownum <= pagerright)
where rn >= pageleft and rn < pageright 即可,
修改后再试,又快了一些;发现翻页时,随着页码的增加,响应时间也逐渐增大,这是肯定的了,rownum<=10和rownum<=1000肯定不一样的了。
2. 不计算count
过了一两个月,用户又打电话来了,说有些组合条件查询很慢,甚至没有响应。到现场看看,查一下现在有多少数据,执行了个select count(*) ,竟然等了十几分钟才出来结果,记录数是3500多万条,后来调试程序,发现真正取得一页数据还是比较快的,耗费时间的操作是计算记录总数,把sql语句直接放到pl sql 中执行也需要几分钟才能出来结果,靠,这个select count 要这么长时间,算了,不计算查询结果的总数了。不计算记录总数,还要分页,想了一些办法解决了。修改后查询速度快了很多,这两个月通过对oracle的学习,俺已经认识到这么大的数据量应该对主表进行分区了。
3. 按照时间对主表进行分区
按照时间对过往车辆表进行分区无疑是最合适的。
如果一个月建一个分区,我可以预先建出来未来半年或一年的分区,例如2008-07-01 00:00:00 到2008-08-01 00:00:00 之间的记录在一个分区,2008-08-01 00:00:00 到2008-09-01 00:00:00 之间的记录又是一个分区;这样做的问题是过半年我需要到现场再创建以后的分区,删除以前作废的分区,不是个好办法。
最后想到的办法是按照月份建12个分区,按照记录的时间字段所在的月份映射到对应的月份分区。不管是08年7月的还是09年7月的记录,均映射到7月的分区。
按照月份分区后,效果很明显。
4. 定期对表进行统计分析
5. 一个教训是:使用绑定变量方式和直接赋值方式的执行计划可能是不一样的;当发现某个查询的速度比预期的慢的很多,很可能是sql语句执行时使用了错误的查询计划,这个时候就需要去查找原因了。
6.数据量大,增长快,数据的备份也不容易了。