Oracle order by 运行非常缓慢

3

我正在执行如下查询,它需要6秒钟才能完成:

select * 
from ( select aaa."ID" 
      from "aaa"  
      where aaa."DELETED" is null 
      order by aaa."CREATED" desc ) 
where rownum <= 15;

我有大约160万条记录在我的表中,我尝试为删除列和创建列添加单独的索引,还尝试添加包含创建和删除列的索引,并尝试以不同的顺序创建相同的索引。但是似乎没有任何帮助。我该怎么做才能加快速度?
由于查询是由Hibernate生成的,我不能太多地更改查询。
编辑: 即使没有 "aaa. “DELETED” is null” 的查询,查询也运行得非常缓慢。
编辑2: 查询计划如下: Query plan 编辑3: 添加了我的索引定义。老实说,我不知道这些数字中的大部分是什么意思,我使用SQLDeveloper来创建索引。我甚至不知道每个索引有这么多配置选项,我现在会查阅文档。
CREATE INDEX "aaa"."aaa_CREATED_ASC" ON "aaa"."aaa"
  (
    "CREATED"
  )
  PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE
  (
    INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT
  )
  TABLESPACE "SYSTEM" ;
CREATE INDEX "aaa"."aaa_CREATED_DESC" ON "aaa"."aaa"
  (
    "CREATED" DESC
  )
  PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE
  (
    INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT
  )
  TABLESPACE "SYSTEM" ;

查询计划是什么样子的? - tobiasbayer
这张图片是我添加的查询计划吗? :) (我对Oracle术语不熟悉,我是一个MySQL人:D) - Yervand Aghababyan
所以你正在处理一个有数百万行的表,对它们进行排序,然后只查看前15行--这就是你感兴趣的全部内容。你应该看一下Oracle的分析函数。例如,参见https://dev59.com/XlbTa4cB1Zd3GeqP7yT2 - Jim Hudson
那个解释计划似乎是针对一个没有任何索引的表。或者至少是针对一个没有按CREATED字段建立索引的表。另外,请问您正在使用哪个版本的数据库?随着每个版本的发布,Oracle的优化器都会有所改进。 - APC
创建的列上肯定有索引(我已经更新了问题并附上了它们的代码)。一个是升序,另一个是降序。但我同意,它们没有被使用。我尝试在查询中提供索引提示,但它没有起作用。 - Yervand Aghababyan
2个回答

9
您需要了解Oracle如何访问数据库中的记录。索引读取是一种操作,表格读取是另一种操作。因此,从表中检索一个索引记录至少需要两次读取。
您的查询使用三个信息:
- DELETED列(限制条件) - CREATED列(排序条件) - ID(结果集)
Oracle不会为NULL值建立索引,因此单独在DELETED上建立单列索引对您没有帮助。那将进行全表扫描,与没有索引的情况一样。
仅在CREATED上建立索引更好,因为访问路径将变为:
INDEX FULL SCAN DESCENDING

也就是说,它从索引中最近的日期开始向后工作。然而,查询仍然需要读取表格以查找ID和DELETED值。这可能需要大量的表读取,具体取决于DELETED为空的频率。
现在,在(CREATED, DELETED)的复合索引中,按照那个顺序应该更有用,因为Oracle现在将在DELETED列中索引NULL值。Oracle可以使用此索引来确保仅查找表记录以获取ID值。这将需要15次表读取。
最后,您可以在(CREATED, DELETED, ID)上建立一个复合索引,并且从索引中服务整个查询。这是最快速的选择。
然后,您只需决定性能收益是否使维护索引的开销变得合理。值得一提的是,维护复合索引的成本只会增加单列索引维护成本的一小部分。
顺便说一句,像这样可怕的查询就是不使用逻辑删除的一个原因。物理删除记录,并使用日志表在需要时检索表的历史状态。

+1,尽管索引 (created,deleted) 仅在 CREATED 不可为空(从 OP 的问题不太可能)或者查询被修改以包括谓词 where created is not null 时才会被使用。 - Vincent Malgrat
非常感谢。根据您的回复,我发现诀窍是将ID列添加到索引中。所以现在是(created,id)。我没有在那里添加删除列,因为我希望它也可以用于其他查询,并且将删除列放入其中的性能优势微不足道。 - Yervand Aghababyan
1
@VincentMalgrat - 说得好。恐怕我假设所有记录都有创建时间(否则这样的列有什么意义呢?)。 - APC

-1

如果我从查询中完全删除deleted is null部分,它仍然在5.9秒内执行。所以我不认为这会有帮助 :( - Yervand Aghababyan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接