Oracle Acs资深顾问罗敏 老罗技术核心感悟:自动扫描SQL语句工具?

作者为: 

SHOUG成员 – ORACLE ACS高级顾问罗敏

 

 

  1. 问题和需求

“你们Oracle公司有这样的自动扫描SQL语句工具吗?通过这个工具,把我们的应用软件输进去,就能扫出SQL语句的大部分问题。这样就可以减少我们测试和性能优化工作量,更能避免投产之后才暴露性能问题。” — 来自某移动客户的需求。

“老罗,XX移动公司希望我们Oracle公司提供自动扫描SQL工具,我们有吗?听说第三方公司有这样的产品,已经在客户那儿试用了。” — 来自Oracle服务销售同事的担忧。

是啊,客户的需求再合理不过。但据我所知,Oracle公司好像没有这样包治百病的神奇工具。第三方公司居然有这样的工具,太吸引客户眼球了,一方面让人感到质疑,另一方面也令人感到一种竞争压力。

 

  1. 初识庐山真面目

于是,我和销售同事趁去该客户现场拜访、调研的机会,对该客户的上述需求和第三方公司的自动化工具一探究竟了。客户的需求不必多言了,我们关键是对所谓自动化工具充满好奇。因商务因素,客户并没有给我们直接展示该工具的使用过程和界面,但告诉我们大致原理:原来该工具首先通过定义一组评分规则,例如:SQL语句是否使用绑定变量;条件字段前是否有函数;多表连接是否超过4个表… …,然后将输入的SQL语句进行评判,若违反这些规则,扣分!最后给该SQL语句和整个应用模块打分。

原来如此!这些规则在大部分情况下不无道理,例如,条件字段加函数,特别是在日期字段前加to_char函数:

to_char(DJ_SZ.JDRQ, ‘YYYY.MM.DD’) BETWEEN ‘2014.04.01’ AND ‘2014.04.17’

就是一种非常初级、业余、错误的编程方式。正确方式应该是:

DJ_SZ.JDRQ BETWEEN to_date(‘2014.04.01’,’YYYY.MM.DD’) AND to_date(‘2014.04.17’,’YYYY.MM.DD’)

但是,更多的规则值得商榷。例如,在Oracle公司推荐的编程规范中,并不是所有SQL语句都应该使用绑定变量的,而只是针对并发量大的小事务SQL语句才应该使用绑定变量,而针对并发量小的大事务SQL语句,特别是非常复杂SQL语句,Oracle公司建议是不要使用绑定变量。第三方的自动工具能分析出SQL语句是高并发量还是低并发量访问,以及大事务和小事务吗?值得怀疑。

更为典型的例子是,其实Oracle公司从来没有官方正式建议:一个SQL语句不能超过4个表的连接。的确,多表连接可能导致性能不佳,但问题不在于连接表的多和少,而在于编程人员是否理解了Oracle的Nested Loop、Hash Join等多种表连接技术原理和适应场景,以及在表连接中索引的设计原理。以下就是一个国内著名财务软件的典型SQL语句:

select *

from (select rownum num, temp.*

from (select a.fid,

… …

a.playdeptname as playdeptNameCode

from t_claim_remittancerecord a

left join t_pay_remittype b on a.remittype = b.fid

left join t_pay_fundtype c on c.fid = a.amountscategory

left join t_org_department d on a.remitdepart =

d.finasyscode

left join t_org_department y on a.playdeptname =

y.finasyscode

and y.status = 1

left join t_org_employee f on f.empcode = a.addperson

left join t_org_department k on f.deptid = k.id

left join t_org_employee g on g.empcode = a.updateperson

left join t_org_employee h on h.empcode = a.claimman

left join t_bd_customer cus on cus.fnumber = a.customer

left join V_LMS_SUPPLIER s on s.snumber = a.supplier

left join t_deposit_printer i on i.codenum = a.codenum

left join t_org_employee j on i.createuser = j.empcode

WHERE 1 = 1

and a.accountName like ‘%’ || :1 || ‘%’

and a.claimState like ‘%’ || :2 || ‘%’

and a.writeOffState like ‘%’ || :3 || ‘%’

and a.reachAmountDate between :4 and :5

and a.repealstate != 1

order by addTime desc, codeNum) temp) t

WHERE t.num <= :6

and t.num > :7

 

哇!该语句好复杂哦,连接的表多达10多个。若采用第三方公司的SQL自动扫描工具。该语句一定被扣分甚至彻底枪毙了。可是,该语句实际运行情况如何呢?以下就是该语句的执行计划:

———————————————————————————————| Id  | Operation                                     | Name                      | Cost (%CPU)|

———————————————————————————————|   0 | SELECT STATEMENT                              |                           |    14 (100)|

|   1 |  FILTER                                       |                           |            |

|   2 |   VIEW                                        |                           |    14   (8)|

|   3 |    COUNT                                      |                           |            |

|   4 |     VIEW                                      |                           |    14   (8)|

|   5 |      SORT ORDER BY                            |                           |    14   (8)|

|   6 |       FILTER                                  |                           |            |

|   7 |        NESTED LOOPS OUTER                     |                           |    13   (0)|

|   8 |         NESTED LOOPS OUTER                    |                           |    12   (0)|

|   9 |          NESTED LOOPS OUTER                   |                           |    11   (0)|

|  10 |           NESTED LOOPS OUTER                  |                           |    10   (0)|

|  11 |            NESTED LOOPS OUTER                 |                           |     9   (0)|

|  12 |             NESTED LOOPS OUTER                |                           |     8   (0)|

|  13 |              NESTED LOOPS OUTER               |                           |     7   (0)|

|  14 |               NESTED LOOPS OUTER              |                           |     6   (0)|

|  15 |                NESTED LOOPS OUTER             |                           |     5   (0)|

|  16 |                 NESTED LOOPS OUTER            |                           |     4   (0)|

|  17 |                  NESTED LOOPS OUTER           |                           |     3   (0)|

|  18 |                   NESTED LOOPS OUTER          |                           |     2   (0)|

|  19 |                    TABLE ACCESS BY INDEX ROWID| T_CLAIM_REMITTANCERECORD  |     1   (0)|

|  20 |                     INDEX RANGE SCAN          | IDX_TCR                   |     1   (0)|

|  21 |                    TABLE ACCESS BY INDEX ROWID| T_PAY_REMITTYPE           |     1   (0)|

|  22 |                     INDEX UNIQUE SCAN         | PK_REMITTYPE_FID          |     1   (0)|

|  23 |                   TABLE ACCESS BY INDEX ROWID | T_PAY_FUNDTYPE            |     1   (0)|

|  24 |                    INDEX UNIQUE SCAN          | PK_FUNDTYPE_FID           |     1   (0)|

|  25 |                  TABLE ACCESS BY INDEX ROWID  | T_DEPOSIT_PRINTER         |     1   (0)|

|  26 |                   INDEX UNIQUE SCAN           | PK_T_DEPOSIT_PRINTER      |     1   (0)|

|  27 |                 TABLE ACCESS BY INDEX ROWID   | T_BD_SUPPLIER             |     1   (0)|

|  28 |                  INDEX RANGE SCAN             | IDX_BD_SUPPLIER_NUM       |     1   (0)|

|  29 |                TABLE ACCESS BY INDEX ROWID    | T_ORG_DEPARTMENT          |     1   (0)|

|  30 |                 INDEX RANGE SCAN              | IDX_T_ORG_DPT_FINASYSCODE |     1   (0)|

|  31 |               TABLE ACCESS BY INDEX ROWID     | T_ORG_DEPARTMENT          |     1   (0)|

|  32 |                INDEX RANGE SCAN               | IDX_T_ORG_DPT_FINASYSCODE |     1   (0)|

|  33 |              TABLE ACCESS BY INDEX ROWID      | T_BD_CUSTOMER             |     1   (0)|

|  34 |               INDEX RANGE SCAN                | IDX_BD_CUSTOMER_NUM       |     1   (0)|

|  35 |             TABLE ACCESS BY INDEX ROWID       | T_ORG_EMPLOYEE            |     1   (0)|

|  36 |              INDEX UNIQUE SCAN                | UK_EMPLOYEE_EMPCODE       |     1   (0)|

|  37 |            TABLE ACCESS BY INDEX ROWID        | T_ORG_DEPARTMENT          |     1   (0)|

|  38 |             INDEX UNIQUE SCAN                 | SYS_C00797036             |     1   (0)|

|  39 |           TABLE ACCESS BY INDEX ROWID         | T_ORG_EMPLOYEE            |     1   (0)|

|  40 |            INDEX UNIQUE SCAN                  | UK_EMPLOYEE_EMPCODE       |     1   (0)|

|  41 |          TABLE ACCESS BY INDEX ROWID          | T_ORG_EMPLOYEE            |     1   (0)|

|  42 |           INDEX UNIQUE SCAN                   | UK_EMPLOYEE_EMPCODE       |     1   (0)|

|  43 |         TABLE ACCESS BY INDEX ROWID           | T_ORG_EMPLOYEE            |     1   (0)|

|  44 |          INDEX UNIQUE SCAN                    | UK_EMPLOYEE_EMPCODE       |     1   (0)|

 

 

大家看到上述执行计划,首先不应感到畏惧,而应该从外观上感慨一下,那就是数据库的美感!大家看这个执行计划的形状多么对称和富有韵律感,也多像一把打开的美丽扇子。其次,大家一定要相信,外观充满美感的东西,本质上也应该不错,呵呵。的确,回到技术本质,我们发现虽然该语句涉及10多张表的连接,但实际运行效率效果非常高,例如Cost才14,当然Cost有不准确的时候。更重要的是,该语句每次表连接都非常漂亮地采用了Nested Loop连接技术,并且都合理地采用了被连接字段的索引。正是因为设计开发人员非常了解Oracle表连接原理以及索引设计规范,所以才设计出了这样“又好吃、又好看”的SQL语句。

可是,第三方公司的SQL自动扫描工具却很可能滥杀无辜了。大家一定能相信一个原理:世界上一件事物的好坏不在于多和少,而在于其本身的对和错。若将此原理运用在多表连接技术方面,那就是:多表连接的好坏不在于连接表的多和少,而在于每次表连接的对和错。因为Oracle表连接每次都是两个表进行连接,然后再进行第三个、第四个表的连接。若充分理解了Oracle各种表连接技术、索引设计规范等,每次表连接都是高效的,再多的表连接也是合理的。反过来,若不了解Oracle表连接技术和适应场景,即便是两个表的连接都会出问题。

 

  1. 少一点噱头,多一点务实

此标题有点刺耳,甚至刻薄,但的确是本人有感而发。国内IT市场也的确存在这种不太正常现象:面对客户某些看似合理,实则很难实现的需求,某些公司不是去合理引导客户,反而是一味迎合客户,甚至是推波助澜,更实质的目的还是出于商业考虑。但是,大家不知这是一种非常短视的行为吗?难道客户不会很快就验证出这种所谓SQL自动扫描工具的有效性,甚至真伪性吗?既然如此,大家何必去费尽心机,去讨客户这种“好”?实际上很可能是既让客户失望,也毁自己声誉的事情。

性能分析和优化,特别是SQL语句性能分析和优化,怎么可能只做静态的形式分析?而不做与实际系统和数据相关联的动态神式分析?记得有一年参加一个数据库技术大会,一位国外性能优化大师的演讲曾经让我非常震撼,他的演讲主题是性能优化与应用数据的关联性,整个演讲中,他未展现一个SQL语句优化技术,而是大谈数据分布对SQL语句访问性能的重要性,诸如按字段分析最大值、最小值、分组统计等,以及何时需要按Bucket方式收集统计信息等。所谓的SQL语句自动扫描工具,可能连客户实际系统都不连接,执行计划也不分析,客户数据更不了解,就能扫描出SQL语句质量?的确有点像个乌托邦的东西。

再回到本文开头一个问题:“你们Oracle公司有这样的自动扫描SQL语句工具吗?”准确地回答是:Oracle的确没有这种不看数据、不看执行计划的所谓自动扫描SQL语句工具,但Oracle公司自10g开始就提供了大量内置的SQL优化工具,例如:ADDM、SQL Access Advisor、SQL Tuning Advisor、Automatic SQL Tuning、SQL Profile、SPA(SQL Performance Analyzer)、SPM(SQL Plan Management)… …这些工具一个共同特点是不仅分析SQL语句执行计划,而且分析统计信息,分析数据分布情况,分析索引设计情况等,综合各方面情况,给出一些更合理的SQL语句优化建议。例如,11g的Automatic SQL Tuning就是分析SQL语句所访问表的统计信息是否过期、是否缺乏索引、是否可产生有效的SQL Profile信息、语句编写是否合理等。

再者,虽然Oracle自动优化工具能有效分析和解决很多SQL性能问题,但更多基础性,特别是与应用数据紧密相关的问题,还是需要应用设计开发人员从数据库模型规范化设计、 基础技术掌握、SQL设计开发规范、应用软件质量控制、加强设计开发管理等层面和角度去加以解决。

总之,性能优化工作,特别是应用性能分析和优化工作,还是需要大家踏踏实实、一点一滴地做起,即便需要所谓自动化的工具,也建议大家优先考虑Oracle公司本身自带的工具,毕竟这些工具是Oracle产品的一部分,经过了严格测试,也为全球广大客户的大量实践所验证,是具有普遍适用性的东西。

还是以本节标题作为本文结尾:

少一点噱头,多一点务实!

Comment

*

沪ICP备14014813号-2

沪公网安备 31010802001379号