我同意 Tom Kyte 关于全表扫描不是邪恶来源的观点,但只有在表相对较小的情况下。因此,拥有此类表的附加索引是多余的。但是,不应将具有 100.000 条记录的表视为小表,但此类表的解释计划显示已执行表全扫描。所以,我在本地安装了 Oracle 的笔记本电脑上做了一个小实验:
1)首先,创建my_table:
CREATE TABLE my_table(
"ID" NUMBER NOT NULL ENABLE,
"INVOICE_NO" VARCHAR2(10),
CONSTRAINT "test _PK" PRIMARY KEY ("ID")
)
2)然后,为 invoice_no 列创建索引(因为将使用它进行过滤):
CREATE INDEX "my_table_index1" ON my_table (invoice_no)
3)然后,插入100K记录:
DECLARE
mod_val NUMBER;
BEGIN
FOR i IN 1..100000 LOOP
mod_val := MOD(i,6);
IF (mod_val = 0) THEN
INSERT INTO my_table (ID, INVOICE_NO) VALUES (i, '5570-110');
ELSIF (mod_val = 1) THEN
INSERT INTO my_table (ID, INVOICE_NO) VALUES (i, '5570-111');
ELSIF (mod_val = 2) THEN
INSERT INTO my_table (ID, INVOICE_NO) VALUES (i, '5570-112');
ELSIF (mod_val = 3) THEN
INSERT INTO my_table (ID, INVOICE_NO) VALUES (i, '5570-113');
ELSIF (mod_val = 4) THEN
INSERT INTO my_table (ID, INVOICE_NO) VALUES (i, '5570-114');
ELSIF (mod_val = 5) THEN
INSERT INTO my_table (ID, INVOICE_NO) VALUES (i, '5570-115');
END IF;
END LOOP;
COMMIT;
END;
4)然后更新一条随机记录(只是为了强调选择):
BEGIN
UPDATE my_table SET INVOICENO = 'exception' WHERE id = 50000;
COMMIT;
END;
5)然后使用解释计划执行选择:
EXPLAIN PLAN FOR
SELECT * FROM my_table WHERE invoice_no = 'exception';
6)然后抓取统计数据:
SELECT * FROM TABLE(dbms_xplan.display);
7)并得到结果:
"PLAN_TABLE_OUTPUT"
"Plan hash value: 3804444429"
" "
"------------------------------------------------------------------------------"
"| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |"
"------------------------------------------------------------------------------"
"| 0 | SELECT STATEMENT | | 83256 | 1626K| 103 (1)| 00:00:02 |"
"| 1 | TABLE ACCESS FULL| MY_TABLE | 83256 | 1626K| 103 (1)| 00:00:02 |"
"------------------------------------------------------------------------------"
" "
"Note"
"-----"
" - dynamic sampling used for this statement (level=2)"
结论:很奇怪,闻起来很“神奇”,为什么 Oracle 决定不使用 invoice_no 字段上的索引并扫描 83256 条记录?我同意我的笔记本电脑没有因并发用户超载,表的大小不是很大(包含数字和 varchars),但是,我不喜欢这种魔法,想知道这种行为的原因:)
UPDATE : I just added some dummy value (see below) to invoice_no field to all records - just to increase the size of the table, however, table full scan remains: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
UPDATE2:我也执行了分析表,但结果是一样的:
ANALYZE TABLE my_table COMPUTE STATISTICS;
UPDATE3:试图强制使用索引,但结果是一样的(也许语法错误?):
EXPLAIN PLAN FOR
SELECT /*+ INDEX(my_table my_table_index1) */ * FROM my_table t WHERE invoice_no = 'exception'
UPDATE4:最后,能够“告诉 Oracle”使用索引 - 执行新的收集表统计程序:
BEGIN
DBMS_STATS.GATHER_TABLE_STATS ( OWNNAME=>user
, TABNAME=>'my_table');
END;
这是解释计划的输出:
"--------------------------------------------------------------------------------------"
"| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |"
"-----------------------------------------------------------------------------------------------"
"| 0 | SELECT STATEMENT | | 1 | 294 | 5 (0)| 00:00:01 |"
"| 1 | TABLE ACCESS BY INDEX ROWID| MY_TABLE | 1 | 294 | 5 (0)| 00:00:01 |"
"|* 2 | INDEX RANGE SCAN | my_table_index1 | 1 | | 4 (0)| 00:00:01 |"
"-----------------------------------------------------------------------------------------------"
" "
"Predicate Information (identified by operation id):"
"---------------------------------------------------"
" "
" 2 - access(""INVOICE_NO""='exception')"
因此,Oracle 似乎决定在某个时间点使用某种查询方法,并且即使情况发生变化也不会更新它。我同意这一点,但奇怪的是为什么当我刚刚创建、插入和执行选择时它没有为这个测试用例选择正确的方法。我们是否总是至少在开始时执行 DBMS_STATS.GATHER_TABLE_STATS 来告诉 Oracle 使用最佳查询方法?