您的位置:首页 > 数据库 > Oracle

分页查询优化--oracle

2009-09-24 11:36 579 查看
前一段时间优化了一个大数据量的查询功能(最大大约250万条/月,大约300M/100万条,库中始终保持3个月数据,之前的数据由定时任务备份清除),下面梳理一下思路,总结一下经验。

环境:

1. oracle9i==测试数据库服务器HP-UX B.11.23 安腾64为 4核服务器

2. java1.4

3. jboss4.0.5==测试应用服务器rhel as r4 pc主机

4. 一天入库量8.5万条左右,10分钟一个周期

5. 表名:tab_name

结构: c1 varchar, c2 varchar, c3 varchar, dateCollection date, ip varchar, cname varchar, type varchar

优化(查询速度):

1. SQL语句优化

采用绑定变量的形式,避免每一次查询使oracle都去解析SQL语句而影响效率;

调整SQL语句写法,采用如下形式:

select *

from (select row_.*, rownum rownum_

from (

select c1, c2, c3

from tab_name

where dateCollection >= startDate

and dateCollection < endDate

and ip = '127.0.0.1'

and cname = 'abc'

and type = 'efd' order by dateCollection desc

) row_

where rownum <= endNum)

where rownum_ >= startNum

这里每页展示10条记录0<endNum-startNum<=10

2. 表结构调整


前该表是一个普通表,没有索引没有PK;后将该表修改为分区表,每一个自然月一个分区。结合这一个应用,页面有3个查询限定条件
(ip,cname,dateCollection
,type为后台隐含条件),时间条件为必选项,起初只对dateCollection建立索引,但是经过测试发现性能达不到要求(查询第一页的时间在
18~20秒之间);后来反复查看代码和执行计划发现性能主要消耗在两个地方:

A. select count(1)
from tab_name where dateCollection >= startDate and dateCollection
< endDate and ip = '127.0.0.1' and cname = 'abc' and type = 'efd';
这个是计算符合条件的记录总数,执行时先扫描索引,然后根据rowid再去磁盘(内存)中去取指定的数据,因为根据rowid拿数据的时候只是拿一个
block的数据与全表扫描相比平白多出N多disk read操作,按照这种方式count一个月的数据大概需要5~6秒;

B. 数据查询的SQL语句也存在和A一样的问题,查询SQL的另一个性能主要消耗在order by dateCollection desc;一个月250万数据,order by一下可不是一个简单的问题;

针对这两个问题,对表进行了调整:建立组合索引create index ind_his on tab_name(dateCollection
desc,ip,cname,type )
local,这样建立索引后需要的数据都可以在索引中拿到,且索引是按照dateCollection进行降序排序的,查询SQL中的order by
可以省去但是在SQL需要键入hints/*+index()*/强制用组合索引,这样查询出来的数据都是按照dateCollection降序排序的;
修改之后count一个月数据大概需要2.0秒左右,从一个月中查询出前10条记录在0.1秒内完成;从页面上操作查询一个月数据的前10条可以控制在3
秒以内,比预期的5秒还少了2秒的时间。查询的完整SQL如下:

select *

from (select row_.*, rownum rownum_

from (

select /*+index(tab_name, ind_his)*/c1, c2, c3

from tab_name

where dateCollection >= startDate

and dateCollection < endDate

and ip = '127.0.0.1'

and cname = 'abc'

and type = 'efd'

) row_

where rownum <= endNum)

where rownum_ >= startNum

总结:

1. 查看SQL的执行计划,查找到瓶颈所在,优化SQL;使用绑定变量的形式执行SQL

2. 索引不是万能的,当依据索引查询出的数据占总数据的量比较大(有人说5%~10%)时反而会降低性能

3. 建立合理索引

4. 创建索引时可以进行排序,合理的运用这个功能,减少在查询时进行排序,会给SQL性能带来一个指数级的提升

进一步:

1.
由于受原系统设计和时间的限制,没有对该查询功能进行更优的优化;按照上面进行优化后在查询一个月(250万条)数据的前面页时速度相对比较理想一些(第
一页3秒以内,单用户操作),但是查询最后一页需要18秒左右,这个就很不理想,那么有没有办法查询最后一页时也可以达到3秒以内的速度呢?有!创建索引
时是按照dateCollection进行降序排序的,上面的查询方法读索引时是从索引的前面开始的(个人理解,测试得到验证),如果读索引时从索引的最
后读那么读到的内容不就是最后面的数据了吗?的确是这个样子(因为自己对oracle不甚了解,不知道怎么用比较专业的术语来表述),将查询SQL的
hints换为/*+index_desc()*/就可以达到这个目的。对查询出来的数据做order by dateCollection
desc处理就可以得到和上面查询一样的结果,而且查询时间在3秒以内。完整查询SQL如下:

select *

from (select row_.*, rownum rownum_

from (

select /*+index_desc(tab_name, ind_his)*/c1, c2, c3,dateCollection

from tab_name

where dateCollection >= startDate

and dateCollection < endDate

and ip = '127.0.0.1'

and cname = 'abc'

and type = 'efd'

) row_

where rownum <= endNum)

where rownum_ >= startNum order by dateCollection desc


需要注意的是,需要对页面传回来的startNum,endNum进行转换处理。按照这种方式处理的话代码中就需要有两个SQL语句,由代码去判断用哪一
个SQL查询(可以无厘头的决定中间页及前面的页用<优化:2:表结构调整>中的SQL查询,中间页以后的页用上面的SQL查询)

2.
在翻页的时候可不可以更快呢?如果按照<进一步:1>的方案优化的话,那么查询速度最慢的将是查询中间页,经测试(250万条/月)查询中间
页时时间在8.5秒以内,有没有办法提高中间页的查询速度呢?这个问题我还没有想到解决办法,哪位朋友有好的主意希望不吝赐教。继续说翻页的问题,如果中
间页的查询速度也得到解决的话(这里只是假设,O(∩_∩)O)那么翻页将是这个查询的最大弊端,因为我翻一次也只得到10条数据,而要等待3秒时间,这
样看来好像很不划算,那么有没有办法在一定程度上降低这个时间呢?有! 这个问题完全需要在代码层进行解决,大致思路如下:

在查询时一次可以查询出10的倍数的记录(假设100条,针对我的这个应用100条记录大该有30K的数据量,如果网络正常的话不影响响应速度),然后将
这些记录发送到客户端,在客户端用脚本进行处理(javascript等),这样用户翻页时基本感觉不到停顿,当缓冲页面翻完之后才会真正的到服务器端再
做一次查询,再缓冲几页数据。这样在很大程度上会提高用户体验!!

如果能够按照<进一步>讲的进行优化,相信这块的用户体验会更好!但是代码逻辑要会复杂很多,O(∩_∩)O。收获都是有付出的,O(∩_∩)O。

新问题:

现在面临一个新的问题,原先设计的数据量已经过时了,现在每个月的数据量激增到1500万条,代码层面的优化已经没有空间了,个人感觉需要从数据库角度着手才可以解决性能要求的问题。初步考虑从以下角度入手:

1. 考虑是否可以将分区进一步缩小时间跨度

2. 分散IO,分区建在不同磁盘的不同表空间上,索引也一样

3. 数据库相关参数的调整

有朋友看到的话,如果你有点子,请不吝赐教,O(∩_∩)O!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: