分页查询优化--oracle

前一段时间优化了一个大数据量的查询功能(最大大约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

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!

 

 

你可能感兴趣的:(oracle,优化,sql,数据库相关,oracle,数据库服务器,测试)