ITPub博客

首页 > 数据库 > Oracle > [20241221]SQL优化遇到的问题.txt

[20241221]SQL优化遇到的问题.txt

原创 Oracle 作者:lfree 时间:2024-12-23 21:15:25 0 删除 编辑

[20241221]SQL优化遇到的问题.txt

--//每年的年底我都会做一些sql优化工作,最近几年我非常不愿意做这类工作,主要不能体现自己的价值,特别是exadata环境。
--//不过在优化如下sql语句时遇到一些问题,主要是很久没有做做这类工作,有点生疏,做一个记录与总结。

1.环境:
SYS@127.0.0.1:9105/xtdb/xtdb1> @ver2
==============================
PORT_STRING                   : x86_64/Linux 2.4.xx
VERSION                       : 19.0.0.0.0
BANNER                        : Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
BANNER_FULL                   : Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.14.0.0.0
BANNER_LEGACY                 : Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
CON_ID                        : 0
PL/SQL procedure successfully completed.

2.问题语句:
--//通过脚本生产问题语句执行脚本:

$ cat bzmg7z5br9tpg.sql9_0
variable STARTTIME VARCHAR2(32)
variable ENDTIME VARCHAR2(32)
begin
:STARTTIME := '2024/12/20 00:00:00';
:ENDTIME := '2024/12/21 00:00:00';
null;
end;
/
set termout off
set sqlblanklines on
alter session set current_schema=MEDSURGERY;
alter session set statistics_level=all;

SELECT *
  FROM (SELECT A.PATIENT_ID AS PATIENT_ID,
     VISIT_ID,
....
     THIRD_SUPPLY_NURSE,
     CASE WHEN IN_DATE_TIME IS NULL THEN SCHEDULED_DATE_TIME ELSE IN_DATE_TIME END AS START_DATE_TIME,
     OPERATION_NAME,
....
     A.OUT_PACU_DATE_TIME
     FROM MED_OPERATION_MASTER A
     LEFT JOIN MED_PAT_MASTER_INDEX B ON A.PATIENT_ID = B.PATIENT_ID) TEMP
 WHERE 1 = 1
    AND ((SCHEDULED_DATE_TIME >= :startTime AND SCHEDULED_DATE_TIME < :endTime)
     OR (START_DATE_TIME >= :startTime AND START_DATE_TIME <= :endTime)
     OR (IN_DATE_TIME >= :startTime AND IN_DATE_TIME <= :endTime))
    AND ( 1 = 2 OR operating_room = '138')
  ORDER BY OPER_STATUS, OPERATING_ROOM_NO,SEQUENCE,START_DATE_TIME ;
set termout on
set sqlblanklines off
--@zws '' ''
--@dpc '' '' ''
@dpc '' outline ''
rollback;
alter session set current_schema=SYS ;

--//简单说明,做了一些格式化处理,我觉得写代码这有点奇葩,后面的条件( 1 = 2 OR operating_room = '138')竟然使用or。
--//一般正常不会这样写,而是缺省1=1 然后使用and来补充或者替换相关过滤条件,里面如果没有岂不是要再加上一个1=1,变成
--//( 1 = 2 OR 1=1),因为operating_room = '138'经常变化,awr报表漏掉这类语句,这些语句累积起来,消耗还是很大的。
--//执行计划全表扫描MED_OPERATION_MASTER。

3.问题解决:
--//仔细看问题sql语句,可以发现只要建立三个日期字段的相关索引问题就应该解决了。
SYS@127.0.0.1:9106/xtdb/xtdb2> @ desczz MEDSURGERY.MED_OPERATION_MASTER IN_DATE_TIME,SCHEDULED_DATE_TIME,START_DATE_TIME
eXtended describe of MEDSURGERY.MED_OPERATION_MASTER

DISPLAY TABLE_NAME OF COLUMN_NAME INFORMATION.
INPUT   OWNER.TABLE_NAME  <filters>
SAMPLE  : @ desczz TABLE_NAME column_name1,column_name2
IF NOT INPUT <filters> ,USE "" .

Owner      Table_Name           SAMPLE_SIZE LAST_ANALYZED       Col# Column Name          Null? Type    NUM_DISTINCT        Density  NUM_NULLS HISTOGRAM NUM_BUCKETS Low_value           High_value
---------- -------------------- ----------- ------------------- ---- -------------------- ----- ------- ------------ -------------- ---------- --------- ----------- ------------------- -------------------
MEDSURGERY MED_OPERATION_MASTER       91261 2024-12-19 22:01:24   29 START_DATE_TIME            DATE(7)        78072   .00001280869      37559                     1 2022-05-26 08:50:00 2024-12-19 21:35:00
                                      91452 2024-12-19 22:01:24   53 IN_DATE_TIME               DATE(7)        74640   .00001339764      37368                     1 2022-05-26 07:20:00 2024-12-19 21:00:00
                                     128783 2024-12-19 22:01:24  122 SCHEDULED_DATE_TIME        DATE(7)        47948   .00002085593         37                     1 2022-05-18 10:00:00 2024-12-20 10:00:00
3 rows selected.

--//仔细检查发现SCHEDULED_DATE_TIME没有建立索引,想当然认为建立该字段索引问题应该解决,而且查询都是1天的时间间隔。
--//扫描3个相关索引就可以.再次测试后发现并没有使用相关索引,还是选择全表扫描。

--//然后加入如下提示:
BITMAP_TREE(@"SEL$C3EBEA37" "A"@"SEL$2" OR(1 1 ("MED_OPERATION_MASTER"."SCHEDULED_DATE_TIME") 2
("MED_OPERATION_MASTER"."START_DATE_TIME") 3 ("MED_OPERATION_MASTER"."IN_DATE_TIME")))

--//发现并不起作用,在这里开始浪费大量时间....
--//无意中注解 OR (START_DATE_TIME >= :startTime AND START_DATE_TIME <= :endTime) 这行,发现可以使用相关索引,这行注解简
--//单。取消注解就不能使用相关索引,至此陷入思维的很乱....

--//无意中检查发现Predicate Information (identified by operation id):段,发现出现如下信息:
Predicate Information (identified by operation id):
---------------------------------------------------
   3 - storage(("A"."OPERATING_ROOM"='138' AND ((CASE  WHEN "A"."IN_DATE_TIME" IS NULL THEN "A"."SCHEDULED_DATE_TIME" ELSE
              "A"."IN_DATE_TIME" END >=:STARTTIME AND CASE  WHEN "A"."IN_DATE_TIME" IS NULL THEN "A"."SCHEDULED_DATE_TIME" ELSE "A"."IN_DATE_TIME" END
              <=:ENDTIME) OR ("A"."SCHEDULED_DATE_TIME">=:STARTTIME AND "A"."SCHEDULED_DATE_TIME"<:ENDTIME) OR ("A"."IN_DATE_TIME">=:STARTTIME AND
              "A"."IN_DATE_TIME"<=:ENDTIME))))
       filter(("A"."OPERATING_ROOM"='138' AND ((CASE  WHEN "A"."IN_DATE_TIME" IS NULL THEN "A"."SCHEDULED_DATE_TIME" ELSE
              "A"."IN_DATE_TIME" END >=:STARTTIME AND CASE  WHEN "A"."IN_DATE_TIME" IS NULL THEN "A"."SCHEDULED_DATE_TIME" ELSE "A"."IN_DATE_TIME" END
              <=:ENDTIME) OR ("A"."SCHEDULED_DATE_TIME">=:STARTTIME AND "A"."SCHEDULED_DATE_TIME"<:ENDTIME) OR ("A"."IN_DATE_TIME">=:STARTTIME AND
              "A"."IN_DATE_TIME"<=:ENDTIME))))
   5 - access("A"."PATIENT_ID"="B"."PATIENT_ID")

--//filter部分根本没有START_DATE_TIME字段信息,为什么?oracle不可能转换成这样的过滤条件。

--//我改写如下执行:
select * from  MED_OPERATION_MASTER A where
1 = 1
AND ((SCHEDULED_DATE_TIME >= :startTime AND SCHEDULED_DATE_TIME < :endTime)
OR (START_DATE_TIME >= :startTime AND START_DATE_TIME <= :endTime)
OR (IN_DATE_TIME >= :startTime AND IN_DATE_TIME <= :endTime))
AND ( 1 = 2 OR operating_room = '138');

--//ok,没有问题,到此已经浪费大量时间。
--//再仔细看sql语句才发现,编写者给我挖了一个坑,前面START_DATE_TIME被定义如下:
CASE WHEN IN_DATE_TIME IS NULL THEN SCHEDULED_DATE_TIME ELSE IN_DATE_TIME END AS START_DATE_TIME,
--//这样就很好理解为什么filter条件出现前面的情况。
--//也就是我要优化必须建立CASE WHEN IN_DATE_TIME IS NULL THEN SCHEDULED_DATE_TIME ELSE IN_DATE_TIME END的函数索引。

--//实际上仔细看发现这个条件(START_DATE_TIME >= :startTime AND START_DATE_TIME <= :endTime)是多余的。
--//IN_DATE_TIME是null 等于SCHEDULED_DATE_TIME,其他情况是IN_DATE_TIME。
--//这样已经涵盖在如下条件里面,完全可以取消这个条件。
(SCHEDULED_DATE_TIME >= :startTime AND SCHEDULED_DATE_TIME < :endTime)
(IN_DATE_TIME >= :startTime AND IN_DATE_TIME <= :endTime)

4.总结:
--//实际上这个问题很简单,主要问题在于我仅仅盯着谓词条件,没有主要开发重新定义START_DATE_TIME。
--//实际上我个人认为开发写代码不规范,不应该使用该表存在的字段START_DATE_TIME,哪怕定义为START_DATE_TIME1,问题都能很快
--//定位和发现.
--//而且自己在测试时,一开始没有主要执行计划的Predicate Information 信息,如果注意这些就不会走这些弯路,浪费大量时间。
--//至此以为问题已经接近解决,要么叫开发修改代码,要么建立相关索引。但是当我在21c下测试时,又遇到另外的情况。
--//太长另外写一篇blog分析。



来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/267265/viewspace-3058752/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论
熟悉oracle相关技术,擅长sql优化,rman备份与恢复,熟悉linux shell编程。

注册时间:2008-01-03

  • 博文量
    4006
  • 访问量
    7969263