情况如下。我有一个表包含基于许多不同表中记录的记录(下面的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的完整扫描。有没有办法阻止它进行全面扫描,因为我只需要最大的条目?
答案 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;
除非_id
和store_num
也被编入索引,并且是所述索引中的第一列,否则所有table_oid
索引对此查询都无用。
当然,它必须进行全面扫描;它可以在没有任何过滤条件的情况下立即返回max(id)
,但是一旦你输入过滤器,它就不能再使用id
索引,因为它不知道索引的哪个部分匹配那些store_num is not null
条目 - 不是没有扫描。
要加快查询速度,您需要在(store_num, table_oid, id)
上创建索引。关于为单个ad-hoc查询创建索引的标准免责声明适用;索引太多会影响插入/更新性能。
如何“重写”查询并不重要 - 这与应用程序代码不同,优化器无论如何都会重新排列查询的所有部分。除非您在搜索列上有足够的选择性索引,否则整个查询完全由单个索引覆盖,否则它将会很慢。
答案 2 :(得分:1)
我能想到的最好的方法就是:
table_oid - 这是您的主要访问条件 id - 所以你不必访问数据块来读取它, - 首先获得更高的ID值 covered_entity_id - 您基于此过滤数据,null vs not null
这应该可以防止需要访问T1中的473m行。
如果一切就绪,请查询如下:
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_id
与store_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