Oracle:带条件的全文搜索

时间:2011-09-09 06:54:50

标签: sql oracle indexing full-text-indexing oracle-text

我创建了一个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)?

修改(澄清)

考虑一个包含一百万行的表和以下两列,AB,这两个都是数字。假设有{500}个不同的A值和2000个B的不同值,每行都是唯一的。

现在让我们考虑select ... where A = x and B = y

AB上的索引据我所知,在B上进行索引搜索,这将返回500个不同的行,然后执行加入/扫描这些行。在任何情况下,至少需要查看500行(除了数据库是幸运的,并提前找到所需的行。

尽管(A,B)上的索引更有效,但它在一个索引搜索中找到一行。

group_id上放置单独的索引,我认为文本只留下了两个选项。

(1)使用group_id索引,并扫描文本的所有结果行 (2)使用文本索引,并扫描group_id的所有结果行 (3)使用两个索引,并进行连接。

我想要:

(4)使用(group_id, "text")索引查找特定group_id下的文本索引,并扫描该文本索引以查找我需要的特定行/行。不需要扫描和检查或加入,就像在(A,B)上使用索引一样。

4 个答案:

答案 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行。