【问题标题】:Oracle: Full text search with conditionOracle:带条件的全文搜索
【发布时间】:2011-11-13 13:28:27
【问题描述】:

我创建了一个如下所示的 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_ids。

理想情况下,我宁愿只搜索带有group_id = 43 的项目,所以我想要这样的索引:

create index my_idx on my_table (group_id, text) indextype is ctxsys.context; 

有点像普通索引,因此可以为每个 group_id 进行单独的文本搜索。

有没有办法在 Oracle 中做这样的事情(如果这很重要,我正在使用 10g)?

编辑(澄清)

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

现在让我们考虑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) 上使用索引时一样。

【问题讨论】:

  • 我认为你不明白 contains(text, ...) 的实际作用。 contains() 不是您用来根据某个单词的出现来过滤结果的那种函数。它实际上会计算任何给定文本与您正在使用它的列的相关性分数。
  • 假设您有一行包含text = 'hello world'。当您执行where contains(text, 'hello') > 0 时,可能会或不包括此行。你确定这是你真正想要的吗?
  • @NullUserException:您能否在答案中解释contains(...)(和catsearch(...))实际上做了什么,如果他们中的任何一个进行全文搜索? (即,如果您在文本编辑器中使用“查找”,您通常会得到什么)。

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


【解决方案1】:

我手头没有要测试的 Oracle 实例,也没有使用 Oracle 中的全文索引,但我通常使用 内联视图 获得了良好的性能,这可能是一种替代方法到您想到的那种索引。当涉及 contains() 时,以下语法是否合法?

这个内联视图让您获得第 43 组中行的 PK 值:

             (
             select T.pkcol
             from T
             where group = 43
             )

如果 group 有一个正常的索引,并且没有低基数,那么获取这个 set 应该很快。然后,您将再次使用 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 索引来优化连接,然后将 contains 谓词仅应用于组 43 行。

【讨论】:

  • 嗨,蒂姆,我看不出它如何仅在第 43 组的行上使用文本索引?暂时忘记oracle文本,如果我在说列AB上有单独的索引,如果我想做select ... where A = x and B = y我必须要么(1)使用A索引并检查所有B项目或 (2) 使用 B 索引并检查所有 A 项目。要使此查询有效,您确实需要在(A,B)(或(B,A))上建立索引。我看不出 Oracle Text 如何让这一现实有所不同?
  • @Clinton:如果在应用 where 条件之前执行连接,那你就错了;如果在应用 where 条件后执行连接,那么您是对的。这应该很容易找出来。只需在 groupid 上放一个索引。
  • @Tim:像这样添加内联视图通常不会产生任何影响。 Oracle 可以合并内联视图,或将谓词推入内联视图。这种行为不遵循 SQL 标准,并导致一些有趣的数据转换问题,但通常对性能有很大帮助。正如其他人指出的那样,如果您以简单的方式编写查询,Oracle 可能会重写它以快速运行。如果您确实需要 Oracle 以特定顺序执行操作,则必须使用其他技巧或提示(例如将 ROWNUM 添加到内联视图中)。
【解决方案2】:

我会在group_id 上建立一个索引,看看这是否足够好。您没有说我们正在谈论多少行或您需要什么性能。

请记住,处理谓词的顺序不一定是您在查询中编写它们的顺序。除非您有真正的理由,否则不要试图智取优化器。

【讨论】:

  • 真的。优化器应该能够估计在 CONTEXT 索引和 B-TREE 索引之间最适合使用的索引。统计数据将有助于选择一个好的执行计划。
【解决方案3】:

短版:没有必要这样做。查询优化器足够聪明,可以决定选择数据的最佳方式。只需在group_id 上创建一个btree 索引,即:

CREATE INDEX my_group_idx ON my_table (group_id); 

长版:我创建了一个脚本 (testperf.sql),它插入了 136 行虚拟数据。

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%';

请注意,Oracle 每天都会自动收集统计信息(它计划在每晚和周末运行),以不断提高查询优化器的效率。简而言之,Oracle 会尽最大努力优化优化器,因此您不必这样做。

【讨论】:

    【解决方案4】:

    Oracle 文本

    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
    

    这可能是最快的方法。对类似于 A 和 B 场景的 120MB 随机文本使用上述查询只需要 18 次一致的获取。但不利的一面是,创建 CTXCAT 索引花费了将近 11 分钟并使用了 1.8GB 的​​空间。

    (注意:Oracle Text 在这里似乎可以正常工作,但我对 Text 不熟悉,我无法判断这不是像 @NullUserException 所说的那样不恰当地使用这些索引。)

    多列索引与索引连接

    对于您在编辑中描述的情况,通常在 (A,B) 上使用索引和在 A 和 B 上加入单独的索引之间没有显着差异。我构建了一些测试具有与您描述的数据相似的数据,并且索引连接只需要 7 次一致的获取,而多列索引需要 2 次一致的获取。

    这是因为 Oracle 以块的形式检索数据。一个块通常是 8K,并且一个索引块已经排序,因此您可能可以将 500 到 2000 个值放在几个块中。如果您担心性能,通常读取和写入块的 IO 是唯一重要的事情。 Oracle 是否必须将几千行连接在一起是一个无关紧要的 CPU 时间。

    但是,这不适用于 Oracle Text 索引。您可以将 CONTEXT 索引与 btree 索引(“位图和”?)连接起来,但性能很差。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多