Oracle数据库中字符串列的常量索引

时间:2015-08-18 11:33:27

标签: oracle database-design indexing hashmap database-performance

我有一张订单表。该表属于多租户应用程序,因此同一表中有来自多个商家的订单。该表存储了数亿条记录。此问题有两个相关列:

  • MerchantID ,一个存储商家唯一ID的整数
  • TransactionID ,一个标识交易的字符串

我想知道是否有一个有效的索引来执行以下操作:

  • 对每个商家ID 交易ID 实施唯一约束。约束应在常量时间中强制执行。
  • 在两列上执行精确匹配的常量时间查询(例如,SELECT * FROM <table> WHERE TransactionID = 'ff089f89feaac87b98a' AND MerchantID = 24

更多信息:

  1. 我正在使用Oracle 11g。也许this Oracle article与我的问题相关?
  2. 我无法更改列的数据类型。
  3. 常量时间表示以O(1)时间复杂度执行的索引。就像一个hashmap。

1 个答案:

答案 0 :(得分:3)

哈希群集可以提供O(1)访问时间,但不提供O(1)约束执行时间。但是,实际上,散列簇的常量访问时间比常规b-tree索引的O(log N)访问时间更差。此外,群集更难以配置,并且在某些操作中无法很好地扩展。

创建哈希群集

drop table orders_cluster;
drop cluster cluster1;

create cluster cluster1
(
    MerchantID number,
    TransactionID varchar2(20)
)
single table hashkeys 10000; --This number is important, choose wisely!

create table orders_cluster
(
    id number,
    MerchantID number,
    TransactionID varchar2(20)
) cluster cluster1(merchantid, transactionid);

--Add 1 million rows.  20 seconds.
begin
    for i in 1 .. 10 loop
        insert into orders_cluster
        select rownum + i * 100000, mod(level, 100)+ i * 100000, level
        from dual connect by level <= 100000;
        commit;
    end loop;
end;
/

create unique index orders_cluster_idx on orders_cluster(merchantid, transactionid);

begin
    dbms_stats.gather_table_stats(user, 'ORDERS_CLUSTER');
end;
/

创建常规表(用于比较)

drop table orders_table;

create table orders_table
(
    id number,
    MerchantID number,
    TransactionID varchar2(20)
) nologging;

--Add 1 million rows.  2 seconds.
begin
    for i in 1 .. 10 loop
        insert into orders_table
        select rownum + i * 100000, mod(level, 100)+ i * 100000, level
        from dual connect by level <= 100000;
        commit;
    end loop;
end;
/

create unique index orders_table_idx on orders_table(merchantid, transactionid);

begin
    dbms_stats.gather_table_stats(user, 'ORDERS_TABLE');
end;
/

跟踪示例

SQL * Plus Autotrace是一种快速查找解释计划和跟踪每个语句的I / O活动的方法。 I / O请求的数量被标记为“一致获取”,并且是衡量完成工作量的一种不错的方式。此代码演示了如何为其他部分生成数字。查询通常需要多次运行以进行预热。

SQL> set autotrace on;
SQL> select * from orders_cluster where merchantid = 100001 and transactionid = '2';

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 621801084

------------------------------------------------------------------------------------
| Id  | Operation         | Name           | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |                |     1 |    16 |     1   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS HASH| ORDERS_CLUSTER |     1 |    16 |     1   (0)| 00:00:01 |
------------------------------------------------------------------------------------

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

   1 - access("MERCHANTID"=100001 AND "TRANSACTIONID"='2')


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         31  consistent gets
          0  physical reads
          0  redo size
        485  bytes sent via SQL*Net to client
        540  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          0  rows processed

SQL>

查找最佳Hashkeys,权衡

为获得最佳读取性能,所有散列冲突都应该适合一个块(所有Oracle I / O都是按块完成的,通常是8K)。获得理想的存储权限非常棘​​手,需要了解哈希算法,存储大小(与块大小不同)和散列键数(存储桶)。 Oracle有一个默认的算法和大小,因此可以只关注一个属性,即散列键的数量。

更多哈希密钥可以减少冲突。这对于TABLE ACCESS HASH性能很有用,因为只有一个块可供读取。以下是不同hashkey大小的一致获取数。为了比较,还包括索引访问。使用足够的hashkey,块数减少到最佳数量,1。

Method          Consistent Gets (for transactionid = 1, 20, 300, 4000, and 50000)
Index           4,  3,  3,  3,  3
Hashkeys 100    1, 31, 31, 31, 31
Hashkeys 1000   1,  3,  4,  4,  4
Hashkeys 10000  1,  1,  1,  1,  1

更多哈希键也会导致更多存储桶,浪费更多空间以及更慢的TABLE ACCESS FULL操作。

Table type      Space in MB
HeapTable       24MB
Hashkeys 100    26MB
hashkeys 1000   30MB
hashkeys 10000  81MB

要重现我的结果,请使用select * from orders_cluster where merchantid = 100001 and transactionid = '1';等示例查询,并将最后一个值更改为1,20,300,4000和50000。

效果比较

一致的获取是可预测且易于衡量的,但在一天结束时,只有挂钟时间很重要。令人惊讶的是,索引访问量增加了4倍  一致性获取仍然比最佳散列聚类场景更快。

--3.5 seconds for b-tree access.
declare
    v_count number;
begin
    for i in 1 .. 100000 loop
        select count(*)
        into v_count
        from orders_table
        where merchantid = 100000 and transactionid = '1';
    end loop;
end;
/

--3.8 seconds for hash cluster access.
declare
    v_count number;
begin
    for i in 1 .. 100000 loop
        select count(*)
        into v_count
        from orders_cluster
        where merchantid = 100000 and transactionid = '1';
    end loop;
end;
/

我也尝试使用变量谓词进行测试,但结果相似。

是否可以扩展?

不,哈希集群不会扩展。尽管TABLE ACCESS HASH的O(1)时间复杂度和INDEX UNIQUE SCAN的O(log n)时间复杂度,哈希簇似乎永远不会超越b树索引。

我尝试了上面的示例代码,包含1000万行。哈希集群的加载速度非常慢,并且仍然在SELECT性能上执行不足的索引。我试图将它扩展到1亿行,但插入需要11天。

好消息是b *树的规模很好。在上面的示例中添加1亿行只需要索引中的3个级别。我查看了所有DBA_INDEXES的大型数据库环境(数百个数据库和1 PB的数据) - 最差的索引只有7个级别。这是VARCHAR2(4000)列的病态索引。在大多数情况下,无论表大小如何,您的b树索引都将保持浅。

在这种情况下,O(log n)击败O(1)。

但为什么?

糟糕的散列集群性能可能是Oracle尝试简化事物并隐藏使散列集群运行良好所需的详细信息的牺牲品。集群很难正确设置和使用,并且很少提供显着的好处。在过去的几十年里,甲骨文并没有付出太多努力。

评论者认为简单的b-tree索引是最好的。但是为什么这应该是正确的并不明显,考虑数据库中使用的算法是好的。