【问题标题】:Constant-time index for string column on Oracle databaseOracle 数据库中字符串列的常量时间索引
【发布时间】:2015-11-11 07:43:49
【问题描述】:

我有一个订单表。该表属于多租户应用,因此同一个表中有多个商家的订单。该表存储数亿条记录。这个问题有两个相关的专栏:

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

我想知道是否有一个有效的索引来做以下事情:

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

更多信息:

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

【问题讨论】:

  • merchantid, transactionid上创建普通索引?我怀疑你在很大程度上想太多了......
  • 除了@Ben 的建议之外,我还要在merchantid 和transactionid 列上创建一个唯一约束。然后将创建一个索引来支持约束(并且取决于您创建约束的方式 - 延迟的、未验证的等 - 它可能是唯一的或非唯一的)。
  • 但这不是原因;您对在merchantid = :val1 and transactionid = :val2 类型查询中请求行需要多长时间有时间限制吗?另外,您是否尝试过创建沼泽标准约束(和索引),并确定需要多长时间?
  • 我的开发环境中有一个表,有 3M 行,其唯一的 b 树索引深度为 2。如果它大 100 倍(300M 行),则级别可能会增加到 3。这有什么大不了的吗?
  • 你为什么不尝试在你的表上创建约束 + 索引并亲自看看呢?老实说,我以前从不需要担心索引查找是否是“恒定时间”;特别是因为您很可能会考虑更大的问题,例如逻辑 IO 与物理 IO(您的索引可能会被缓存,但您的表不太可能缓存,除非您要查询的数据位于足够小的块集中)。

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


【解决方案1】:

哈希集群可以提供 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>

寻找最佳哈希键,权衡

为了获得最佳读取性能,所有哈希冲突都应放在一个块中(所有 Oracle I/O 都在每个块中完成,通常为 8K)。获得理想的存储是很棘手的,需要知道哈希算法、存储大小(与块大小不同)和哈希键的数量(桶)。 Oracle 有一个默认算法和大小,因此可以只关注一个属性,即哈希键的数量。

更多的哈希键导致更少的冲突。这对 TABLE ACCESS HASH 性能有好处,因为只有一个块要读取。以下是不同 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-tree 索引。

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

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

在这种情况下,O(log n) 优于 O(1)。

但是为什么?

哈希集群性能不佳可能是 Oracle 试图简化事物并隐藏使哈希集群正常工作所需的那种细节的牺牲品。集群很难正确设置和使用,而且无论如何也很少提供显着的好处。在过去的几十年里,甲骨文并没有在他们身上投入太多精力。

评论者正确地认为简单的 b 树索引是最好的。但不清楚为什么这应该是真的,考虑一下数据库中使用的算法是件好事。

【讨论】:

    猜你喜欢
    • 2015-12-08
    • 1970-01-01
    • 2012-11-24
    • 1970-01-01
    • 2011-03-08
    • 1970-01-01
    • 2018-07-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多