为什么有时计算 MIN 比计算 MAX 快很多
查询 SELECT MIN(TOTAL_AMOUNT) FROM CUSTOMER 查找 TOTAL_AMOUNT 上的现有索引中的值,并立即返回答案。但是,一个非常相似的查询 SELECT MAX(TOTAL_AMOUNT) FROM CUSTOMER 却需要耗费多得多的时间。执行计划指出优化器选择了扫描整个索引来计算 MAX。为什么?
在这种特殊情况下,没有更好的选择。TOTAL_AMOUNT 上的索引不允许反向扫描:
SELECT REVERSE_SCANS FROM SYSCAT.INDEXES WHERE
INDNAME = ''CUSTOMER_AMT''
REVERSE_SCANS
-------------
N
在删除索引并用选项 ALLOW REVERSE SCANS 重新创建它之后,这两个查询开始运行得一样快了。
CREATE INDEX CUSTOMER_AMT ON CUSTOMER(TOTAL_AMOUNT) ALLOW REVERSE SCANS
RUNSTATS ON TABLE MYSCHEMA.CUSTOMER FOR INDEX MYSCHEMA. CUSTOMER_AMT
缺省情况下,DB2 索引不允许反向扫描。
提示:每当您在 CREATE TABLE 语句中创建 PRIMARY KEY、FOREIGN KEY 或 UNIQUE 约束时,就会隐式地创建一个索引。该索引不允许反向扫描。
您可以覆盖缺省行为:
创建一个没有约束的表(或删除现有约束)。
创建适当的索引。
使用 ALTER TABLE SQL 语句创建约束。
例如:
ALTER TABLE CUSTOMER DROP PRIMARY KEY;
--or create a table not defining a primary key
CREATE UNIQUE INDEX CUSTOMER_ID ON CUSTOMER(ID) ALLOW REVERSE SCANS;
ALTER TABLE CUSTOMER ADD PRIMARY KEY(ID);
DB2 会给出一条警告,并重用第 2 步中创建的索引。
正如我们所见,在一些十分常见的情况下,允许反向扫描的索引是必要的。
消除不必要的连接
让我们考虑以下视图:
CREATE VIEW CUSTOMER_ORDER_LIST
AS
SELECT
CUSTOMER_ORDER.CUSTOMER_ID
CUSTOMER.LAST_NAME
CUSTOMER.FIRST_NAME
CUSTOMER.PHONE
CUSTOMER.EMAIL
CUSTOMER_ORDER.ORDER_DT
CUSTOMER_ORDER.AMOUNT
CUSTOMER_ORDER.STATUS
FROM CUSTOMER JOIN CUSTOMER_ORDER
ON CUSTOMER.ID = CUSTOMER_ORDER.CUSTOMER_ID
CUSTOMER_ORDER 表中的所有记录在 CUSTOMER 表中都有父记录。该业务规则是由触发器维护的,而不是由外键约束维护的。(不要问我为什么。我能说的就是我在生产数据库中已经很多次看到它了。)
考虑查询:
SELECT CUSTOMER_ID, ORDER_DT, AMOUNT, STATUS FROM CUSTOMER_ORDER_LIST
您可能会认为根本不需要访问 CUSTOMER 表,因为所有必需的信息都在 CUSTOMER_ORDER 表的视图中,对吗?
事实并非这样。出于某些原因,优化器选择访问 CUSTOMER 表上的索引:
Estimated Cost = 25693
Access Table Name = DB2INST1.CUSTOMER ID = 2,5
| #Columns = 1
| Index Scan: Name = SYSIBM.SQL021126111001110 ID = 3
| | Index Columns:
| | | 1: ID (Ascending)
| | #Key Columns = 0
| | | Start Key: Beginning of Index
| | | Stop Key: End of Index
| | Index-Only Access
| | Index Prefetch: Eligible 199
| Lock Intents
| | Table: Intent Share
| | Row : Next Key Share
Merge Join
| Access Table Name = DB2INST1.CUSTOMER_ORDER ID = 2,6
(这只是部分输出。)
究竟为什么要访问 CUSTOMER 表呢?优化器的选择实际上非常有道理:您可能轻易地删除了触发器,将一条违反引用完整性的记录插入 CUSTOMER_ORDER 表中,并重新创建了触发器。记录将保留在 CUSTOMER_ORDER 表中,这意味着存在这种情况:触发器不保证引用完整性。这就意味着优化器必须假设 CUSTOMER_ORDER 表中可能有一些记录在 CUSTOMER 表中没有匹配的记录,因此查找 CUSTOMER 表上的记录是必要的。
现在,让我们创建适当的约束,看看会发生什么:
ALTER TABLE CUSTOMER_ORDER ADD FOREIGN KEY(CUSTOMER_ID) REFERENCES CUSTOMER(ID)
如果有任何记录违反了该约束,那么这条语句就会失败。现在,优化器能消除不必要的连接,而且查询可以运行得更快:
Estimated Cost = 18067
Access Table Name = DB2INST1.CUSTOMER_ORDER ID = 2,6
| #Columns = 1
| Relation Scan
| | Prefetch: Eligible
| Lock Intents
| | Table: Intent Share
| | Row : Next Key Share
| Return Data to Application
| | #Columns = 1
Return Data Completion
正如我们所见,添加外键约束向优化器提供了一些非常有用的数据。优化器则向我们提供更有效的执行计划作为报答。