Oracle 11G - 插入索引的性能影响

Oracle 11G - Performance effect of indexing at insert

Objective

验证插入没有 PK/index 的记录是否真的比使用 PK/Index 插入记录更快。

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

环境

Windows 7 64 位 DELL Latitude core i7 2.8GHz 8G 内存和 SSD HDD
甲骨文 11G R2 64 位

背景

有人告诉我,不使用 PK/Index 插入记录并在插入后创建它们比使用 PK/Index 插入要快。

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

条件

SQL 冲洗:

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

问题

"insert witout PK/Index + PK/Index creation later" 真的比 "insert with PK/Index" 快吗?

我是不是在实验中犯了错误或者遗漏了一些条件?

使用 PK/Index

插入记录
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

Table DDL

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

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

这样想,

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

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

让我们看一个小例子,

数据库版本:

SQL> SELECT banner FROM v$version where ROWNUM =1;

BANNER
--------------------------------------------------------------------------------
Oracle Database 12c Enterprise Edition Release 12.1.0.1.0 - 64bit Production

OS : 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 更快。

当前的测试用例可能足以让您否决 "best practices"。涉及的变数太多,无法一概而论 "it's always best to leave the indexes enabled"。但您可能已经足够接近,可以说这对您的环境是正确的。

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

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

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

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

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

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

另一种方法是,如果您在 table 上有五个索引,则在加载它之后进行五次完整 table 扫描以构建索引。

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

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

编辑:

进一步的考虑 -- 您 运行 在删除索引时进行备份。现在,在紧急恢复之后,您必须有一个脚本来验证所有索引是否到位,当您的业务需要您的帮助才能使系统恢复正常时。

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