FILTER(过滤)操作是优化器的核心执行路径之一,
逻辑上与 Nested Loop(嵌套循环)高度相似—— 都是 “外层驱动 + 内层过滤”,但
FILTER更侧重 “先筛选外层结果集,再用结果集过滤内层数据”,而索引是让
FILTER/Nested Loop 高效执行的关键(减少内层扫描范围)。| 操作类型 | 执行逻辑 | 适用场景 | 性能关键 |
|---|---|---|---|
| Nested Loop | 外层每行 → 内层索引查找匹配行(回表) | 外层结果集小(<1000 行)+ 内层有索引 | 内层索引的聚簇因子低、选择性高 |
| FILTER | 外层结果集 → 内层过滤(通常是
EXISTS/IN),仅返回满足条件的行 |
外层小 + 内层需 “存在性验证” | 内层索引覆盖过滤条件(无需回表) |
EXISTS触发 FILTER 操作):select a.clientno, a.customername, case when exists (select 1 from cdk_freezesdeductsmsg b where a.clientno = b.customerno and b.freezestatus = '1') then '是' else '否' end as frozen_state from cr_client a where a.comcode = '1001'; -- 筛选1001机构的客户,约5000行
cdk_freezesdeductsmsg全表扫描(约 100 万行),外层 5000 行 → 总扫描量 = 5000×100 万 = 50 亿行,性能极差。----------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| ----------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 5000 | 195K| 8900 (1)| | 1 | FILTER | | | | | |* 2 | TABLE ACCESS FULL| CR_CLIENT | 5000 | 195K| 100 (1)| |* 3 | TABLE ACCESS FULL| CDK_FREEZESDEDUCTSMSG | 1 | 20 | 1780 (1)| ----------------------------------------------------------------------------------
EXISTS触发的 FILTER 操作,内层只需 “验证是否存在匹配行”,无需返回数据 → 给内层过滤字段
(customerno, freezestatus)建
复合索引(覆盖过滤条件,避免回表)。-- 1. 客户表(外层表,模拟5000行数据) CREATE TABLE cr_client ( clientno VARCHAR2(20) PRIMARY KEY, -- 客户编号 customername VARCHAR2(50), -- 客户名称 comcode VARCHAR2(4) -- 机构编码 ) TABLESPACE USERS; -- 插入测试数据(1001机构5000行) INSERT INTO cr_client SELECT 'C'||LPAD(LEVEL, 10, '0'), -- clientno: C0000000001~C000005000 '客户'||LEVEL, '1001'FROM DUALCONNECT BY LEVEL <= 5000; COMMIT; -- 2. 冻结记录表(内层表,模拟100万行数据) CREATE TABLE cdk_freezesdeductsmsg ( id NUMBER PRIMARY KEY, customerno VARCHAR2(20), -- 关联客户表clientno freezestatus VARCHAR2(1), -- 冻结状态:1=冻结,0=解冻 freezetime DATE -- 冻结时间 ) TABLESPACE USERS; -- 插入测试数据(100万行,其中1000行是1001机构客户的冻结记录) INSERT INTO cdk_freezesdeductsmsg SELECT LEVEL, 'C'||LPAD(MOD(LEVEL, 5000)+1, 10, '0'), -- 关联客户表 CASE WHEN LEVEL <= 1000 THEN '1' ELSE '0' END, -- 前1000行冻结 SYSDATE - LEVEL/10000FROM DUALCONNECT BY LEVEL <= 1000000; COMMIT; -- 收集统计信息(让优化器识别数据分布) EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'CR_CLIENT', CASCADE=>TRUE); EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'CDK_FREEZESDEDUCTSMSG', CASCADE=>TRUE);
-- 开启执行计划显示 SET AUTOTRACE ON EXPLAIN; -- 原SQL(无索引) SELECT a.clientno, a.customername, CASE WHEN EXISTS ( SELECT 1 FROM cdk_freezesdeductsmsg b WHERE a.clientno = b.customerno AND b.freezestatus = '1' ) THEN '是' ELSE '否' END AS frozen_state FROM cr_client aWHERE a.comcode = '1001'; -- 关闭执行计划 SET AUTOTRACE OFF;
-----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
-----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5000 | 195K| 8900 (1)|
| 1 | FILTER | | | | |
|* 2 | TABLE ACCESS FULL| CR_CLIENT | 5000 | 195K| 100 (1)|
|* 3 | TABLE ACCESS FULL| CDK_FREEZESDEDUCTSMSG | 1 | 20 | 1780 (1)|
-----------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("A"."COMCODE"='1001')
3 - filter("B"."CUSTOMERNO"=:B1 AND "B"."FREEZESTATUS"='1')
FILTER操作:外层
CR_CLIENT全表扫描(5000 行),内层
CDK_FREEZESDEDUCTSMSG全表扫描(100 万行);EXISTS的过滤条件,创建
复合索引(覆盖
customerno+freezestatus,无需回表):-- 优化:创建内层过滤字段的复合索引(选择性高的字段在前) CREATE INDEX IDX_CDK_FREEZES_CUST_STATUS ON cdk_freezesdeductsmsg(CUSTOMERNO, FREEZESTATUS)TABLESPACE USERS; -- 重新收集统计信息EXEC DBMS_STATS.GATHER_INDEX_STATS(USER, 'IDX_CDK_FREEZES_CUST_STATUS');
SET AUTOTRACE ON EXPLAIN; -- 同一条SQL(索引自动生效) SELECT a.clientno, a.customername, CASE WHEN EXISTS ( SELECT 1 FROM cdk_freezesdeductsmsg b WHERE a.clientno = b.customerno AND b.freezestatus = '1' ) THEN '是' ELSE '否' END AS frozen_state FROM cr_client a WHERE a.comcode = '1001'; SET AUTOTRACE OFF;
plaintext
-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5000 | 195K| 150 (1)|
| 1 | FILTER | | | | |
|* 2 | TABLE ACCESS FULL | CR_CLIENT | 5000 | 195K| 100 (1)|
|* 3 | INDEX RANGE SCAN | IDX_CDK_FREEZES_CUST_STATUS | 1 | 20 | 1 (0)|
-----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("A"."COMCODE"='1001')
3 - access("B"."CUSTOMERNO"=:B1 AND "B"."FREEZESTATUS"='1')
TABLE ACCESS FULL变为
INDEX RANGE SCAN(Cost 从 1780→1);access(索引命中)代替
filter(全表过滤),仅扫描匹配的索引行。SELECT a.clientno, b.orderid, b.amount FROM cr_client a JOIN aml.orders b ON a.clientno = b.clientno WHERE a.comcode = '1001' AND b.orderstatus = '01'; -- 01=已支付
CREATE INDEX IDX_ORDERS_CLIENT_STATUS ON aml.orders(CLIENTNO, ORDERSTATUS);
EXISTS),建
覆盖索引(包含所有过滤字段),避免回表;freezestatus(2 个值)<
customerno(5000 个值),所以
customerno在前)。cr_client.comcode建索引,缩小外层扫描范围:CREATE INDEX IDX_CR_CLIENT_COMCODE ON cr_client(COMCODE)
INSTR4(customerno, 'C')=1),需建函数索引:CREATE INDEX IDX_CDK_FREEZES_CUST_FUNC ON cdk_freezesdeductsmsg(INSTR4(CUSTOMERNO, 'C'), FREEZESTATUS)
freezetime字段),需确保索引聚簇因子低(参考之前聚簇因子优化)。-- 查看正在执行的FILTER/Nested Loop慢查询SELECT
sql_id,
sql_text,
elapsed_time/1000000 AS elapsed_sec,
operation,
optionsFROM v$sql_planJOIN v$sql ON v$sql_plan.sql_id = v$sql.sql_idWHERE operation IN ('FILTER', 'NESTED LOOPS')
AND elapsed_time/1000000 > 5; -- 耗时>5秒
-- 查看索引使用情况 SELECT index_name, table_name, usage_count, last_usedFROM v$object_usageWHERE table_name = 'CDK_FREEZESDEDUCTSMSG';
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/12798004/viewspace-3112171/ ,如需转载,请注明出处,否则将追究法律责任。