SQL:如何加快此查询速度

时间:2010-02-15 20:45:31

标签: sql oracle optimization

情况如下。我有一个表包含基于许多不同表中记录的记录(下面的t1)。 t2是将信息从中拉入t1的表格之一。

t1
  table_oid --which table id is a FK to
  id        --fk to other table
  store_num --field

t2
  t2_id

这是我需要找到的:我需要最大的t2_id,其中store_num在t1的相应记录中不为null。这是我写的查询:

select max(id) from t1
join t2 on t2.t2_id = t1.id
where store_num is not null
and table_oid = 1234;

然而,这需要相当长的时间。我认为这应该是一个快速查询。所有_ids都有索引。 (t1.id/t1.table_oid,t2.t2_id)。 t1中的绝大多数条目都有store_num。

在心理上,我会按顺序获取t2_id,而不是一个一个地尝试t1_id,直到找到第一个有store_num的t1_id。

select t2_id from t2 order by t2_id desc;

的解释成本为25612

select t1.* from t1 where table_oid = 1234
and id in (select max(t2_id) from t2);

的解释费用为8。

那么为什么上述查询的成本最高不得超过25612 * 8 = 204896?当我解释它时,它回来的次数是它的3倍多。

真的,我的问题是如何重新编写该查询以更快地运行。

注意:我使用的是Oracle。

编辑:

t2有11,895,731行 t1有473,235,192行

编辑2:

由于我尝试了不同的东西,查询中耗时最长的部分是在t1上查找store_num的完整扫描。有没有办法阻止它进行全面扫描,因为我只需要最大的条目?

4 个答案:

答案 0 :(得分:2)

不确定这些是否适用于Oracle。您是否在联接的fk id列上有索引。此外,如果你可以避免'NOT IN'在SQL中不是一个不可搜索的类型,这会减慢查询速度。

另一个可能较慢的选项是执行外连接,然后在该列上检查null。 (不确定这是否也仅适用于sql)

select max(id) from t1
left outer join t2 on t2.t2_id = t1.id
where t1... IS NULL
and table_oid = 1234;

答案 1 :(得分:2)

你说:

  

所有_ids都有索引

但您的查询是:

...
where store_num is not null
and table_oid = 1234;

除非_idstore_num也被编入索引,并且是所述索引中的第一列,否则所有table_oid索引对此查询都无用。

当然,它必须进行全面扫描;它可以在没有任何过滤条件的情况下立即返回max(id),但是一旦你输入过滤器,它就不能再使用id索引,因为它不知道索引的哪个部分匹配那些store_num is not null条目 - 不是没有扫描。

要加快查询速度,您需要在(store_num, table_oid, id)上创建索引。关于为单个ad-hoc查询创建索引的标准免责声明适用;索引太多会影响插入/更新性能。

如何“重写”查询并不重要 - 这与应用程序代码不同,优化器无论如何都会重新排列查询的所有部分。除非您在搜索列上有足够的选择性索引,否则整个查询完全由单个索引覆盖,否则它将会很慢。

答案 2 :(得分:1)

我能想到的最好的方法就是:

  1. 按顺序在(TABLE_OID,ID DESC,COVERED_ENTITY_ID)上创建索引。为什么?
  2.   

    table_oid - 这是您的主要访问条件   id - 所以你不必访问数据块来读取它,                       - 首先获得更高的ID值   covered_entity_id - 您基于此过滤数据,null vs not null

    这应该可以防止需要访问T1中的473m行。

    1. 确保T2_ID上有索引。
    2. 如果一切就绪,请查询如下:

      select max(id) 
        from t1     
             inner join t2 
                on t2.t2_id = t1.id     
       where covered_entity_id is not null     
         and table_oid = 1234;   
      

      应该(优化器是一个挑剔的野兽)能够通过对T1上的索引进行快速全扫描来驱动半连接,而不是扫描数据块。还可以考虑将其作为manaully编写为:

      select max(id) 
        from t1     
       where covered_entity_id is not null     
         and table_oid = 1234
         and exists (select null 
                       from t2
                      where t1.id = t2.t2_id);   
      
      select max(id)
        from t1
       where covered_entity_id is not null
         and table_oid = 1234
         and id in (select t2_id from t2);
      

      由于优化器可能会略微区别地编写这些计划。

答案 3 :(得分:1)

在下面我假设covered_entity_idstore_num相同 - 如果你的命名一致,它会让我们更容易。

  

t1中的绝大多数条目   有一个store_num。

鉴于这种情况,以下条款不应对查询的性能产生任何影响......

where covered_entity_id is not null

然而,你继续说

  

正在进行的查询部分   最长的是t1上的全扫描   寻找store_num

这表明查询首先要查找covered_entity_id is not null,而不是大概更具选择性table_oid = 1234。解决方案可能就像重写这样的查询一样简单......

where table_oid = 1234 
and  covered_entity_id is not null;

......虽然我怀疑没有。您可以尝试提示以使查询使用table_oid上的索引。

另一件事是,统计数据有多新鲜?当优化器选择一个极其糟糕的执行计划时,通常是因为统计数据已过时。

顺便说一下,你为什么要加入T2呢?从T1中选择max(id)可以满足您的要求(除非您没有外键强制执行T1.ID个引用T2.T2_ID,因此需要确定)。

修改

要检查统计信息,请运行此查询:

select table_name
       , num_rows
       , last_analyzed
from user_tables
where table_name in ('T1', 'T2')
/

如果结果显示num_rows与您在第一次编辑中提供的值差异很大,那么您应该重新收集统计信息。如果last_anlayzed类似于您上线的那一天,那么您肯定应该重新聚集。您可能希望先导出统计信息;刷新统计信息可能会影响执行计划(这是练习的对象)通常是好的,但有时事情会变得更糟。 Find out more