item_type_1、item_type_2、... 上的复合索引(item、item_type)还是单列索引更好?

Shi*_*zou 5 index oracle database-design

我们需要根据某些业务逻辑来存储和查询三种类型的项目。

\n\n
create table a_table\n(\n   item_a   varchar2(30),\n   item_b   varchar2(16),\n   item_c   varchar2(2),\n   -- other columns\n);\n
Run Code Online (Sandbox Code Playgroud)\n\n

和索引

\n\n
create unique index idx_1 on a_table (item_a);\ncreate unique index idx_2 on a_table (item_b);\ncreate unique index idx_3 on a_table (item_c);\n
Run Code Online (Sandbox Code Playgroud)\n\n

特定类型的特定项目的数据将简单地读取为:

\n\n
-- reading item_a\nselect ... from a_table where item_a = \'...\';\n\n-- reading item_b\nselect ... from a_table where item_b = \'...\';\n\n-- ...\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果某项的类型为a,则它不能为b类型,因此每一行必须只有其中之一item_aitem_b或者item_c具有值,其他必须为 null。(这可以以任何方式强制执行,是否影响离子并不重要insert。)

\n\n

理论上,物品类型的数量可能会增加(也许将来可能会添加第四种物品类型)。

\n\n

这一事实推动了另一种解决方案,该解决方案也避免了丑陋的互斥(在给定示例中未检查)列:

\n\n
create table a_table\n(\n   item       varchar2(30) not null,\n   item_type  varchar2(10) not null,\n   -- other columns\n);\n\ncreate unique index idx_1 on a_table (item_type, item);\n\n-- reading item_X\nselect ... from a_table where item=\'...\' and item_type=\'item_X\';\n\n
Run Code Online (Sandbox Code Playgroud)\n\n

忽略第一个解决方案在项目类型数量增加时的明显缺点以及其他可能的设计缺陷,在第一种情况下访问(使用索引列选择行)表和访问表之间是否存在重要的性能差异正如第二个所做的那样

\n\n

item_a和的基数item_b可以是数百万,但 的基数item_c肯定小于 200。

\n\n

笔记

\n\n
    \n
  • item_a假设和item_b的选择次数相同item_c
  • \n
  • 在第一个解决方案中,考虑复合索引(item_c, item_a, item_b) 当且仅当它比三个单独的索引更好(性能方面),但不要忘记必须与第二个解决方案进行比较,其中两个索引列都将在条件中使用where:假设对于这两种情况,使用最佳的 \xe2\x80\x9c Correct\xe2\x80\x9d 索引。
  • \n
\n

Edu*_*vat 5

我认为,从性能角度来看,它会非常接近。

但有些想法:

  1. 一列索引不存储 NULL 值。所以个别索引会更小。复合索引(item_type,item)大小将大于各个索引的总和。在极端情况下,甚至索引的高度也可能不同。

  2. 三个单独索引中的两个是唯一的,因此与非唯一索引相比,它需要较少的一致性读取。

  3. 对于 item_c 列,oracle 可以构建精确的直方图。

  4. 在 (item_type, item ) 的情况下,oracle 将正确计算项目类型 A 和 B 的查询的基数,但对于“item_type='C' 和 item=:X”,它可能会低估。这可能不是问题,但在带有连接的复杂查询中可能会给出无效的计划。所以你需要检查一下。

  5. 将索引 (item_a,item_b,item_c) 与 (item_type, item ) 进行比较 - 就像其他人所说的那样,它几乎只适用于“item_a=:X”这样的查询,而当“item_b=:Y”时则不起作用。因此,当我们在 item_b 和 item_c 列上有相同数量的查询时,对第一种情况使用索引 (item_a,item_b,item_c) 是一个坏主意。

    create table hr.tt nologging
    as
    select case when mod(rownum,3) = 0 then rownum end AS item_a,
           case when mod(rownum,3) = 1 then rownum end AS item_b,
           case when mod(rownum,3) = 2 then round(dbms_random.value(1,200)) end AS item_c,
           o.* 
     from all_objects o, 
    (select * from dual connect by level < 100) d 
     where rownum <= 5e6; 

    create unique index hr.item_a_idx on hr.tt(item_a) nologging;

    create unique index hr.item_b_idx on hr.tt(item_b) nologging;      

    create index hr.item_c_idx on hr.tt(item_c) nologging;


    create table hr.tt2 nologging
    as
    select case when mod(rownum,3) in (0,1) then rownum
                else round(dbms_random.value(1,200)) 
                end AS item,
           case when mod(rownum,3) = 0 then 'A' 
                when mod(rownum,3) = 1 then 'B' 
                when mod(rownum,3) = 2 then 'C' end AS item_type,
           o.* 
     from all_objects o, 
    (select * from dual connect by level < 100) d 
     where rownum <= 5e6;

    create index hr.item_item_type_idx on hr.tt2(item_type, item) nologging;
Run Code Online (Sandbox Code Playgroud)

一些疑问

SYS@mydb> select * from hr.tt where item_a = 300;


------------------------------------------------------------------------------------------
| Id  | Operation                   | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |            |     1 |   197 |     3   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| TT         |     1 |   197 |     3   (0)| 00:00:01 |
|*  2 |   INDEX UNIQUE SCAN         | ITEM_A_IDX |     1 |       |     2   (0)| 00:00:01 |
------------------------------------------------------------------------------------------
Statistics
----------------------------------------------------------
...
          4  consistent gets
Run Code Online (Sandbox Code Playgroud)
SYS@mydb> select * from hr.tt2 where item_type = 'A'  and item = 300;

1 row selected.


--------------------------------------------------------------------------------------------------
| Id  | Operation                   | Name               | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |                    |     1 |   115 |     4   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| TT2                |     1 |   115 |     4   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | ITEM_ITEM_TYPE_IDX |     1 |       |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------

Statistics
----------------------------------------------------------
          5  consistent gets
Run Code Online (Sandbox Code Playgroud)

下面是优化器计算错误的示例(oracle 11.2)。优化器认为“INDEX RANGE SCAN”中只会有一行。实际上我们看到 8438 行。这两列上的扩展优化器统计信息可能有帮助,也可能没有帮助。

SYS@mydb> select * from hr.tt2 where item_type = 'C'  and item = 150;

8438 rows selected.


Execution Plan
----------------------------------------------------------
Plan hash value: 4063424880

--------------------------------------------------------------------------------------------------
| Id  | Operation                   | Name               | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |                    |     1 |   115 |     4   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| TT2                |     1 |   115 |     4   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | ITEM_ITEM_TYPE_IDX |     1 |       |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------

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

   2 - access("ITEM_TYPE"='C' AND "ITEM"=150)


Statistics
----------------------------------------------------------
          8  recursive calls
          0  db block gets
       8618  consistent gets
       . . .
       8438  rows processed

Run Code Online (Sandbox Code Playgroud)

下面是单个 item_c 列的相同情况。这里优化器计算是正确的 - 估计 8133 行,选择 8290 行。

SYS@mydb> select * from hr.tt where item_c = 150;

8290 rows selected.


Execution Plan
----------------------------------------------------------
------------------------------------------------------------------------------------------
| Id  | Operation                   | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |            |  8133 |   826K|  7737   (1)| 00:01:33 |
|   1 |  TABLE ACCESS BY INDEX ROWID| TT         |  8133 |   826K|  7737   (1)| 00:01:33 |
|*  2 |   INDEX RANGE SCAN          | ITEM_C_IDX |  8133 |       |    19   (0)| 00:00:01 |
------------------------------------------------------------------------------------------

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

   2 - access("ITEM_C"=150)


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       8499  consistent gets
       . . .
       8290  rows processed
Run Code Online (Sandbox Code Playgroud)