使用 Oracle 进行第一个字符计数导航:计数查询不使用功能索引

Posted

技术标签:

【中文标题】使用 Oracle 进行第一个字符计数导航:计数查询不使用功能索引【英文标题】:first character count navigation with Oracle: count queries dont't use functional indices 【发布时间】:2011-02-09 14:11:54 【问题描述】:

甲骨文 9i。我们有一个包含标题和作者列的内容的大表(约 1M 行)。我们想编写视图,为该内容的标题和作者提供 A-Z 导航(A:1300、B:45000、...)

首先进行一些没有任何索引的准备工作:

select * from content where substr(upper(title),0,1) = 'M'

性能略好于

select * from content where upper(title) LIKE 'M%'

为这些人制定计划:

TABLE ACCESS content FULL Cost=1624

即使没有任何索引,两者都非常快。现在是缓慢的部分:

select count(*) from CONTENT WHERE substr(upper(TITLE),0,1) = 'A';

计划:

SORT AGGREGATE else like above.

现在累积(这是我们想要的,真的很慢):

select substr(upper(title),0,1) , count(*) from content group by substr(upper(title),0,1);

计划:

SORT: GROUP BY COST=8069 / TABLE ACCESS on CONTENT FULL COST=1624

于是我开始创建功能索引:

create index CONTENT_TITLE_LETTER_IDX on CONTENT(substr(upper(TITLE),0,1));

这大大加快了单字母计数查询:

select count(*) from CONTENT WHERE substr(upper(TITLE),0,1) = 'A';

ExPlan(它几乎实时响应):

SORT AGGREGATE COST=1 / INDEX CONTENT_TITLE_LETTER_IDX RANGE SCAN COST=1

但是基本上查询相同事物的累积查询没有使用索引(它显示与上面相同的解释计划)。我尝试了提示:

select /*+ index(CONTENT CONTENT_TITLE_LETTER_IDX) */ substr(upper(title),0,1) , count(*) from content group by substr(upper(title),0,1);

但它仍然很慢。我认为这可能是由于无序索引造成的,但我猜即使我对所有 26 个可能的字母进行循环,单个查询( = 'letter' )也会更快!

谁知道如何告诉 Oracle 使用该索引(或创建单字符列或表之外的另一种方法)?

【问题讨论】:

【参考方案1】:

再看看你的查询:

select substr(upper(title),0,1) , count(*) 
from content 
group by substr(upper(title),0,1)

请注意没有任何where 子句。实际上,您告诉数据库引擎获取所有行并计算每个首字母有多少行。您不能跳过任何行,因为否则您无法计算它。我认为索引不容易存储此类信息,因此完整扫描是您可以拥有的最快速度。如果您要求输入特定字母,则对索引使用范围扫描可能是有意义的。

如果您经常需要此信息,请创建一个汇总表,该汇总表将由您的主表上的触发器进行更新。

【讨论】:

+1 但由于这是 Oracle,我更希望看到一个可快速刷新的物化视图来维护汇总表。 @Justin-Cave:我想,普通 MV 会发出相同的全扫描查询。由于更新可能不是很频繁,这不是什么大问题,但视图会落后一点。触发器会立即且廉价地刷新视图。【参考方案2】:

好的,这回答了问题,但并没有真正解决问题(感谢 Gerrat,我们几乎是同一时间):

我查看了this interesting question,它告诉我 CBO 可能会拒绝包含 NULL 值的索引。考虑到我们的表格包含以非字母字符和空格开头的标题(但绝不为空...),我尝试了以下操作:

select   substr(upper(title),0,1), count(*) 
from content
 where substr(upper(title),0,1) >= 'A' AND substr(upper(title),0,1) <= 'Z'
group by substr(upper(title),0,1);

这大大降低了成本:

SORT GROUP BY NOSORT COST=16 / CONTENT ACCESS BY INDEX ROWID COST=6 / CONTENT_TITLE_LETTER_IDX RANGE SCAN COST=2

从 8069 到 16

有趣的是,条件查询现在甚至比无条件查询(平均 1.5 秒)(平均 2.7 秒)。如 9000 所述,添加更多条件会显着加快速度 - 即使它根本不使用字母索引。感谢那 洞察力!

【讨论】:

+1,您找到了索引未被使用的主要原因。尽管我更喜欢子句WHERE substr(upper(TITLE),0,1) IS NOT NULL,它应该产生相同的计划并且(IMO)更具可读性。【参考方案3】:

我不能向你保证这会更快,但你可以试试:

select /*+ index(CONTENT CONTENT_TITLE_LETTER_IDX) */ substr(upper(title),0,1), 
count(*) from content 
where substr(upper(title),0,1) in ('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 
'X', 'Y', 'Z')
group by substr(upper(title),0,1);

在我做的类似测试中,这确实使用了索引(我没有尝试过其他任何方法),但成本实际上高于全表扫描)。

确保您的表和索引也被分析。

【讨论】:

如果标题以 '1' 或 'É' 或 'Я' 等开头,这将打开另一个蠕虫罐。但不能打赌它会比完整扫描更快。

以上是关于使用 Oracle 进行第一个字符计数导航:计数查询不使用功能索引的主要内容,如果未能解决你的问题,请参考以下文章

Oracle SQL - 如何使用 PIVOT 进行计数

如何使用java打印字符串中每个字符的计数

mysql数据库中的substring函数问题?

jquery 使用字符计数器插件进行验证

列表(索引与切片,增删改查) ,计数,排序,元祖和元祖的嵌套

Oracle:根据结果集计数更新行