一个分页查询优化的案例解析-谈谈SQL调优的基本思想

2020-08-08 00:00:00 索引 字段 执行 逻辑 排序

昨天下午完成公司安排的培训有点累,又跟同事忙里偷闲到哈尔滨的步行街走了走, 品尝了一根马迭尔冰棍,到松花江畔吹了吹江风,讲课的疲惫缓解了不少。配一张图吧,这是同事拍的,抓的角度很特别,让我们的技术文章多一点生活的趣味。

今天跟大家分享一个小案例,之前为一个公司做项目终结验收的一个护航工作,我的任务就是每天监控并抓几个TOPSQL 分析下看是否可以调优,而后直接将这些SQL调优意见发给对方的技术总监,他们确定该SQL对应的业务后决定是否调优处理。

对于SQL调优,我们要普及写基本知识,调优的一个基本原则就是少拿数,这三个字是之前听Oracle集群专家高斌老师的课程时听到的,或许之前也知道这个原则,但是那次优化学习这三个字深深刻在我的脑子里。也希望大家能够铭记于心,也就是SQL的执行要在解析阶段就确定尽量少拿数,在引擎阶段就做出好的决策,也就是有个优的执行计划,但是这个优还要依靠表数据分布的特点以及其他对象的存在,比如索引,约束,主键,也可能需要配合数据库参数调整。

由于不能全文发布该SQL(出于职业要求),我就模拟这个SQL的逻辑来分析,在分析的过程中我们一起学习调优的知识点以及对SQL逻辑的分析。

这是一个分页查询,它在TOPSQL中排第三位,主查询查询一个业务表,需要针对一个字段做排序,后取排序的前49行数据(49纯属满足测试rownum<50,没有实际意义),SQL语句如下:

select * from ( select a.object_name,a.owner,a.status,a.namespace from test_obj a order by created desc ) where rownum<50;

原始的执行计划如下:


 

Predicate Information (identified by operation id):

---------------------------------------------------


   1 - filter(ROWNUM<50)

   3 - filter(ROWNUM<50)


Statistics

----------------------------------------------------------

          1  recursive calls

          0  db block gets

        185  consistent gets

          0  physical reads

          0  redo size

       2299  bytes sent via SQL*Net to client

        556  bytes received via SQL*Net from client

          5  SQL*Net roundtrips to/from client

          1  sorts (memory)

          0  sorts (disk)

         49  rows processed


我们先分析下SQL逻辑,针对表test_obj查询四个字段,排序字段是created不在查询需求中,这个排序是降序排序,外围的分页查询需要取前10行数据。

从执行计划看,显示执行第四步全表扫描,而后通过第3步骤排序操作,通过第1步分页取数获得,我们看到在34步骤是耗资源的部分,尤其是第3步的sort oder by 操作优化器评估使用Temp空间,也就是服务器进程的PGA排序区已经无法满足存储数据需求,走了外存,这步骤也是要尽量避免,而目前这个步骤对于Oracle而言也是迫不得已,因为第4步走全表,根据用户的业务需求确实需要全表排序。注意此时的逻辑读为185,有内存排序。

对于这个操作,我应该知道索引的基础知识,就是索引是有序的,并且返回的数据量不大,这是典型缺乏索引的情况,而通过查询该字段系统没有创建索引,所以我建议再改字段创建desc索引.

SCOTT@orcl1>create index idx_test_obj_created on test_obj(created desc);


Index created.

我们再次执行该SQL ,看优化器是否使用该索引。 


Predicate Information (identified by operation id):

---------------------------------------------------


   1 - filter(ROWNUM<50)



Statistics

----------------------------------------------------------

          0  recursive calls

          0  db block gets

         20  consistent gets

          0  physical reads

          0  redo size

       2305  bytes sent via SQL*Net to client

        556  bytes received via SQL*Net from client

          5  SQL*Net roundtrips to/from client

          0  sorts (memory)

          0  sorts (disk)

         49  rows processed

此时的全表扫扫描消失了,Rows字段为49,也就是通过索引找到了前49行记录,然后通过步骤3回表,而步骤1没有消耗资源,因为用户所需要的数据前面已经全部获得。此时的逻辑读只有20,注意此时步骤4的逻辑读为6个,回表找数又耗费了14个逻辑读,这里可以进一步优化。

其实这里还可以做一次针对该SQL的优化,因为走索引依然需要回表的IO需求,本案例是14个逻辑读,也就是回表依然需要逻辑读,如果能消除这个逻辑读,而直接从索引获得数据,走index full scan效率会更高,我们做如下测试,创建组合索引created字段放在前面。


组合索引


SCOTT@orcl1>create index idx_com_test_obj on test_obj(created desc,object_name,owner,status,namespace);


Index created.

再次观察执行计划和逻辑读

 

谓词和统计信息

Predicate Information (identified by operation id):

---------------------------------------------------


   1 - filter(ROWNUM<50)



Statistics

----------------------------------------------------------

          0  recursive calls

          0  db block gets

          6  consistent gets

          0  physical reads

          0  redo size

       2299  bytes sent via SQL*Net to client

        557  bytes received via SQL*Net from client

          5  SQL*Net roundtrips to/from client

          0  sorts (memory)

          0  sorts (disk)

         49  rows processed

从执行计划看步骤3走此时走index full scan此时消除了回表,只有6个逻辑读,由于数据都在索引中,这个组合索引类似于瘦表从而减少逻辑读。

总结:索引有序消除全表扫描,满足排序和分页要求,根据查询需求进一步通过组合索引消除索引回表的逻辑读开销,进一步提高SQL的执行效率。


相关文章