复杂SQL性能优化的剖析(一)(r11笔记第36天)

今天本来是处理一个简单的故障,但是发现是一环套一环,花了我快一天的时间。

开始是早上收到一条报警:

报警内容: CPUutilization is too high
------------------------------------
报警级别: PROBLEM
------------------------------------
监控项目: CPU idle time:59.94 %
------------------------------------
报警时间:2017.01.06-06:35:22开始也没有在意,准备花几分钟处理完事,但是发现这个坑越来越大。对于问题的处理,我觉得还是能够刨坑问底,你能说服自己,弄明白了,碰到类似的问题就会得心应手。

    我发现数据库的负载有了较大的提升,查看快照级别的DB time负载如下:

BEGIN_SNAP   END_SNAP SNAPDATE            DURATION_MINS     DBTIME
---------- ---------- --------------------------------- ----------
     19028      19029 06 Jan 2017 00:00              60        104
     19029      19030 06 Jan 2017 01:00              60        233
     19030      19031 06 Jan 2017 02:00              60        331
     19031      19032 06 Jan 2017 03:00              60        409
     19032      19033 06 Jan 2017 04:00              60        465
     19033      19034 06 Jan 2017 05:00              60        513
     19034      19035 06 Jan 2017 06:00              60        538
     19035      19036 06 Jan 2017 07:00              59        591
     19036      19037 06 Jan 2017 08:00              60        614
     19037      19038 06 Jan 2017 09:00              60        622
     19038      19039 06 Jan 2017 10:00              60        665
这个情况不容乐观,很快就定位到是一个SQL语句引起的。

可以看到语句的执行计划发生了改变,本来执行2秒的语句,现在执行近5000多秒。这个差距实在是有些大了。

语句的结构如下:

merge into xxxx using

(
select xxxx from h1_first_dev d, ua_td_active_log a
where d.dt = to_date(:1, 'yyyy-mm-dd')
and a.dt = :2
and d.deviceid is not null
and (d.deviceid = a.idfa or d.deviceid = a.mac or
a.idfa =lower(utl_raw.cast_to_raw(dbms_obfuscation_toolkit.md5(input_string => d.deviceid))))
...
union
select xxxx from h1_first_dev d, ua_td_active_log a
where d.dt = to_date(:1, 'yyyy-mm-dd')
and a.dt = :2
and d.deviceid is not null
and (d.deviceid = a.idfa or d.deviceid = a.mac or
and (a.idfa =lower(utl_raw.cast_to_raw(dbms_obfuscation_toolkit.md5(input_string => nvl(case when instr(d.deviceid, ':') > 0 then replace(substr(substr(d.deviceid, 19, 15), 1, 15), chr(2), '') else replace(substr(d.deviceid, 1, 15), chr(2), '') end, 'null')))))
....
)  

on(xxxx)  when matched xxx

看起来语句结构也蛮复杂,而且调用了几个大家平时很少见到的包,这个时候就有两个问题需要解释清楚。

  1. 之前为什么没有这个问题
  2. 问题的解决方法是什么

查看数据库层面的活跃会话情况,可以看到有大量的会话被阻塞了,而且看阻塞的频率,语句大概是10分钟执行一次。


我对比了变化前后的执行计划情况。

一个重要的变化是两个执行计划的表关联方式不同,左边的效率较差,使用了Nested Loop Join,右边的部分效率较高,使用了Hash Join


当然语句的执行计划很长,我点到为止,重点是如果单单是表关联方式发生变化,这个肯定说明不了Nested Loop Join比Hash Join要好。

简单总结了一下索引的扫描方式,也有了不同的结果。

IDX_H1_FIRST_DT_DEV_DEVICEID(Nested Loop Join)  使用了index range scan
IDX_H1_FIRST_DEV_APPKEY_DT(Hash Join)  使用了 index skip scan

不过这个地方我排查了直方图等信息,没有发现异常,对于NL Join和Hash Join,我可以简单通过如下的数据来论证。

 SQL> select count(*)from mbi.h1_first_dev where dt between sysdate-1 and sysdate;
  COUNT(*)
----------
      3330

所以说这种的情况下,NL Join是完全没有问题的,支持绰绰有余。

那么这个问题我们怎么分析呢,我们分而治之。里面有几个子查询,我们可以拆开来看。

在子查询的执行计划中,我竟然看到了“MERGE JOIN CARTESIAN”的字样。

哪里来的笛卡尔积?

我拿出一个子查询来解释。

select xxxx from h1_first_dev d, ua_td_active_log a
where d.dt = to_date(:1, 'yyyy-mm-dd')
and a.dt = :2
and d.deviceid is not null
and (d.deviceid = a.idfa or d.deviceid = a.mac or
a.idfa =lower(utl_raw.cast_to_raw(dbms_obfuscation_toolkit.md5(input_string => d.deviceid))))

绑定变量值都是日期相关的。

Name    Position    Type    Value   
:1    1    VARCHAR2(32)    2017-01-06
:2    2    VARCHAR2(32)    2017-01-06
:3    3    VARCHAR2(32)    2017-01-06
:4    4    VARCHAR2(32)    2017-01-06

可以看出h1_first_dev的字段dt是日期型,ua_td_active_log的dt是字符型。

在这种情况下就是两个结果集的运算了。

本来根据时间条件,可以从两个表里各筛取出很小的一部分数据。


但是两个表的字段类型又不相同,一个date型,一个varchar2,而且没有映射关系,最要命的是后面的条件and (d.deviceid = a.idfa or d.deviceid = a.mac or ... 最后两个表的关联关系被这个条件给彻底破坏了。

所以如此一来,原本3000条左右的数据的关联,硬是给映射成了4千万数据的关联(表里的数据有4千万),想想就是心塞。

我用下面的一个图来表达我的这种心情吧。


而且里面竟然还用到了几个比较少见的包dbms_obfuscation_toolkit utl_raw       导致CPU解析非常繁忙。

查看SQL Monitor报告,基本都被PL/SQL的包给攻占了。


所以这个问题解决起来其实还是要花一些功夫的。我对比模拟测试了一下。取得增量数据,然后运行同样的SQL

SQL> create table H1_FIRST_DEV as select * from mbi.H1_FIRST_DEV where dt=to_date('2017-01-06','yyyy-mm-dd');   
SQL> create table UA_TD_ACTIVE_LOG as select * from mbi.UA_TD_ACTIVE_LOG where dt='2017-01-06';

没有任何索引,也没有收集统计信息,同样的语句消耗在1秒内。
SQL> @sqltune1.sql
no rows selected
Elapsed: 00:00:00.60  

而更为奇怪的是返回竟然是0行。

这个时候回头来看开始性能较差的执行计划统计信息,就能理解了。

所以这个问题的解决方案可以稳定执行计划,让dt的过滤优先。或者是重新修改SQL语句,把一些逻辑做精简和改进,比如PL/SQL调用的包体可以通过数据过滤来处理,毕竟是很小的一部分数据。
    当然和开发的同学沟通后,我发现问题还没想象的那么简单,因为还有几个更有挑战的SQL要优化,下一篇来谈谈怎么把一个平均执行20秒的核心SQL优化为2秒,关联的表都是千万级别。

时间: 2024-10-28 19:22:59

复杂SQL性能优化的剖析(一)(r11笔记第36天)的相关文章

复杂SQL性能优化的剖析(二)(r11笔记第37天)

    昨天的一篇文章复杂SQL性能优化的剖析(一)(r11笔记第36天) 分析了一个SQL语句导致的性能问题,问题也算暂时告一段落,因为这个语句的执行频率是10分钟左右,所以优化后(大概是2秒左右,需要下周再次确认)的提升很大.    对于优化是一个持续的改进,我们碰到的问题,最终的原因可能五花八门,但是正如柯南所说,真相只有一个.我把这个问题和前几天处理的一个问题结合起来,前几天处理了一个紧急问题,也是有一个SQL语句的执行计划发生改变,这个语句的业务比较关键,触发频率是每分钟一次,如果一旦

ORACLE SQL性能优化系列 (十四) 完结篇

oracle|性能|优化 46.       连接多个扫描 如果你对一个列和一组有限的值进行比较, 优化器可能执行多次扫描并对结果进行合并连接. 举例:     SELECT *     FROM LODGING     WHERE MANAGER IN ('BILL GATES','KEN MULLER');       优化器可能将它转换成以下形式     SELECT *     FROM LODGING     WHERE MANAGER = 'BILL GATES'     OR MA

面包含点-PostGresql SQL性能优化求助

问题描述 PostGresql SQL性能优化求助 点表:create table point_p(flong float8flat float8userid int4);insert into point_p(flongflatuserid) values (113.12655922.6553671);insert into point_p(flongflatuserid) values (113.02934522.6219592);insert into point_p(flongflatu

SQLSERVER SQL性能优化技巧_MsSql

1.选择最有效率的表名顺序(只在基于规则的优化器中有效) SQLSERVER的解析器按照从右到左的顺序处理FROM子句中的表名,因此FROM子句中写在最后的表(基础表driving table)将被最先处理,在FROM子句中包含多个表的情况下,必须选择记录条数最少的表作为基础表,当SQLSERVER处理多个表时,会运用排序及合并的方式连接它们, 首先,扫描第一个表(FROM子句中最后的那个表)并对记录进行排序:然后扫描第二个表(FROM子句中最后第二个表):最后将所有从第二个表中检索出的记录与第

SQL性能优化之定位网络性能问题的方法(DEMO)_MsSql

最近项目组同事跟我说遇到一个SQL性能问题,他说全表只有69条记录,客户端执行耗费了两分多钟,很不科学.我帮了分析出了原因并得到解决.下面小编安装类似表结构,构造了一个案例,测试截图如下所示: 这个表有13800KB(也就是13M多大小),因为该表将图片保存到数据库(Item_Photo字段为iamge类型),这个是历史原因,暂且不喷这种的设计.看来这个SQL执行时间长的性能问题不在于IO和SQL本身执行计划是否有问题,而是在网络数据传时间上(服务器与客户端位于异地,两地专线带宽6M,不过很多应

Oracle SQL性能优化系列学习三_oracle

正在看的ORACLE教程是:Oracle SQL性能优化系列学习三.8. 使用DECODE函数来减少处理时间  使用DECODE函数可以避免重复扫描相同记录或重复连接相同的表.  例如:  SELECT COUNT(*),SUM(SAL) FROM EMP  WHERE DEPT_NO = 0020  AND ENAME LIKE 'SMITH%';  SELECT COUNT(*),SUM(SAL)  FROM EMP  WHERE DEPT_NO = 0030  AND ENAME LIKE

Oracle SQL性能优化系列学习一_oracle

正在看的ORACLE教程是:Oracle SQL性能优化系列学习一.1. 选用适合的ORACLE优化器  ORACLE的优化器共有3种:  a. RULE (基于规则) b. COST (基于成本) c. CHOOSE (选择性)  设置缺省的优化器,可以通过对init.ora文件中OPTIMIZER_MODE参数的各种声明,如RULE,COST,CHOOSE,ALL_ROWS,FIRST_ROWS . 你当然也在SQL句级或是会话(session)级对其进行覆盖.  为了使用基于成本的优化器(

你对SQL性能优化知识知多少?

转自:http://www.vaikan.com/what-do-you-know-about-sql-performance/  "SQL性能优化是一种黑魔法 就像炼金术一样: 各种配方难解晦涩, 只有一小部分圈内人才能理解." 这是一种误解,SQL数据库使用的是大家公知的算法来实现可以预期的执行性能.然而,问题是,人们很容易写出不能发挥最高效算法的SQL查询语句,因而也容易产生无法预期的性能结果. 下面是5道关于SQL性能优化小测试题,这些测试题也许会让你坚信SQL优化就是一种黑魔

Oracle SQL性能优化系列学习二_oracle

正在看的ORACLE教程是:Oracle SQL性能优化系列学习二.  4. 选择最有效率的表名顺序(只在基于规则的优化器中有效)  ORACLE的解析器按照从右到左的顺序处理FROM子句中的表名,因此FROM子句中写在最后的表(基础表 driving table)将被最先处理. 在FROM子句中包含多个表的情况下,你必须选择记录条数最少的表作为基础表.当ORACLE处理多个表时, 会运用排序及合并的方式连接它们.首先,扫描第一个表(FROM子句中最后的那个表)并对记录进行派序,然后扫描第二个表