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),结果是一样的。
条件
- table DDL 如下。一个大文件 table space 用于数据和
指数。
(测试了一个单独的索引 tablespace,结果相同但整体性能较差)
- 在每个 运行 之前刷新 buffer/spool。
- 运行每次实验3次,确定结果
是相似的。
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,希望其他人能将其添加到列表中。
- 直接路径插入。直接路径写入使用不同的机制,工作方式可能完全不同。直接路径插入通常比常规插入快得多,尽管它们有一些复杂的限制(例如,必须禁用触发器)和缺点(数据不会立即备份)。它影响这种情况的一种特殊方式是索引的 NOLOGGING 仅在索引创建期间应用。因此,即使使用直接路径插入,启用的索引也将始终生成 REDO 和 UNDO。
- 并行性。 大型插入语句通常受益于并行 DML。通常不值得担心大容量加载的性能,直到它花费超过几秒,这时并行性开始有用。
- 位图索引不适用于大型 DML。插入或更新带有位图索引的 table 可以锁定整个 table 并导致灾难性的表现。将测试用例限制为 b 树索引可能会有所帮助。
- 添加
alter system switch logfile;
? 日志文件切换有时会导致性能问题。如果测试都以空日志文件开始,测试会更加一致。
- 将数据生成逻辑移到一个单独的步骤中。 分层查询对于生成数据很有用,但它们可能有自己的性能问题。最好在中间创建 table 来保存结果,然后只测试将中间 table 插入最终 table.
确实,如果您不必同时修改一个或多个索引并且可能还执行约束检查,则修改 table 会更快,但如果您随后必须修改,这在很大程度上也无关紧要添加那些索引。您必须考虑您希望影响的系统的完整更改,而不仅仅是其中的一个部分。
显然,如果您要将单行添加到已经包含数百万行的 table 中,那么删除并重建索引将是愚蠢的。
但是,即使您有一个完全空的 table 并要向其中添加几百万行,将索引推迟到之后进行仍然会比较慢。
这样做的原因是这样的插入最好使用直接路径机制执行,并且当您使用直接路径插入到带有索引的 table 中时,会构建包含数据的临时段需要构建索引(数据加上 rowid)。如果这些临时段比您刚刚加载的 table 小得多,那么它们也将更快地扫描和构建索引。
另一种方法是,如果您在 table 上有五个索引,则在加载它之后进行五次完整 table 扫描以构建索引。
显然这里涉及到巨大的灰色地带,但做得很好:
- 质疑权威和一般经验法则,以及
- 运行实际测试以确定您自己的情况。
编辑:
进一步的考虑 -- 您 运行 在删除索引时进行备份。现在,在紧急恢复之后,您必须有一个脚本来验证所有索引是否到位,当您的业务需要您的帮助才能使系统恢复正常时。
此外,如果您绝对确定在批量加载期间不维护索引,请不要删除索引——而是禁用它们。这保留了索引存在和定义的元数据,并允许更简单的重建过程。请注意不要意外地通过 t运行cating table 重新启用索引,因为这会再次启用禁用的索引。
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),结果是一样的。
条件
- table DDL 如下。一个大文件 table space 用于数据和
指数。
(测试了一个单独的索引 tablespace,结果相同但整体性能较差) - 在每个 运行 之前刷新 buffer/spool。
- 运行每次实验3次,确定结果 是相似的。
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,希望其他人能将其添加到列表中。
- 直接路径插入。直接路径写入使用不同的机制,工作方式可能完全不同。直接路径插入通常比常规插入快得多,尽管它们有一些复杂的限制(例如,必须禁用触发器)和缺点(数据不会立即备份)。它影响这种情况的一种特殊方式是索引的 NOLOGGING 仅在索引创建期间应用。因此,即使使用直接路径插入,启用的索引也将始终生成 REDO 和 UNDO。
- 并行性。 大型插入语句通常受益于并行 DML。通常不值得担心大容量加载的性能,直到它花费超过几秒,这时并行性开始有用。
- 位图索引不适用于大型 DML。插入或更新带有位图索引的 table 可以锁定整个 table 并导致灾难性的表现。将测试用例限制为 b 树索引可能会有所帮助。
- 添加
alter system switch logfile;
? 日志文件切换有时会导致性能问题。如果测试都以空日志文件开始,测试会更加一致。 - 将数据生成逻辑移到一个单独的步骤中。 分层查询对于生成数据很有用,但它们可能有自己的性能问题。最好在中间创建 table 来保存结果,然后只测试将中间 table 插入最终 table.
确实,如果您不必同时修改一个或多个索引并且可能还执行约束检查,则修改 table 会更快,但如果您随后必须修改,这在很大程度上也无关紧要添加那些索引。您必须考虑您希望影响的系统的完整更改,而不仅仅是其中的一个部分。
显然,如果您要将单行添加到已经包含数百万行的 table 中,那么删除并重建索引将是愚蠢的。
但是,即使您有一个完全空的 table 并要向其中添加几百万行,将索引推迟到之后进行仍然会比较慢。
这样做的原因是这样的插入最好使用直接路径机制执行,并且当您使用直接路径插入到带有索引的 table 中时,会构建包含数据的临时段需要构建索引(数据加上 rowid)。如果这些临时段比您刚刚加载的 table 小得多,那么它们也将更快地扫描和构建索引。
另一种方法是,如果您在 table 上有五个索引,则在加载它之后进行五次完整 table 扫描以构建索引。
显然这里涉及到巨大的灰色地带,但做得很好:
- 质疑权威和一般经验法则,以及
- 运行实际测试以确定您自己的情况。
编辑:
进一步的考虑 -- 您 运行 在删除索引时进行备份。现在,在紧急恢复之后,您必须有一个脚本来验证所有索引是否到位,当您的业务需要您的帮助才能使系统恢复正常时。
此外,如果您绝对确定在批量加载期间不维护索引,请不要删除索引——而是禁用它们。这保留了索引存在和定义的元数据,并允许更简单的重建过程。请注意不要意外地通过 t运行cating table 重新启用索引,因为这会再次启用禁用的索引。