【问题标题】:Oracle 11G - Performance effect of indexing at insertOracle 11G - 插入时索引的性能影响
【发布时间】:2015-07-02 03:12:15
【问题描述】:

目标

验证是否插入没有 PK/index 的记录加上稍后创建比插入 PK/Index 更快。

注意
这里的重点不是索引需要更多时间(很明显),而是总成本(Insert without index + create index)高于(Insert with index)。因为我被教导在没有索引的情况下插入并稍后创建索引,因为它应该更快。

环境

DELL Latitude core i7 2.8GHz 8G 内存和 SSD HDD 上的 Windows 7 64 位
Oracle 11G R2 64 位

背景

我被告知插入没有 PK/Index 的记录并在插入后创建它们会比使用 PK/Index 插入更快。

然而,使用 PK/Index 插入 100 万条记录实际上比稍后创建 PK/Index 快,大约 4.5 秒对 6 秒,下面的实验。通过将记录增加到 300 万条(999000 -> 2999000),结果是一样的。

条件

  • DDL 表如下。一个用于数据和数据的大文件表空间 指数。
    (测试了一个单独的索引表空间,结果相同,整体性能较差)
  • 每次运行前冲洗缓冲液/线轴。
  • 每次运行 3 次实验并确保结果 是相似的。

要刷新的 SQL:

ALTER SYSTEM CHECKPOINT;
ALTER SYSTEM FLUSH SHARED_POOL;
ALTER SYSTEM FLUSH BUFFER_CACHE;

问题

“在没有 PK/Index + PK/Index 创建之后插入”实际上是否比“使用 PK/Index 插入”更快?

我是否在实验中犯了错误或遗漏了某些条件?

使用 PK/索引插入记录

TRUNCATE TABLE TBL2;
ALTER TABLE TBL2 DROP CONSTRAINT PK_TBL2_COL1 CASCADE;
ALTER TABLE TBL2 ADD  CONSTRAINT PK_TBL2_COL1 PRIMARY KEY(COL1) ;

SET timing ON
INSERT INTO TBL2
SELECT i+j, rpad(TO_CHAR(i+j),100,'A')
FROM (
  WITH DATA2(j) AS (
      SELECT 0 j FROM DUAL
      UNION ALL
      SELECT j+1000 FROM DATA2 WHERE j < 999000
  )
  SELECT j FROM DATA2
),
(
  WITH DATA1(i) AS (
      SELECT 1 i FROM DUAL
      UNION ALL
      SELECT i+1 FROM DATA1 WHERE i < 1000
  )
  SELECT i FROM DATA1
);
commit;

1,000,000 rows inserted.
Elapsed: 00:00:04.328 <----- Insert records with PK/Index

插入不带 PK/Index 的记录并在之后创建

TRUNCATE TABLE TBL2;
ALTER TABLE &TBL_NAME DROP CONSTRAINT PK_TBL2_COL1 CASCADE;

SET TIMING ON
INSERT INTO TBL2
SELECT i+j, rpad(TO_CHAR(i+j),100,'A')
FROM (
  WITH DATA2(j) AS (
      SELECT 0 j FROM DUAL
      UNION ALL
      SELECT j+1000 FROM DATA2 WHERE j < 999000
  )
  SELECT j FROM DATA2
),
(
  WITH DATA1(i) AS (
      SELECT 1 i FROM DUAL
      UNION ALL
      SELECT i+1 FROM DATA1 WHERE i < 1000
  )
  SELECT i FROM DATA1
);
commit;
ALTER TABLE TBL2 ADD CONSTRAINT PK_TBL2_COL1 PRIMARY KEY(COL1) ;

1,000,000 rows inserted.
Elapsed: 00:00:03.454 <---- Insert without PK/Index

table TBL2 altered.
Elapsed: 00:00:02.544 <---- Create PK/Index

表 DDL

CREATE TABLE TBL2 (
    "COL1" NUMBER,
    "COL2" VARCHAR2(100 BYTE),
    CONSTRAINT "PK_TBL2_COL1" PRIMARY KEY ("COL1")
) TABLESPACE "TBS_BIG" ;

【问题讨论】:

  • @David Costa 如果你可以看看...

标签: oracle indexing insert database-performance oracle11gr2


【解决方案1】:

当前的测试用例可能足以让您否决“最佳实践”。涉及的变量太多,无法一概而论地声明“始终最好启用索引”。但您可能已经足够接近,可以说这对您的环境来说是正确的。

以下是测试用例的一些注意事项。我已将其设为社区 wiki,希望其他人能将其添加到列表中。

  1. 直接路径插入。 直接路径写入使用不同的机制,并且工作方式可能完全不同。直接路径插入通常比常规插入快得多,尽管它们有一些复杂的限制(例如,必须禁用触发器)和缺点(不会立即备份数据)。它影响这种情况的一种特殊方式是索引的 NOLOGGING 仅适用于索引创建期间。因此,即使使用直接路径插入,启用的索引也将始终生成 REDO 和 UNDO。
  2. 并行性。大型插入语句通常受益于并行 DML。通常在需要超过几秒的时间之前不必担心批量加载的性能,此时并行性开始发挥作用。
  3. 位图索引不适用于大型 DML。 使用位图索引插入或更新表会锁定整个表并导致灾难性的性能。将测试用例限制为 b 树索引可能会有所帮助。
  4. 添加alter system switch logfile;? 日志文件切换有时会导致性能问题。如果测试都以空日志文件开始,那么测试会更加一致。
  5. 将数据生成逻辑移到一个单独的步骤中。 分层查询对于生成数据很有用,但它们可能有自己的性能问题。最好在中间表中创建来保存结果,然后只测试将中间表插入到最终表中。

【讨论】:

    【解决方案2】:

    确实,如果您不必同时修改一个或多个索引并且可能还执行约束检查,那么修改表会更快,但如果您必须添加这些索引,这在很大程度上也无关紧要。您必须考虑对您希望实现的系统进行全面更改,而不仅仅是其中的一个部分。

    显然,如果您要向已经包含数百万行的表中添加单行,那么删除和重建索引将是愚蠢的。

    但是,即使您有一个完全空的表,您要在其中添加几百万行,将索引推迟到之后仍然会比较慢。

    这样做的原因是这样的插入最好使用直接路径机制执行,当您使用直接路径插入到带有索引的表中时,会构建临时段,其中包含构建索引所需的数据(数据加上rowids)。如果这些临时段比您刚刚加载的表小得多,那么它们的扫描和构建索引也会更快。

    如果表上有五个索引,则另一种方法是在加载后进行五次全表扫描以构建索引。

    显然这里涉及到巨大的灰色区域,但做得很好:

    1. 质疑权威和一般经验法则,以及
    2. 运行实际测试以确定您自己案例中的事实。

    编辑:

    其他注意事项 - 在删除索引时运行备份。现在,在紧急恢复之后,您必须有一个脚本来验证所有索引是否都已到位,而此时您需要竭尽全力恢复系统。

    此外,如果您确定在批量加载期间不维护索引,请不要删除索引 - 而是禁用它们。这保留了索引存在和定义的元数据,并允许更简单的重建过程。请注意不要通过截断表意外地重新启用索引,因为这会使禁用的索引再次启用。

    【讨论】:

      【解决方案3】:

      在将数据插入具有索引的表中时,Oracle 必须做更多的工作。一般来说,不带索引的插入比带索引的插入要快。

      这样想,

      • 在没有特定行顺序的常规堆组织表中插入行很简单。找一个有足够空闲空间的表块,随机放置行。

      • 但是,当表上有索引时,还有很多工作要做。为索引添加新条目并不是那么简单。它必须遍历索引块才能找到特定的叶节点,因为新条目不能成为 any 块。一旦找到正确的叶节点,它就会检查是否有足够的可用空间,然后创建新条目。如果没有足够的空间,则必须拆分节点并将新条目分配到旧节点和新节点。因此,所有这些工作都是开销,并且总体上会消耗更多时间。

      让我们看一个小例子,

      数据库版本:

      SQL> SELECT banner FROM v$version where ROWNUM =1;
      
      BANNER
      --------------------------------------------------------------------------------
      Oracle Database 12c Enterprise Edition Release 12.1.0.1.0 - 64bit Production
      

      操作系统: Windows 7, 8GB RAM

      带索引

      SQL> CREATE TABLE t(A NUMBER, CONSTRAINT PK_a PRIMARY KEY (A));
      
      Table created.
      
      SQL> SET timing ON
      SQL> INSERT INTO t SELECT LEVEL FROM dual CONNECT BY LEVEL <=1000000;
      
      1000000 rows created.
      
      Elapsed: 00:00:02.26
      

      所以,它花了00:00:02.26。索引详情:

      SQL> column index_name format a10
      SQL> column table_name format a10
      SQL> column uniqueness format a10
      SQL> SELECT index_name, table_name, uniqueness FROM user_indexes WHERE table_name = 'T';
      
      INDEX_NAME TABLE_NAME UNIQUENESS
      ---------- ---------- ----------
      PK_A       T          UNIQUE
      

      无索引

      SQL> DROP TABLE t PURGE;
      
      Table dropped.
      
      SQL> CREATE TABLE t(A NUMBER);
      
      Table created.
      
      SQL> SET timing ON
      SQL> INSERT INTO t SELECT LEVEL FROM dual CONNECT BY LEVEL <=1000000;
      
      1000000 rows created.
      
      Elapsed: 00:00:00.60
      

      所以,它只需要00:00:00.60,与00:00:02.26相比更快。

      【讨论】:

      • 感谢您的回答。可能这个问题没有正确提出,我的错。我被告知,当加载大量数据时,在插入之前删除/隐藏索引,然后在插入之后重新创建索引,因为它应该更快。然而实验结果却相反。带索引的插入比(不带索引的插入+创建索引)快。我想知道这是否是真的。
      猜你喜欢
      • 2017-02-28
      • 2012-05-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-30
      • 2015-05-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多