【问题标题】:Oracle where and indexOracle 位置和索引
【发布时间】:2020-07-11 13:36:52
【问题描述】:

我有这样的问题,这是我的数据结构

create table tab (
     id_tab integer not null,
     val1 integer,
     val2 integer,
     val3 integer,
     val4 integer,
     val5 integer,
     val6 integer,
     val7 integer,
     val8 integer,
     val9 integer,
     CONSTRAINT tab_pk PRIMARY KEY (id_tab)
  );
  
  create index val1_index on tab (val1);
  create index val2_index on tab (val2);
  create index val3_index on tab (val3);
  create index val4_index on tab (val4);
  create index val5_index on tab (val5);
  create index val6_index on tab (val6);
  create index val7_index on tab (val7);
  create index val8_index on tab (val8);
  create index val9_index on tab (val9);

  create procedure test1 as
  begin
    for x in 1..10000
    loop
      insert into tab(id_tab, val1, val2, val3, val4, val5, val6, val7, val8, val9)
      values ((select nvl(max(id_tab), 0) + 1 from tab), 
              decode(round(dbms_random.value(0,2)), 1, null, dbms_random.value(1,9)), 
              decode(round(dbms_random.value(0,2)), 1, null, dbms_random.value(1,9)), 
              decode(round(dbms_random.value(0,2)), 1, null, dbms_random.value(1,9)), 
              decode(round(dbms_random.value(0,2)), 1, null, dbms_random.value(1,9)), 
              decode(round(dbms_random.value(0,2)), 1, null, dbms_random.value(1,9)), 
              decode(round(dbms_random.value(0,2)), 1, null, dbms_random.value(1,9)),
              decode(round(dbms_random.value(0,2)), 1, null, dbms_random.value(1,9)), 
              decode(round(dbms_random.value(0,2)), 1, null, dbms_random.value(1,9)), 
              decode(round(dbms_random.value(0,2)), 1, null, dbms_random.value(1,9)));
    end loop;
  end;
  /

BEGIN
test1;
-- for my example, to find value:
insert into tab (id_tab, val1, val2, val3, val4, val5, val6, val7, val8, val9) 
values(-1, 3, 1, null, 5, 2, 1, 9, null, 1);
END;
/

现在我正在寻找结果

SELECT * FROM tab
where 
(decode(val1, 3, 1) = 1) and -- it's like: (val1 = 3 or (val1 is null and 3 is null) 
(decode(val2, 1, 1) = 1) and 
(decode(val3, null, 1) = 1) and -- it's like: (val1 = null or (val1 is null and null is null)
(decode(val4, 5, 1) = 1) and
(decode(val5, 2, 1) = 1) and
(decode(val6, 1, 1) = 1) and
(decode(val7, 9, 1) = 1) and
(decode(val8, null, 1) = 1) and
(decode(val9, 1, 1) = 1)

我有一个预期的结果:

问题是我有大约一百万个找到这样的组合,大约需要一个小时,问题是是否可以使用其他索引或以其他方式构造一个查询(在哪里)来搜索这样的组合省时吗?

这是一个例子: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=ed77f058517197d4468e143f9deab3e1

数据库:Oracle 11 标准版一

【问题讨论】:

  • 您可以尝试 FBIs(基于函数的索引),如果它们的参数是固定的,则每个 decode()s 一个。
  • 我不明白。 decode(val1, 3, 1) = 1 完全等价于val1 = 3,那你为什么要写得这么复杂——这也妨碍了在val1 上使用索引?如果这不是您的实际查询 - 如果真正的查询更复杂,并且使用 decode 确实有某种目的 - 然后向我们展示真正的查询片段,而不是完全没有意义的模型。
  • 3是输入参数,所以可以是NULL真正的问题是 - 谓词应该表现为NULL = NULL@mathguy
  • decode(val1, :x, 1) = 1 当 val1 为 null 且 :x 为 null 然后 1 或 val1 = :x 然后 1. (decode(val1, :x, 1) = 1 ) = (val1 = :x or (val1 is null and x is null))

标签: oracle indexing where-clause


【解决方案1】:

如果您在创建索引后对索引使用函数,它将被该函数抑制并变得无用(就像这个(decode(val1, 3, 1) = 1))。 另一方面,您不应该使用那么多索引。这不是最佳实践,您应该知道这一点。您创建的每个索引都会在磁盘上产生开销。

如果你坚持使用索引,你应该考虑创建一个函数来获取一些输入并根据你的条件对其进行解码,然后使用该函数创建索引。

Function based indexes

【讨论】:

  • 我就是这么做的。我在一列中添加了一个额外的索引列。 (看上面);)
  • 那么你不应该在你的WHERE 子句中对它们使用DECODE。这是您使用DECODE 抑制索引的地方。你应该在你的TABfunction 中实现它。
【解决方案2】:

好吧,您根本不应该创建任何这些索引,因为您的 where 子句会影响所有列。当您的 where 子句查找一个或多个特定列时,索引非常有效,但当您的表中的所有列都是谓词的一部分时,索引就不是很有效了。在这种情况下,CBO(基于成本的优化器)将始终使用表全扫描。

让我用你的数据模型向你展示一个例子

SQL> create table tab_example (
     id_tab integer not null,
     val1 integer,
     val2 integer,
     val3 integer,
     val4 integer,
     val5 integer,
     val6 integer,
     val7 integer,
     val8 integer,
     val9 integer,
     CONSTRAINT tab_example_pk PRIMARY KEY (id_tab)
  )
/

Table created.

SQL> create index val1_index on tab_example (val1);
  create index val2_index on tab_example (val2);
  create index val3_index on tab_example (val3);
  create index val4_index on tab_example (val4);
  create index val5_index on tab_example (val5);
  create index val6_index on tab_example (val6);
  create index val7_index on tab_example (val7);
  create index val8_index on tab_example (val8);
  create index val9_index on tab_example (val9);
  
  Index created.

SQL>
Index created.

SQL>
Index created.

SQL>
Index created.

SQL>
Index created.

SQL>
Index created.

SQL>
Index created.

SQL>
Index created.

SQL>

Index created.

我在范围内创建了一百万行的 procreate

SQL> create or replace procedure test1 as
begin
    for x in 1..1000000
    loop
      insert into tab_example(id_tab, val1, val2, val3, val4, val5, val6, val7, val8, val9)
      values ((select nvl(max(id_tab), 0) + 1 from tab_example), 
              decode(round(dbms_random.value(0,2)), 1, null, dbms_random.value(1,9)), 
              decode(round(dbms_random.value(0,2)), 1, null, dbms_random.value(1,9)), 
              decode(round(dbms_random.value(0,2)), 1, null, dbms_random.value(1,9)), 
              decode(round(dbms_random.value(0,2)), 1, null, dbms_random.value(1,9)), 
              decode(round(dbms_random.value(0,2)), 1, null, dbms_random.value(1,9)), 
              decode(round(dbms_random.value(0,2)), 1, null, dbms_random.value(1,9)),
              decode(round(dbms_random.value(0,2)), 1, null, dbms_random.value(1,9)), 
              decode(round(dbms_random.value(0,2)), 1, null, dbms_random.value(1,9)), 
              decode(round(dbms_random.value(0,2)), 1, null, dbms_random.value(1,9)));
    end loop;
  end;
  /

Procedure created.

SQL> set timing on 
SQL>  begin
  2  test1;
  3  insert into tab_example (id_tab, val1, val2, val3, val4, val5, val6, val7, val8, val9)
  4  values(-1, 3, 1, null, 5, 2, 1, 9, null, 1);
  5* end;
/

PL/SQL procedure successfully completed.

Elapsed: 00:08:09.34

如您所见,该过程的执行需要大量时间,主要是因为该操作涉及 9 个索引。请记住,针对表的任何 DML 操作都必须对索引执行相同的操作。在您的情况下,这些索引对您的查询没有任何帮助,因为就像我说的那样,您的谓词正在查看表中的每一列。

让我们计算统计数据并查看查询的解释计划

SQL> exec dbms_stats.gather_table_stats( 'MYUSER' , 'TAB_EXAMPLE' );

PL/SQL procedure successfully completed.

Elapsed: 00:00:04.19

SQL> set autotrace traceonly explain
SQL> SELECT * FROM tab_example
where
  2    3  (decode(val1, 3, 1) = 1) and -- it's like: (val1 = 3 or (val1 is null and 3 is null)
  4  (decode(val2, 1, 1) = 1) and
(decode(val3, null, 1) = 1) and -- it's like: (val1 = null or (val1 is null and null is null)
  5    6  (decode(val4, 5, 1) = 1) and
(decode(val5, 2, 1) = 1) and
(decode(val6, 1, 1) = 1) and
(decode(val7, 9, 1) = 1) and
  7    8    9   10  (decode(val8, null, 1) = 1) and
 11  (decode(val9, 1, 1) = 1);
Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
Plan hash value: 2592949226

---------------------------------------------------------------------------------
| Id  | Operation         | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |             |     1 |    23 |   709   (3)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| TAB_EXAMPLE |     1 |    23 |   709   (3)| 00:00:01 |
---------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter(DECODE("VAL1",3,1)=1 AND DECODE("VAL2",1,1)=1 AND
              DECODE("VAL4",5,1)=1 AND DECODE("VAL5",2,1)=1 AND DECODE("VAL6",1,1)=1
              AND DECODE("VAL7",9,1)=1 AND DECODE("VAL9",1,1)=1 AND
              DECODE(TO_CHAR("VAL3"),NULL,1)=1 AND DECODE(TO_CHAR("VAL8"),NULL,1)=1)

在我的例子中,查询几乎不需要任何内容​​,执行计划是正确的,给定谓词信息。我正在检查每一列,因此在这种情况下使用全表访问会更好。

SQL> SELECT * FROM tab_example
where
  2    3  (decode(val1, 3, 1) = 1) and -- it's like: (val1 = 3 or (val1 is null and 3 is null)
(decode(val2, 1, 1) = 1) and
(decode(val3, null, 1) = 1) and -- it's like: (val1 = null or (val1 is null and null is null)
(decode(val4, 5, 1) = 1) and
  4    5    6    7  (decode(val5, 2, 1) = 1) and
(decode(val6, 1, 1) = 1) and
(decode(val7, 9, 1) = 1) and
  8    9   10  (decode(val8, null, 1) = 1) and
(decode(val9, 1, 1) = 1) 11  ;

    ID_TAB       VAL1       VAL2       VAL3       VAL4       VAL5       VAL6       VAL7       VAL8       VAL9
---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
        -1          3          1                     5          2          1          9                     1

Elapsed: 00:00:00.21

如果我删除索引(主键使用的强制索引除外),让我们看看我需要多少时间来进行插入

SQL> declare
begin
for i in ( select index_name from dba_indexes where table_name = 'TAB_EXAMPLE' and index_name like 'VAL%' )
loop
        execute immediate ' drop index '||i.index_name||' ' ;
end loop;
end;
/
  2    3    4    5    6    7    8
PL/SQL procedure successfully completed.

Elapsed: 00:00:00.32

SQL> truncate table TAB_EXAMPLE ;

Table truncated.

Elapsed: 00:00:00.17

SQL> begin
test1;
insert into tab_example (id_tab, val1, val2, val3, val4, val5, val6, val7, val8, val9)
values(-1, 3, 1, null, 5, 2, 1, 9, null, 1);
end;
/  2    3    4    5    6

PL/SQL procedure successfully completed.

Elapsed: 00:02:41.01

总结:在构建物理数据模型时,正确的索引是一项很好的练习。索引所有列不是一个好的选择,也是一个糟糕的决定。在某些情况下,您可能需要一个 IOT(索引组织表),但我在这里没有看到这种情况,也没有使用这么多无用的索引。

希望对你有所帮助!

【讨论】:

    【解决方案3】:

    我在“TAB”表中添加了一个名为“HASH”的附加文本列。

    我是这样算的:

    VAL1||'*'||VAL2||'*'||val3||'*'||val4||'*'||val5||'*'||val6||'*'||val7||'*'||val8||'*'||val9
    

    我对文本列“HASH”进行了索引,工作时间从一个小时减少到几秒钟,因为搜索遵循一个索引......这里有什么问题吗?

    【讨论】:

    • 没问题,我本来打算建议这个,但你自己已经找到了。当然,您也必须重写 WHERE 子句,以“散列”输入变量。 (顺便说一句,在这种情况下不要说“哈希”,“列表聚合”会更好;哈希的技术含义与您所做的不同)。警告 - 如果您有太多列并且输入的数字太多,您的连接字符串可能会变得超过 4000 个字符,这将是一个问题。
    • one 问题@mathguy 是您依赖当前 NLS 设置进行 数字到字符串 的转换。这可能会改变,这将对搜索产生致命影响。所以我建议明确转换,例如to_char(val1,'S0999')||'x'||to_char(val2,'S0999')..。作为分隔符,您不应该使用可以出现在数字中的字符(- 不会引起任何问题,但如果您有负数,可能会混淆
    • 我是这么想的,我应用了“*”,我要更正了。
    • 就是这样。布拉沃@TomaszTomzik
    猜你喜欢
    • 1970-01-01
    • 2013-09-15
    • 2017-10-31
    • 1970-01-01
    • 2018-03-11
    • 2014-07-26
    • 1970-01-01
    • 2013-06-02
    • 2021-05-23
    相关资源
    最近更新 更多