如何优化 SQL 查询(Oracle 数据库)

Posted

技术标签:

【中文标题】如何优化 SQL 查询(Oracle 数据库)【英文标题】:How to optimize SQL query (Oracle Database) 【发布时间】:2018-10-01 20:36:01 【问题描述】:

我想优化我的 SQL 查询。它看起来很简单,但对我来说编译时间太长了。我的数据库是 Oracle 12.1.0.3。当表 AB_MESSAGE 包含大约 1K 条记录时(测试环境),查询编译时间大于等于 6 秒。

我的 SQL 查询:

SELECT * from AB_MESSAGE m 
left join AB_MESSAGE_TYPE mt ON m.MESSAGE_TYPE_ID=m.ID
where to_char(m.CREATION_DATE,'YY/MM/DD') >= '18/09/30'  
and m.MESSAGE_ID is null 
and m.id in (select max(m2.ID) 
             from AB_MESSAGE m2
             where m2.MESSAGE_ID is null 
             group by m2.AB_MESSAGE_ID, m2.SEND_DATE)  
order by m.SEND_DATE desc;

表 AB_MESSAGE:

CREATE TABLE "AB"."AB_MESSAGE" 
(   "ID" NUMBER(*,0) NOT NULL ENABLE, 
"MESSAGE_ID" NUMBER(*,0), 
"AB_MESSAGE_ID" NUMBER(*,0), 
"SNDR_MSG_REF" VARCHAR2(20 BYTE), 
"SENDER_ID" VARCHAR2(20 BYTE), 
"RECEIVER_ID" VARCHAR2(20 BYTE), 
"ESDK_MESSAGE_TYPE_ID" NUMBER, 
"MESSAGE_TYPE_ID" NUMBER NOT NULL ENABLE, 
"MT_FIELD_DEF_VARIANT_ID" NUMBER, 
"ISSUE_ID" NUMBER, 
"PARENT_ID" NUMBER, 
"PARENT_MESSAGE_ID" NUMBER, 
"CREATION_DATE" TIMESTAMP (6) DEFAULT CURRENT_TIMESTAMP NOT NULL ENABLE, 
"CHANGE_DATE" TIMESTAMP (6), 
"SEND_DATE" TIMESTAMP (6), 
"MESSAGE_FILE_NAME" VARCHAR2(100 BYTE), 
"MESSAGE_TYPE" VARCHAR2(24 BYTE), 
"RAW_ESDK_MESSAGE" BLOB, 
"XML_MESSAGE_CLOB" CLOB, 
"MESSAGE_STATUS" VARCHAR2(50 BYTE), 
 CONSTRAINT "AB_MESSAGE_PK" PRIMARY KEY ("ID")
 USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS 
 STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
 BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
 TABLESPACE "ABA"  ENABLE, 
 CONSTRAINT "AB_MESSAGE_FK5" FOREIGN KEY ("PARENT_ID")
  REFERENCES "AB"."AB_MESSAGE" ("ID") ENABLE, 
 CONSTRAINT "AB_MESSAGE_FK1" FOREIGN KEY ("MESSAGE_TYPE_ID")
  REFERENCES "AB"."AB_MESSAGE_TYPE" ("ID") ON DELETE CASCADE ENABLE)

表 AB_MESSAGE:

CREATE TABLE "AB"."AB_MESSAGE_TYPE" 
("ID" NUMBER(*,0) NOT NULL ENABLE, 
"NAME" VARCHAR2(20 BYTE) NOT NULL ENABLE, 
"DESCRIPTION" VARCHAR2(150 BYTE), 
"CREATION_DATE" TIMESTAMP (6) DEFAULT CURRENT_TIMESTAMP NOT NULL ENABLE, 
 CONSTRAINT "AB_MESSAGE_TYPE_PK" PRIMARY KEY ("ID")
 USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS 
 STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
 BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
 TABLESPACE "ABA" ENABLE)

问题:有没有办法优化这个查询?

【问题讨论】:

首先,您要否定使用条件为to_char(m.CREATION_DATE,'YY/MM/DD') >= '18/09/30' 的索引,它会受到“等式左侧的表达式”的影响。将其更改为m.CREATION_DATE >= date '2018-09-30' 你有什么索引,计划说了什么? 第二行不应该以=mt.ID而不是m.ID结尾吗? 你是如何测量查询编译时间的? 【参考方案1】:

我将从改进两个简单的事情开始:

    改进您的 SQL:将 to_char(m.CREATION_DATE,'YY/MM/DD') >= '18/09/30' 更改为 m.CREATION_DATE >= date '2018-09-30'。此更改将有助于索引的使用。

    添加以下索引:

    create index ix1 on AB_MESSAGE (AB_MESSAGE_ID, SEND_DATE, MESSAGE_ID, ID);
    
    create index ix2 on AB_MESSAGE (CREATION_DATE, MESSAGE_ID);
    

如果查询仍然很慢,我会获取查询的执行计划以验证它是否使用了正确的索引。

【讨论】:

【参考方案2】:

您的查询未使用在ab_message(creation_date) 上定义的索引(如果有的话)。 将 >= 比较的左侧部分更改为不使用函数,因为它们是优化器的黑盒。

此外,您的 JOIN 列可能是错误的,因为它应该从 m 获取一列,从 mt 获取另一列。请改用m.message_type_id = mt.id

SELECT * 
from AB_MESSAGE m 
left join AB_MESSAGE_TYPE mt ON m.MESSAGE_TYPE_ID = mt.ID -- change here
where 
      m.CREATION_DATE >= '2018-09-30' -- change here
  and m.MESSAGE_ID is null 
  and m.id in (
    select max(m2.ID) 
    from AB_MESSAGE m2 
    where m2.MESSAGE_ID is null 
    group by m2.AB_MESSAGE_ID, m2.SEND_DATE)  
order by m.SEND_DATE desc;

除此之外,还有可能重写子查询,但我们需要查看您在表上创建的索引。

阅读 Oracle 文档中关于 EXPLAIN PLAN 的章节。这可能是对您问题的一个很好的补充,以便我们知道您的数据库引擎选择什么计划来执行查询。

我建议初学者有如下定义的索引,看看计划如何/如果改变

ab_message(ab_message_id, send_date) ab_message(message_type_id, creation_date)

【讨论】:

以上是关于如何优化 SQL 查询(Oracle 数据库)的主要内容,如果未能解决你的问题,请参考以下文章

oracle怎样查询数据库函数是不是被执行

Oracle查询速度优化问题

如何提高oracle的查询速度

ORACLE中这个运行4秒左右的SQL语句如何优化?我想查询少用点时间

oracle怎么优化

SQL为王:oracle标量子查询和表连接改写