ORACLE的SQL是怎么执行的呢?
主要用到SGA中共享池的库高速缓存和SGA的缓冲区缓存。
库高速缓存主要判断是不是存在同样的SQL语句,如果存在,则进行软解析(即重用之前解析的sql语句);如果不存在,则进行硬解析(即从SQL语句中解析生成执行计划,并缓存)。这里指的同样的SQL语句是指完全一样的语句,包括大小写,中间的空格和是否有注释的等。这就是为什么在SQL语句中要使用变量,而不是常量了,因为使用变量可以保证语句是一致的。
库高速缓存中还有一个需要知道的是锁存器。锁存器是ORACLE读取SGA信息经常用的一种锁。锁存器是串行的,其等待需要使用CPU时间去检查锁存器是不是空闲的。因此,正确编写你的代码使其较少使用锁存器(也就是硬解析)是非常关键的。上面这句原话应该是说硬解析比如软解析更占用锁存器,但不知道为什么会更占用,是因为硬解析需要的时间比较长吗?——疑问
缓冲区缓存是用来存储从硬盘中读取后或写入硬盘的数据块。从缓冲区读取叫做逻辑读取,从硬盘中读取叫物理读取。同样,缓冲区缓存也需要锁存器。有一种方法可以有助于ORACLE使用较少的锁存器,这就是在写SQL语句的时候使得该语句在获取能够满足查询需要的数据行时访问尽可能少的数据块。要怎么做呢?——未知
在执行计划生成之前,检查完语法和权限之后,ORACLE会做一个查询转换。所以你必须明白你写的SQL可能不是最终的执行计划的语句。昨天一个同事在字符串转时间的时候就出现了错误,虽然他之前进行了验证字符串的格式是否满足时间类型。如:SELECT * FROM TABLE1 WHERE TO_DATE(STR_DATE, 'YYYY/MM/DD HH:MI:SS') > TO_DATE('2012/01/01 00:00:00') AND sp_Validate(STR_DATE) = 'T',根据ORACLE WHERE语句的执行顺序,该语句应该先执行sp_Validate(STR_DATE) = 'T',也就是验证字符串类型是否是时间类型,然后在进行两个时间的比较,然而该语句始终有错误。其实是ORACLE做了查询转换(可以通过NO_QUERY_TRANSFORMATION提示来检查转换前后的区别,除谓语前推外)。
查询转换包括视图合并,子查询接嵌套,谓语前推,使用物化视图进行查询重写。
视图合并。虽然你用视图进行关联,但是ORACLE会把视图解析成普通的SQL语句。如:SELECT * FROM TABLE1 T1, (SELECT ID FROM TABLE1) V1 WHERE T1.ID = V1.ID(+) AND T1.MONEY > 100, ORACLE的执行顺序是这样
1. NESTED LOOPS OUTER
2. TABLE ACCESS FULL
3 INDEX RANGE SCAN
我们可以用NO_MERGE提示进行强制不合并,SELECT * FROM TABLE1 T1, (SELECT /*+ NO_MERGE */ FROM TABLE1) V1 WHERE T1.ID = V1.ID(+) AND T1.MONEY > 100,那么ORACLE的执行顺序就变成这样了
1. HASH JOIN OUTER
2. TABLE ACCESS FULL
3. VIEW
4. TABLE ACCESS FULL
显然是不一样的。这些执行操作具体什么意思,性能如何,暂时还没搞懂,后面应该会有介绍。
还有一些运算也会阻止视图合并,如:查询块中包含解析函数或聚合函数、集合运算(如UNION、INTERSECT、MINUS)、ORDER BY子句或使用了ROWNUM。当然你也可以使用MERGE强制使用视图合并,但你必须确认合并后是否会影响结果集。
例子:
NO_MERGE
SELECT E1.LAST_NAME, E1.SALARY, V.AVG_SALARY FROM EMPLOYEE E1, (SELECT DEPARTMENT_ID, AVG(SALARY) AVG_SALARY FROM EMPLOYEE E2 GROUP BY DEPARTMENT_ID) V WHERE E1.DEPARTMENT_ID = V.DEPARTMENT_ID AND E1.SALARY > V.AVG_SALARY
执行顺序
0. SELECT STATEMENT
1. HASH JOIN
2. VIEW
3. HASH GROUP BY
4. TABLE ACCESS FULL
5. TABLE ACCESS FULL
MERGE
SELECT /*+ MERGE(V) */ E1.LAST_NAME, E1.SALARY, V.AVG_SALARY FROM EMPLOYEE E1, (SELECT DEPARTMENT_ID, AVG(SALARY) AVG_SALARY FROM EMPLOYEE E2 GROUP BY DEPARTMENT_ID) V WHERE E1.DEPARTMENT_ID = V.DEPARTMENT_ID AND E1.SALARY > V.AVG_SALARY
执行顺序
0. SELECT STATEMENT
1. FILTER
2. HASH GROUP BY
3. HASH JOIN
4. TABLE ACCESS FULL
5. TABLE ACCESS FULL
视图合并可以通过隐藏参数_complex_view_merging来控制,默认值为TRUE。
子查询解嵌套。如:
SELECT * FROM EMPLOYEES WHERE DEPARTMENT_ID IN (SELECT DEPARTMENT_ID FROM DEPARTMENTS)
就相当于
SELECT E.* FROM EMPLOYEES E, DEPARTMENT D WHERE E.DEPARTMENT_ID = D.DEPARTMENT_ID.
可以用NO_UNNEST提示强制不解嵌套。
SELECT * FROM EMPLOYEES WHERE DEPARTMENT_ID IN (SELECT /*_ NO_UNNEST */ DEPARTMENT_ID FROM DEPARTMENTS)
联结子查询的解嵌套转换。如:
SELECT OURTER.EMPLOYEE_ID, OUTLER.LAST_NAME, OUTER.SALARY, OUTER.DEPARTMENT_ID FROM EMPLOYEES OUTER WHERE OUTER.SALARY > (SELECT AVG(INNER.SALARY) FROM EMPLOYEES INNER WHERE INNER.DEPARTMENT_ID = OUTER.DEPARTMENT_ID)
相当于
SELECT OURTER.EMPLOYEE_ID, OUTLER.LAST_NAME, OUTER.SALARY, OUTER.DEPARTMENT_ID FROM EMPLOYEES OUTER, (SELECT DEPARTMENT_ID, AVG(INNER.SALARY) AVG_SAL FROM EMPLOYEES GROUP BY DEPARTMENT_ID) INNER WHERE OUTER.DEPARTMENT_ID = INNER.DEPARTMENT_ID AND OUTER.SALARY > INNER.AVG_SAL。
不过在书中没有OUTER.SALARY > INNER.AVG_SAL这个条件,我认为应该是漏了。
子查询接嵌套可以通过隐藏参数_unnect_subquery来控制,默认值为TRUE。
谓语前推。如:
SELECT OURTER.EMPLOYEE_ID, OUTLER.LAST_NAME, OUTER.SALARY, OUTER.DEPARTMENT_ID FROM EMPLOYEES OUTER, (SELECT DEPARTMENT_ID, AVG(INNER.SALARY) AVG_SAL FROM EMPLOYEES GROUP BY DEPARTMENT_ID) INNER WHERE OUTER.DEPARTMENT_ID = INNER.DEPARTMENT_ID AND OUTER.SALARY > INNER.AVG_SAL AND OUTER.DEPARTMENT_ID = 60
相当于
SELECT OURTER.EMPLOYEE_ID, OUTLER.LAST_NAME, OUTER.SALARY, OUTER.DEPARTMENT_ID FROM EMPLOYEES OUTER, (SELECT DEPARTMENT_ID, AVG(INNER.SALARY) AVG_SAL FROM EMPLOYEES WHERE DEPARTMENT_ID = 60 GROUP BY DEPARTMENT_ID) INNER WHERE OUTER.DEPARTMENT_ID = INNER.DEPARTMENT_ID AND OUTER.SALARY > INNER.AVG_SAL AND OUTER.DEPARTMENT_ID = 60。
很显然ORACLE用谓语前推先在子查询中进行过滤。可以用NO_PUSH_PRED提示来强制阻止谓语前推。也可以用ROWNUM,如:
SELECT OURTER.EMPLOYEE_ID, OUTLER.LAST_NAME, OUTER.SALARY, OUTER.DEPARTMENT_ID FROM EMPLOYEES OUTER, (SELECT DEPARTMENT_ID, AVG(INNER.SALARY) AVG_SAL FROM EMPLOYEES GROUP BY DEPARTMENT_ID) INNER WHERE ROWNUM > 0 OUTER.DEPARTMENT_ID = INNER.DEPARTMENT_ID AND OUTER.SALARY > INNER.AVG_SAL AND OUTER.DEPARTMENT_ID = 60
用了ROWNUM就相当于用了NO_MERGE和NO_PUSH_PRED提示。所以当你是用ROWNUM的时候要小心。
使用物化视图进行查询重写。如已存在物化视图
CREATE MATERIALIZED VIEW SALES_TIME_PRODUCT_MV
ENABLE QUERY REWRITE AS
SELECT P.PROD_ID, P.PROD_NAME, T.TIME_ID, T.WEEK_ENDING_DAY, S.CHANNEL_ID, S.PROMO_ID, S.CUST_ID, S.AMOUNT_SOLD
FROM SALES S, PRODUCTS P, TIMES T
WHERE S.TIME_ID = T.TIME_ID AND S.PROD_ID = P.PROD_ID
对SQL
SELECT P.PROD_ID, P.PROD_NAME, T.TIME_ID, T.WEEK_ENDING_DAY, S.CHANNEL_ID, S.PROMO_ID, S.CUST_ID, S.AMOUNT_SOLD
FROM SALES S, PRODUCTS P, TIMES T
WHERE S.TIME_ID = T.TIME_ID AND S.PROD_ID = P.PROD_ID
可以进行重写
SELECT /*+ rewrite(SALES_TIME_PRODUCT_MV) */ P.PROD_ID, P.PROD_NAME, T.TIME_ID, T.WEEK_ENDING_DAY, S.CHANNEL_ID, S.PROMO_ID, S.CUST_ID, S.AMOUNT_SOLD
FROM SALES S, PRODUCTS P, TIMES T
WHERE S.TIME_ID = T.TIME_ID AND S.PROD_ID = P.PROD_ID
后面的语句仅仅对物化视图进行访问。但如果SQL语句稍有不同,不知道还能不能通过重写物化视图来实现?
确定执行计划。当发生硬解析的时候,ORACLE将会确定哪个执行计划对于该查询是最优的。以上原话可以看出确定执行计划只发生在硬解析时。那软解析的话,应该是在库高速缓存中已缓存了执行计划,只要去读取执行计划就可以了。
执行计划怎么被确定的呢?ORACLE可能的执行计划的统计信息进行一个组合运算,优化器根据计算出来的成本值确定最佳执行计划。例如:
SELECT * FROM Widgets WHERE Color = 'BLUE'
统计信息,Widgets行数 num_rows = 1000,Color种类 num_distinct = 10,那么查询的结果行数 = num_rows / num_dinstinct = 100, 而 1/num_distinct就是选择比。这只是一个简单的例子,实际中更为复杂。
有一种情况,解析查询时统计信息已经过时或缺失,会导致ORACLE确定执行计划时选择的不是最优的。为什么会统计信息过时或缺失呢?是因为执行计划从缓存中读取的,而表的统计信息已经变了的关系吗?怎么保证统计信息是实时的呢?清除缓存?——暂时没找到答案。
另一种情况,给统计信息不准确。如:
SELECT * FROM CAR_PURCHASES WHERE MANUFACTURER = 'Ford' AND MAKE = 'Focus'
假设每一个品牌(MAKE)只能由一个公司(MANUFACTURER)生产。
统计信息 num_rows(CAR_PURCHASES) = 1,000,000
num_distinct(MANUFACTURER) = 4
num_distinct(MAKE) = 1000
那么查询结果行数 = num_rows(CAR_PURCHASES) / num_distinct(MANUFACTURER) / num_distinct(MAKE) = 250
选择比 = 1 / num_distinct(MANUFACTURER) / num_distinct(MAKE) = 1/4000
但事实上MAKE已经能确定MANUFACTURER,所以实际的选择比应该是 1 /1000
所以当你写SQL时应该要避免让优化器误入歧途。
执行计划并取得数据行。
1. 执行SQL语句
2.判断语句是否在库高速缓存中,如果是,进行软解析,否则,进行硬解析,并存储到库高速缓存中
3.执行计划
4.块是否在缓冲区缓存,如果不是进行物理读取,并保存在缓冲区。
5.根据列大小(Arraysize)不停地取数据,直到数据提取完成。
所以提取数据时,列大小对影响SQL的响应时间。所以较大的Arraysize可以减少FETCH调用的次数以及网络往返。那是不是越大就越好呢?太大会不会造成浪费呢?——疑问