我创建了一个Oracle Text索引,如下所示:
create index my_idx on my_table (text) indextype is ctxsys.context;
然后我可以做以下事情:
select * from my_table where contains(text, '%blah%') > 0;
但是我们假设我们在此表中有另一列,比如group_id
,我想改为执行以下查询:
select * from my_table where contains(text, '%blah%') > 0 and group_id = 43;
使用上述索引,Oracle必须搜索包含'blah'
的所有项目,然后检查所有group_id
个。
理想情况下,我更愿意只使用group_id = 43
搜索项目,所以我想要一个这样的索引:
create index my_idx on my_table (group_id, text) indextype is ctxsys.context;
有点像普通索引,因此可以为每个group_id
进行单独的文本搜索。
有没有办法在Oracle中做这样的事情(如果这很重要,我会使用10g)?
修改(澄清)
考虑一个包含一百万行的表和以下两列,A
和B
,这两个都是数字。假设有{500}个不同的A
值和2000个B
的不同值,每行都是唯一的。
现在让我们考虑select ... where A = x and B = y
A
和B
上的索引据我所知,在B
上进行索引搜索,这将返回500个不同的行,然后执行加入/扫描这些行。在任何情况下,至少需要查看500行(除了数据库是幸运的,并提前找到所需的行。
尽管(A,B)
上的索引更有效,但它在一个索引搜索中找到一行。
在group_id
上放置单独的索引,我认为文本只留下了两个选项。
(1)使用group_id
索引,并扫描文本的所有结果行
(2)使用文本索引,并扫描group_id
的所有结果行
(3)使用两个索引,并进行连接。
我想要:
(4)使用(group_id, "text")
索引查找特定group_id
下的文本索引,并扫描该文本索引以查找我需要的特定行/行。不需要扫描和检查或加入,就像在(A,B)
上使用索引一样。
答案 0 :(得分:8)
Oracle Text
1 - 您可以通过使用FILTER BY:
创建CONTEXT索引来提高性能create index my_idx on my_table(text) indextype is ctxsys.context filter by group_id;
在我的测试中,filter by
肯定提高了性能,但在group_id上使用btree索引的速度仍然稍快。
2 - CTXCAT索引使用“子索引”,似乎与多列索引类似。这似乎是您正在寻找的选项(4):
begin
ctx_ddl.create_index_set('my_table_index_set');
ctx_ddl.add_index('my_table_index_set', 'group_id');
end;
/
create index my_idx2 on my_table(text) indextype is ctxsys.ctxcat
parameters('index set my_table_index_set');
select * from my_table where catsearch(text, 'blah', 'group_id = 43') > 0
这可能是最快的方法。使用上述查询对120MB的随机文本类似于您的A和B场景,只需要18个一致的获取。但在不利方面,创建CTXCAT指数花了将近11分钟并使用了1.8GB的空间。
(注意:Oracle Text似乎在这里工作正常,但我不熟悉Text,我不能保证这不是对@NullUserException这些索引的不当使用。)
多列索引与索引联接
对于您在编辑中描述的情况,通常 在(A,B)上使用索引和在A和B上加入单独的索引之间没有显着差异。我构建了一些测试使用与您描述的数据类似的数据,索引连接只需要7个一致的获取而不是多列索引的2个一致获取。
之所以这样,是因为Oracle以块的形式检索数据。块通常为8K,并且索引块已经排序,因此您可以在几个块中拟合500到2000个值。如果您担心性能,通常读取和写入块的IO是唯一重要的。 Oracle是否必须将几千行连接在一起是一个无关紧要的CPU时间。
但是,这不适用于Oracle Text索引。您可以使用btree索引(“位图和”?)加入CONTEXT索引,但性能很差。
答案 1 :(得分:1)
我在group_id
上放了一个索引,看看它是否足够好。您没有说我们正在讨论的行数或您需要的性能。
请记住,处理谓词的顺序不一定是您在查询中编写谓词的顺序。除非你有真正的理由,否则不要试图超越优化器。
答案 2 :(得分:1)
简短版本:没有必要这样做。查询优化器足够聪明,可以决定选择数据的最佳方式。只需在group_id
上创建一个btree索引,即:
CREATE INDEX my_group_idx ON my_table (group_id);
长版本:我创建了一个插入136行虚拟数据的脚本(testperf.sql
)。
DESC my_table;
Name Null Type
-------- -------- ---------
ID NOT NULL NUMBER(4)
GROUP_ID NUMBER(4)
TEXT CLOB
group_id
上有一个btree索引。要确保实际使用索引,请以dba用户身份运行:
EXEC DBMS_STATS.GATHER_TABLE_STATS('<YOUR USER HERE>', 'MY_TABLE', cascade=>TRUE);
以下是每个group_id
的行数和相应的百分比:
GROUP_ID COUNT PCT
---------------------- ---------------------- ----------------------
1 1 1
2 2 1
3 4 3
4 8 6
5 16 12
6 32 24
7 64 47
8 9 7
请注意,查询优化器只有在认为这是一个好主意时才会使用索引 - 也就是说,您要检索的行数达到一定比例。因此,如果您要求查询计划:
SELECT * FROM my_table WHERE group_id = 1;
SELECT * FROM my_table WHERE group_id = 7;
您将看到,对于第一个查询,它将使用索引,而对于第二个查询,它将执行全表扫描,因为当group_id = 7
WHERE group_id = Y AND text LIKE '%blah%'
时索引有太多行无效
现在,考虑一个不同的条件 - ctxsys.context
(因为我对SELECT * FROM my_table WHERE group_id = 1 AND text LIKE '%ipsum%';
不是很熟悉)。
group_id
查看查询计划,您会看到将使用SELECT * FROM my_table WHERE text LIKE '%ipsum%' AND group_id = 1;
上的索引。请注意,您的条件顺序并不重要:
group_id = 7
生成相同的查询计划。如果您尝试在SELECT * FROM my_table WHERE group_id = 7 AND text LIKE '%ipsum%';
上运行相同的查询,您将看到它返回到全表扫描:
{{1}}
请注意,Oracle每天自动收集统计信息(计划每晚和周末运行),以不断提高查询优化器的效率。简而言之,Oracle尽力优化优化器,因此您不必这样做。
答案 3 :(得分:0)
我手边没有Oracle实例进行测试,并且没有在Oracle中使用全文索引,但我通常使用内联视图获得了良好的性能,这可能是另一种选择到你想到的那种索引。当涉及 contains()时,以下语法是否合法?
此内联视图可以获取组43中行的PK值:
(
select T.pkcol
from T
where group = 43
)
如果组具有正常索引,并且没有低基数,则应该快速获取此设置。然后你会再次用T加入内集:
select * from T
inner join
(
select T.pkcol
from T
where group = 43
) as MyGroup
on T.pkcol = MyGroup.pkcol
where contains(text, '%blah%') > 0
希望优化器能够使用PK索引来优化连接,然后将包含谓词仅应用于组43行。