Oracle 2005 年出了一个 30 多页的小册子,《Query Optimization in Oracle Database10g Release 2》,介绍了常见的优化器技术。
我是做 SQL 执行的,优化部分只了解皮毛,从没有系统学习过。本系列逐个学习和介绍,自我提升,也帮助他人。
简单来说,OR Expansion 就是把 OR 拆成 UNION ALL,来打开优化空间。
举个例子:
select * from t1, t2 where t1.pkey > 10 or t2.pkey = 100;
这个语句直接执行怎么做?先让 t1, t2 做笛卡尔乘积,然后做过滤。
思考:能不能把 or 条件拆了,分别下压到两张基表上去,然后做 join?> > 答案:显然是不行的,举个例子:t2 有一个 pkey = 1 的行,t1 有一个 pkey = 20 的行,按照语义,[t1.pkey = 20, t1.pkey = 1] 这一行数据属于结果集。但是如果下压的话,显然 t2 不会输出值为 1 的这一行。
为了提高性能,这个语句可以改写成:
select * from t1,t2 where t1.pkey > 10
union all
select * from t1,t2 where t2.pkey = 100
这样一来,上下两个子 query 可以分别用到一些过滤条件,各自做 join 时可以少处理一些数据。
进一步思考:是不是所有的 OR 都能做 expansion?是不是所有的 OR 都应该做 expansion?这个是由代价决定的,并无定数。比如说,上面的 query 里 t1.pkey > 10 如果没有什么过滤性,那么 t1, t2 还是相当于要做笛卡尔乘积,性能并不会有实质提升;对于下面一个子 query 而言,性能提升肯定是必然的,先扫出 t2 的一行,然后和 t1 做一个NLJ,很舒适。
Oracle 小册子里举了一个更复杂一点点的 Query,摘录如下:
#改写前
SELECT * FROM SHIPMENT, PORT P1, PORT P2
WHERE SHIPMENT.SOURCE_PORT_ID = P1.PORT_ID
AND SHIPMENT.DESTINATION_PORT_ID = P2.PORT_ID
AND (P1.PORT_NAME = 'OAKLAND' OR P2.PORT_NAME = 'OAKLAND')
#改写后
SELECT * FROM SHIPMENT, PORT P1, PORT P2
WHERE SHIPMENT.SOURCE_PORT_ID = P1.PORT_ID
AND SHIPMENT.DESTINATION_PORT_ID = P2.PORT_ID
AND P1.PORT_NAME = 'OAKLAND'
UNION ALL
SELECT * FROM SHIPMENT, PORT P1, PORT P2
WHERE SHIPMENT.SOURCE_PORT_ID = P1.PORT_ID
AND SHIPMENT.DESTINATION_PORT_ID = P2.PORT_ID
AND P2.PORT_NAME = 'OAKLAND' AND P1.PORT_NAME <> 'OAKLAND'
上半部分,P1 数据量被消减,可能可以当驱动表;下半部分,P2 数据量被消减,可能很有用。