导读:Oracle Code 2019 开发者大会于4月16日在深圳举行,这是甲骨文公司在中国举办的第三届Oracle Code 开发者大会。大会主要面向IT架构师和开发人员,旨在帮助开发人员深入了解最新的开发技术、开发实践和未来发展的趋势。

云和恩墨CTO、Oracle ACE Director杨廷琨受邀参加本次大会,并于会上发表主题演讲《高效SQL语句编写》,与大家分享了高效SQL语句编写的种种技巧。没有机会亲临现场?跟随本文一起学习吧!(演讲PPT点击阅读原文获取)

在工作中高效的SQL是非常必要的。

SQL是世界上第二大的编程语言。超过50%的开发者使用SQL;80%的数据库问题是SQL引起的;80%的性能问题来自20%的SQL语句;高并发环境中,单条SQL语句可能导致整个数据库出现性能故障。

本次分享主要包括四个部分:

  • 合理运用新特性

  • 数据集整体处理

  • 设计SQL执行计划

  • 严格过滤数据

让我们逐一展开,一起编写高效的SQL。

合理运用新特性

比如我们考虑这种场景。

从一张表取数据插入到另一张表中,此外需要为插入的目标表做一个应用级的日志表,也就是说在插入目标表的同时,还需要将相同的数据插入到日志表中。

一般情况下,我们有以下几种选择:

  • CREATE TRIGGER

  • DOUBLE INSERT

  • OPEN CURSOR

  • BULK INTO VARIABLE

以上几种方式各有各的缺点。比如采用CREATE TRIGGER方式:

CREATE OR REPLACE TRIGGER T_INSERT_TAR
AFTER INSERT ON T_TAR
FOR EACH ROW
BEGIN
INSERT INTO T_LOG (ID, NAME)
VALUES (:NEW.ID, :NEW.NAME);
END;

不仅太“重”,而且实现与需求有差异,还增加后续维护成本,触发器效率较低。

其它三种方式也逃不出太过复杂,或者效率低的弊端。

而当我们使用新特性:INSERT ALL 时:

INSERT ALL INTO T_TAR (ID, NAME)
INTO T_LOG (ID, NAME)
SELECT ID, NAME FROM T_ORG;

这些问题就迎刃而解了。

合理地运用新特性,对高效SQL的编写大有裨益。

数据集整体处理

举个例子。

判断表空间是否自动扩展时,我们可以采用以下方式:

select distinct tablespace_name, autoextensible
from DBA_DATA_FILES
where autoextensible = 'YES'
union
select distinct tablespace_name, autoextensible
from DBA_DATA_FILES
where autoextensible = 'NO'
and tablespace_name not in
(select distinct tablespace_name
from DBA_DATA_FILES
where autoextensible = 'YES');

得出结果:

TABLESPACE_NAME AUT
--------------- ---
SYSAUX          YES
SYSTEM          YES
TEST            NO
UNDOTBS1        YES
USERS           YES

优点:思路清晰

缺点:效率低效,冗余严重

  • 三次扫描DBA_DATA_FILES视图

  • 包含三次DISTINCT操作

  • 包含UNION数据集操作

  • 包含NOT IN子查询

我们可以得出正确的结论,然而这显然不是我们想要的高效SQL。如果采取“数据集整体处理“的思维来编写,我们可以写出以下SQL:

SELECT TABLESPACE_NAME, MAX(AUTOEXTENSIBLE)
FROM DBA_DATA_FILES
GROUP BY TABLESPACE_NAME;

可以得出相似的结论。

TABLESPACE_NAME MAX
--------------- ---
SYSAUX          YES
SYSTEM          YES
TEST            NO
UNDOTBS1        YES
USERS           YES

再举个例子。

问题:在5X5的方格棋盘中(如图),每行、列、斜线(斜线不仅仅包 括对角线)最多可以放两个球,如何摆放才能放置最多的球,这样的摆 法总共有几种?输出所有的摆法。

要求:用一句SQL实现。

输出格式:从方格棋盘第一行至第5行,每行从第一列到第5列依次输出, 0表示不放球,1表示放球。

例如:10010。一行输出一个行号和一个解,按解所在的列字符串顺序从大到小排序。

假设每行放两个球,首先构造出行的10种情况:

SQL> with c as
(select rownum - 1 c from dual connect by rownum <= 2),
line_seed as
(select c1.c || c2.c || c3.c || c4.c || c5.c line,
c1.c c1, c2.c c2, c3.c c3, c4.c c4, c5.c c5
from c c1,c c2,c c3,c c4,c c5
where c1.c+c2.c+c3.c+c4.c+c5.c=2
order by 1 desc)
select * from line_seed;
LINE     C1    C2    C3    C4    C5
----- ----- ----- ----- ----- -----
11000     1     1     0     0     0
10100     1     0     1     0     0
10010     1     0     0     1     0
10001     1     0     0     0     1
01100     0     1     1     0     0
01010     0     1     0     1     0
01001     0     1     0     0     1
00110     0     0     1     1     0
00101     0     0     1     0     1
00011     0     0     0     1     1

如果按照每个条件分开处理的SQL如下:

with c as
(select rownum - 1 c from dual connect by rownum <= 2),
line_seed as
(select c1.c || c2.c || c3.c || c4.c || c5.c line, c1.c c1, c2.c c2, c3.c c3, c4.c c4, c5.c c5 from c c1, c c2, c c3, c c4, c c5
where c1.c + c2.c + c3.c + c4.c + c5.c = 2
order by 1 desc)
select rownum, line1.line || line2.line || line3.line || line4.line || line5.line result
from line_seed line1, line_seed line2, line_seed line3, line_seed line4, line_seed line5 where line1.c1 + line2.c1 + line3.c1 + line4.c1 + line5.c1 = 2
and line1.c2 + line2.c2 + line3.c2 + line4.c2 + line5.c2 = 2
and line1.c3 + line2.c3 + line3.c3 + line4.c3 + line5.c3 = 2
and line1.c4 + line2.c4 + line3.c4 + line4.c4 + line5.c4 = 2
and line1.c5 + line2.c5 + line3.c5 + line4.c5 + line5.c5 = 2
and line3.c1 + line4.c2 + line5.c3 <= 2
and line2.c1 + line3.c2 + line4.c3 + line5.c4 <= 2
and line1.c1 + line2.c2 + line3.c3 + line4.c4 + line5.c5 <= 2
and line1.c2 + line2.c3 + line3.c4 + line4.c5 <= 2
and line1.c3 + line2.c4 + line3.c5 <= 2
and line1.c3 + line2.c2 + line3.c1 <= 2
and line1.c4 + line2.c3 + line3.c2 + line4.c1 <= 2
and line1.c5 + line2.c4 + line3.c3 + line4.c2 + line5.c1 <= 2
and line2.c5 + line3.c4 + line4.c3 + line5.c2 <= 2
and line3.c5 + line4.c4 + line5.c3 <= 2;

怎么样根据条件对数据集整体处理呢?

with c as
(select rownum - 1 c from dual connect by rownum <= 2),
lines as
(select to_number(c1.c || c2.c || c3.c || c4.c || c5.c) line
from c c1, c c2, c c3, c c4, c c5
where c1.c + c2.c + c3.c + c4.c + c5.c
order by 1 desc)
select ltrim(to_char(line1.line, '09999')) || chr(10)|| ltrim(to_char(line2.line, '09999')) || chr(10)|| ltrim(to_char(line3.line, '09999')) || chr(10) || ltrim(to_char(line4.line, '09999')) || chr(10) || ltrim(to_char(line5.line, '00009')) result
from lines line1, lines line2, lines line3, lines line4, lines line5
where line1.line + line2.line + line3.line + line4.line + line5.line = 22222
and ltrim(to_char(line1.line + 10*line2.line + 100*line3.line + 1000*line4.line + 10000*line5.line), '012') is null
and ltrim(to_char(10000*line1.line + 1000*line2.line + 100*line3.line + 10*line4.line + line5.line), '012') is null
and rownum= 1;

大家可以思考一下。点击“阅读原文”,PPT中将给出详细解析。

我们建议:

  • 能够用单条SQL处理的,就不要使用多条;

  • 能够只扫描一次的,就不要扫描多次;

  • 从整体上处理数据,避免单条处理逻辑。

设计SQL执行计划

客户某SQL语句执行缓慢,消耗大量逻辑读:

SQL> EXPLAIN PLAN FOR
SELECT VCM.POL_ID, TC.MOBILE, TC.FIRST_NAME PH_NAME,
PKG_UTILS.F_GET_DESC('T_TITLE', DECODE(TC.GENDER,'M', '1', 'F', '2'), '211'),
PKG_UTILS.F_GET_DESC('V_PRO_LIFE_3',
(SELECT T.PROD_ID FROM T_CON_PROD T WHERE T.POL_ID = VCM.POL_ID AND T.MASTER_ID IS NULL), '211'),
SUM(TPA.FEE_AMOUNT), VCM.POLICY_CODE, PKG_UTILS.F_GET_DESC('T_LIA_STAT',VCM.LIA_STATE, '211'),
PKG_UTILS.F_GET_DESC('T_SAL_CHAN', VCM.CHANN_TYPE, '211'), VCM.CHANN_TYPE,TC.CUSTOMER_ID, TCO.ORGAN_CODE
FROM V_CON_MAS VCM, T_CON_MAS TCM, T_AGENT TA, T_CUSTOMER TC,
T_POH VPH,T_COM_ORG TCO, T_P_A TPA
WHERE VCM.POL_ID = VPH.POL_ID
AND VCM.SERVICE_AGENT = TA.AGENT_ID
AND VCM.POL_ID = TCM.POL_ID
AND VPH.PARTY_ID = TC.CUSTOMER_ID
AND VCM.ORGAN_ID = TCO.ORGAN_ID
AND VCM.POL_ID = TPA.POL_ID
AND VCM.LIABILITY_STATE = 1
AND TPA.FEE_TYPE IN (41, 569)
AND TPA.FEE_STATUS <> 2
AND (EXISTS (
SELECT 1 FROM T_PO_CH T, T_CH TC WHERE T.MAS_C_ID = TC.CH_ID AND TC.CH_ID = TPA.CH_ID AND T.SERV_ID = 3 ))
AND TPA.DUE_TIME >= (TRUNC(:B1 ) + 7)
AND TPA.DUE_TIME < (TRUNC(:B1 ) + 8)
AND REGEXP_LIKE(TRIM(TC.MOBILE), '^\d{11}$')
AND NOT EXISTS (
SELECT 1 FROM T_DATA_EXT SDE, T_DATA TSD
WHERE SDE.DATA_ID = TSD.DATA_ID AND SDE.RELAT_VALUE_1 = VCM.POL_ID AND TSD.SMS_ID = 12 AND SDE.RELAT_VALUE_2 = TO_CHAR(:B1 , 'yyyy-MM-dd'))
GROUP BY VCM.POL_ID, TC.MOBILE, TC.FIRST_NAME, TC.GENDER, VCM.POLICY_CODE, VCM.LIABILITY_STATE, VCM.CHANNEL_TYPE, TC.CUSTOMER_ID, TCO.ORGAN_CODE;

执行计划如下:

让我们来重现这个问题:

SQL> create table t_objects as select * from dba_objects; 表已创建。
SQL> create index ind_obj_id on t_objects(object_id); 索引已创建。
SQL> create table t_tab as select * from dba_tables; 表已创建。
SQL> create table t_ind as select * from dba_indexes; 表已创建。
SQL> create index ind_tab_name on t_tab(table_name); 索引已创建。
SQL> create index ind_ind_name on t_ind(index_name); 索引已创建。
SQL> create view v_seg as
select owner, table_name, tablespace_name, blocks from t_tab where temporary = 'N'
union all
select owner, index_name, tablespace_name, num_rows from t_ind where status = 'N/A';
视图已创建。

通过hint走不同的执行计划,资源消耗大相径庭。

select /*+ index(t_tab ind_tab_name) */ obj.owner, obj.object_name, created, v.blocks
from t_objects obj, v_seg v
where obj.object_id = 12345
and obj.object_name = v.table_name;

select /*+ index(v.t_tab ind_tab_name) */ obj.owner, obj.object_name, created, v.blocks
from t_objects obj, v_seg v
where obj.object_id = 12345
and obj.object_name = v.table_name;

select /*+ push_pred(v) */ obj.owner, obj.object_name, created, v.blocks
from t_objects obj, v_seg v
where obj.object_id = 12345
and obj.object_name = v.table_name;

由此可见,好的性能是设计出来的,因此如果想写出高效的SQL语句,要从一开始就考虑好这个SQL的执行计划,驱动表是谁,采用何种JOIN方式连接到被驱动表。设计思路是一方面,另一方面是保证执行计划符合设计思路,这时候就需要用到提示的功能。熟悉提示的功能,可以更好的控制SQL的执行路径,绕过bug或性能问题,强制SQL按照设计思路去执行。

严格过滤数据

先来看一个简单的问题。找出所有100以内的质数。

SQL> WITH T
AS
(SELECT ROWNUM RN FROM DUAL CONNECT BY LEVEL < 100)
SELECT RN FROM T
WHERE RN>1
MINUS
SELECT A.RN*B.RN FROM T A,T B
WHERE A.RN <= B.RN
AND A.RN>1
AND B.RN>1;RN
------2 3 5
.
.
.97
已用时间: 00: 00: 00.07

执行时间并不长。

那么10000以内的质数呢?

SQL> WITH T
AS
(SELECT ROWNUM RN FROM DUAL CONNECT BY LEVEL < 10000)
SELECT RN FROM T
WHERERN>1
MINUS
SELECT A.RN*B.RN FROM T A,T B
WHERE A.RN <= B.RN
AND A.RN>1
AND B.RN>1;已选择 1229 行。
已用时间: 00: 01: 56.88统计信息
----------------------------------------------------------521  recursive calls 1702  db block gets180013  consistent gets65297  physical reads 964 redo size22473  bytes sent via SQL*Net to client1442  bytes received via SQL*Net from client83  SQL*Net roundtrips to/from client 2  sorts (memory)1  sorts (disk) 1229  rows processed

近两分钟的执行时间。我们能否用数据过滤的思维来优化呢?

SQL> WITH T
AS
(SELECT ROWNUM RN FROM DUAL CONNECT BY LEVEL < 10000)
SELECT RN FROM T
WHERE RN > 1
MINUS
SELECT A.RN * B.RN FROM T A, T B
WHERE A.RN <= B.RN
AND A.RN > 1
AND A.RN <= 100
AND B.RN > 1
AND B.RN <= 5000;已选择 1229 行。
已用时间: 00: 00: 00.62统计信息
----------------------------------------------------------10  recursive calls23  db block gets1831  consistent gets16  physical reads624  redo size22473  bytes sent via SQL*Net to client1442  bytes received via SQL*Net from client83  SQL*Net roundtrips to/from client 3 sorts (memory)0  sorts (disk)1229  rows processed

优化效果十分显著。通过更严格的筛选,可以对这个SQL进一步进行优化。大家可以仔细思考,再参考PPT中的内容。

留下一道思考题:

总结

高效SQL的编写有许多技巧,如果能够遵循以下原则,定能起到事半功倍的效果:

  • 处理问题从整体上考虑,避免单条操作

  • 第一步结果集最小原则,合理选择驱动表

  • 利用新特性、分析函数避免重复多次的扫描,减少自关联

  • 在每一个步骤上尽可能过滤掉无用数据

  • 多写SQL:熟能生巧

  • 多思考:算法为王

  • 持之以恒:优化无止境

整理丨陈一宁 (阿文、haha、坤楠对此文亦有贡献)


云和恩墨自主研发的SQL审核软件SQM,能自动抓取开发、测试与生产环境数据库中的对象设计与SQL信息,并依据既定的审核规则,对这些信息进行分析。

分析对象设计与SQL中的潜在性能⻛险,使得DBA和应用开发人员能够较早的介入,将性能隐患扼杀于萌芽阶段,确保线上应用的稳定、高效运行。

专家指导+软件支持,云和恩墨愿为您的数据库保驾护航!

产品咨询:marketing@enmotech.com

干货分享丨高效SQL语句编写-Oracle Code 2019中云和恩墨CTO杨廷琨的分享相关推荐

  1. 像用Oracle一样用openGauss - 云和恩墨开源项目compat-tools介绍

    点击上方"蓝字" 关注我们,享更多干货! 近年来,随着国产数据库势如破竹般地快速发展,作为开源世界新生力量的openGauss亦得到了市场的青睐,尤其在云和恩墨推出的商业发行版Mo ...

  2. 杨廷琨Oracle Code大会分享:如何编写高效SQL(含PPT)

    快捷方式:关注本公众号,回复:yangtingkun ,即可获得大会PPT下载. 2018 Oracle Code 于5月17日在新加坡拉开帷幕.作为全球开发者交流分享的年度盛会,为吸引所有领域的开发 ...

  3. 电子刊丨云和恩墨大讲堂电子刊2019年5月刊

    点击阅读原文,立刻下载云和恩墨大讲堂月刊2019年5月刊 这个我们生活在技术不断快速迭代的时代,虽然这是令人兴奋的,可也是让从事技术的我们比较心累,担心被淘汰掉,毕竟追赶上技术的脚步并不那么容易.笔者 ...

  4. 行研报告解读+落地实践分享,云和恩墨专家参与AI4DB专题研讨(附视频回放 PPT)...

    "2022数据库大咖讲坛(第3期)"直播活动于11月30日下午圆满结束.云和恩墨副总经理熊军和墨天轮负责人章芋文,与来自openGauss的李国良教授.信通院的刘思源.阿里云的胡航 ...

  5. 聚数·智赢未来丨云和恩墨2020夏季产品发布会要点回顾

    点击上方蓝字关注我们,了解更多精彩! 6月10日,云和恩墨2020年夏季产品发布会如约而至.本次发布会以"聚数·智赢未来"为主题,立足DT时代,集中展示了云和恩墨围绕数据为客户提供 ...

  6. 云和恩墨数据库一体机助力天安人寿 zData架构高效运行

    zData Light 是基于x86架构的高性能数据库服务器整体解决方案(一体机),基于自主研发的Light Storage 软件,将服务器.PCle闪存卡.高速互联设备完美集成,结合RAC架构深度优 ...

  7. 云和恩墨 | 全面保障某省移动核心系统高效运行

    中国移动某省分公司是属于中国移动通信集团的分公司,公司下辖约20个市县分公司,在多年的发展和建设中,该公司已建成全省覆盖最广.容量最大.技术先进.质量卓越的移动通信精品网络,市.县.乡镇.行政村覆盖率 ...

  8. oracle坏块 戴明明,云和恩墨:基于PCIE 闪存卡的 Oracle 数据

    原标题:云和恩墨:基于PCIE 闪存卡的 Oracle 数据 戴明明 宝存科技数据库方案架构师 本文来自于本周四云和恩墨大讲堂戴明明(Dave)的分享内容:基于 PCIE 闪存卡的Oracle 数据库 ...

  9. 云和恩墨做的是oracle,云和恩墨张中靖:立足Oracle技术,顺应去IOE大势 原创

    [钉科技报道]互联网与传统产业的结合,带来了新的活力.不仅企业的业务模式正在发生变化,技术架构也在不断的迭代和完善,适应新需求.当然,作为系统运维的工程师,以及服务于企业运维的企业,同样需要紧跟时代的 ...

最新文章

  1. Script标签的async和defer
  2. Python自定义:粒子群优化算法
  3. android listview下拉动画效果,Android开发中利用ListView实现一个渐变式的下拉刷新动画...
  4. (84)多周期路径约束基础
  5. Java关键字fymd,JAVA程式碼一直被foritfy檢出有path manipulation
  6. java.lang.NumberFormatException: For input string: 0.7
  7. 数字证书的基础知识(转)
  8. linux vim编辑矩阵乘,VIM使用系列:二进制文件编辑
  9. 【开发模板】Vue和SpringBoot的前后端分离开发模板
  10. 翻转课堂计算机语言逻辑性,通达翻转课堂模式下大学数学教学模式的探讨
  11. java.lang.ClassNotFoundException: org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEnd
  12. JavaScript小白基础学习教程
  13. 网易资深安卓架构师:2021年Android常见面试题,面试必问
  14. CSS+DIV设计导航条源代码
  15. 文字翻译软件-文字批量翻译转换器免费
  16. Linux 管道操作符详解
  17. 洛谷 P3258 [JLOI2014]松鼠的新家 树上差分
  18. PHP 微信公众号真正正确的客服头像上传
  19. 将excel表格分割成多个
  20. java no cache_从浏览器的Disable cache谈起

热门文章

  1. Beautifulsoup4库提取数据详解
  2. gh0st 内核代码分析
  3. Linux环境变量设置
  4. docker容器部署wordpress,使用原有nginx代理并开启https
  5. DM365 arm_v5t_le编译器jrtplib头文件和库放置位置
  6. 虚拟机linux怎么插u盘,Linux虚拟机怎么挂载U盘
  7. 微软不再支持XP 韩国怒了!
  8. rgb转16进制代码
  9. 概率密度与概率的区别
  10. Daily question 1