Intorduction to the CBO Optimizer
- 介绍CBO优化器的书:Jonathan Lewis写的<<Cost-Based Oracle Fundamentals>>;
- 结构化查询语言的类型:
- DML(Data Manipulation Language):INSERT, UPDATE, DELETE, MERGE, SELECT;
- DDL(Data Definition Language):CREATE, DROP, ALTER, RENAME, TRUNCATE, GRANT, REVOKE, AUDIT, NOAUDIT, COMMENT;
- ESS(Environmental Sustainability Statement):DECLARE, CONNECT, OPEN, CLOSE, DESCRIBLE, WHENEVER, PREPARE, EXECUTE, FETCH;
- TCS(Transactoin Control Statement):COMMIT, ROLLBACK, SAVEPOINT, SET TRANSACTION;
- SystemCS(System Control Statement):ALTER SYSTEM;
- SessionCS(Session Control Statement):ALTER SESSSION, SET ROLE;
- SQL语句的实现过程:相同的SQL语句可以使用相同的执行计划;
- Cursor
- PL/SQL中的cursor:记录行的rowid,用来表示结果集;
- sql解析过程中的cursor:是SHARED POOL中的内存块;
- SQL语句的处理过程:
- Create a cursor:
- Cursor是private SQL area的句柄或者名称;
- 其中包含了语句执行的信息;
- Cursor的结构与它包含SQL语句是互相独立的;
- Parse the statement:
- SQL语句通过用户进程发送到Oracle实例;
- 在PGA中检查语法和语义,然后检查权限,分配private SQL area,然后检查是否已经在Library Cache中存在,如果没有的话,会把sql放入到Shared SQL area中,产生硬解析;
- 如果SQL的执行计划已经存在的话可以被重用;
- Describe query results:
- 提供了SELECT后面的要查找的列的信息,与动态执行SQL有关;
- 9i时使用DBMS_SQL包,之后使用EXECUTE IMMEDIATE来动态执行SQL;
- Define query output:定义要查找的列的位置,大小,数据类型信息;
- Bind variables:
- 开启内存地址来保存数据的值;
- 允许共享sql;
- Parallelize the statement:
- 可以并行的语句:SELECT, INSERT, UPDATE, MERGE, DELETE, CREATE, ALTER;
- 11g中自动判断是否需要并行;
- Execute the statement:执行SQL语句,产生想要的结果;
- Fetch rows of a query:以表格的形式返回数据;
- Close the cursor:关闭游标,PGA中指向cursor的指针关闭,但是内存区域还是被占用,之后可以被覆盖;
- 使用DBMS_SQL包可以看到完整的SQL执行的过程;
- Create a cursor:
- SQL语句解析的过程;
- 需要优化器的理由:它可以选择一种资源消耗最小的方式;
- 硬解析的操作步骤:
- Transformer
- 优化器首先会把语句分成一个一个的查询块,然后进行转换;
- 虽然进行了转换操作以提高效率,但是在内存中保存的还是原来的SQL语句;
- OR转换为UNION ALL操作;
- IN转换为内连接操作(11g);
- IN改写为exists;
- NOT IN改写为外连接+IS NULL,11g中自动转换,10g中需要修改;
- IN改写为外连接+IS NOT NULL;
- 视图合并:查询的时候直接查询视图中的基表,非常适合于视图的记录数很大,查询视图的记录数小的情况;
- 视图不合并:查询时把制图当成基表,这样效率比较低.CBO会自动合并第一层的视图,所以不要使用嵌套视图;
- Predicate Pushing:把条件推到最查询的最低端;
- 条件的传递性:employees的department_id列没有索引,department表department_id列是主键,转换过之后就会先走主键扫描;
- Cost-Based Optimizer
- 由Estimator和Plan Generator组成;
- Estimator决定执行计划的成本消耗的建议;
- 它是基于概率论的,理论依据是数据是均匀分布的;
- 它的基础数据是定期收集并存放在数据字典的统计信息;
- Plan Generator:
- 产生各种不同的执行计划;
- 使用Estimator计算各个执行计划的成本;
- 基于成本选择最好的优化建议;
- 生成最优的执行计划;
- OPTIMIZER_MODE的两个参数:ALL_ROWS, FIRST_ROWS_n:
- FIRST_ROWS_n:
- CBO优先考虑将结果集中的前N条记录以最快的速度返回,而其它的结果集并不需要同时返回;
- 可以使用在BBS的分页上:SELECT /*+ first_rows(10) */ FROM tbname;
- 这种执行计划对于SQL整体的执行时间不是最快的,但是在返回前N条记录的处理上绝对是最快的;
- 使用的排序字段必须有索引,否则CBO会忽略FIRST_ROWS(n),而使用ALL_ROWS;
- ALL_ROWS:
- CBO考虑最快的速度返回所有的结果集,和FIRST_ROWS_n相反;
- 在OLAP系统中使用较多,总体效率高;
- FIRST_ROWS_n:
- Estimator
- Selectivity:选择度;
- Selectivity is the estimated proportion of a row set retrieved by a particular predicate or combination of predicates;选择度是由一个特定的谓词或者组合谓词检索行集的估计比例;
- 计算公式:Selectivity=满足条件的记录数/总记录数;
- 它的取值在0.0-1.0之间:
- High Selectivity:得到大比例的记录数;
- Low Selectivity:得到小比例的记录数;
- 如何获得Selectivity:
- 如果没有统计信息则采用动态采样(Dynamic Sampling);
- 如果没有直方图信息则采用行分布;
- 存放统计信息的视图:
- dba_tables;
- dba_tab_statistics(NUM_ROWS, BLOCKS, AVG_ROW_LEN);
- dba_tab_col_statistics(NUM_DISTINCT, DENSITY, HIGH/LOW_VALUE);
- Cardinality:基数;
- 通过执行计划期望能检索出来的记录数;
- 计算公式:Cardinality=Selectivity*总记录数;
- 对于join, filters和sort的成本是重要的指标;
- 举例:SELECT * FROM hr.employees WHERE job_id = ‘SH_CLERK’;
- employees表的job_id列的取值个数为:19;
- employees表中的记录数:107;
- Selectivity=1/19=0.0526315789473684,即DENSITY的值;
- Cardinality=(1/19)*107=5.63,向上取整为6;
- employees表的job_id列的取值个数为:19;
- Cost:
- Cost是执行特定的语句所花费的标准I/Os数量的最优评估;
- Cost的单位是一个标准块的随机读取:1 cost unit = 1 SRds(Standardized Random Reads);
- 执行计划中Cost(%CPU):一次IO读取一个IO块需要的时间;
- Cost的值由三部分组成;
- Selectivity:选择度;
- 控制优化器的初始化参数:
- CURSOR_SHARING:SIMILAR|EXACT(default)|FORCE,控制可以共享Cursor的SQL语句类型;
- DB_FILE_MULTIBLOCK_READ_COUNT:它是一个可以在表扫描时最小化IO的参数,指定了在顺序扫描时一次IO操作可以读取的最大的块数;(在OLTP系统中一般指定4-16,在DW系统中可以尽量设置的大一点);
- PGA_AGGREGATE_TARGET:PGA自动管理时指定server processes可以使用的PGA内存的总和;
- STAR_TRANSFORMATION_ENABLED:参数设置为TRUE时使用CBO可以使用位图索引的特性,不过貌似现在这个参数不重要;
- RESULT_CACHE_MODE:MANUAL,FORCE,11g
- RESULT_CACHE_MAX_SIZE:11g;
- RESULT_CACHE_MAX_RESULT:11g;
- RESULT_CACHE_REMOTE_EXPIRATION:11g;
- OPTIMIZER_INDEX_CACHING:在Buffer Cache中缓存索引的比例,默认为0;
- OPTIMIZER_INDEX_COST_ADJ:索引扫描/全表扫描的成本,默认为100%,即索引扫描成本等于全表扫描成本;优先会选择全表扫描;比较悲观的配置;
- OPTIMIZER_FEATURES_ENABLE:希望启用哪个版本的CBO;
- OPTIMIZER_MODE:ALL_ROWS|FIRST_ROWS|FIRST_ROWS_n
- 默认是all_rows:资源消耗比较小;
- first_rows_n:n的取值为1|10|100|1000,速度优先,但是消耗很大的资源;
- OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES;
- OPTIMIZER_USE_SQL_PLAN_BASELINES;
- OPTIMIZER_DYNAMIC_SAMPLING:动态采样的特性,10g后默认为2;
- OPTIMIZER_USE_INVISIBLE_INDEXES;
- OPTIMIZER_USE_PENDING_STATISTICS;
- OPTIMIZER_INDEX_COST_ADJ参数设置的例子:
- 首先创建表,索引,并收集统计信息;
- 打开执行计划,执行查询语句,默认的值为100%,即索引扫描成本等于全表扫描成本,则执行计划走全表扫描;
- 修改参数值为50,即索引扫描成本是全表扫描成本的1/2,查看执行计划;
- 首先创建表,索引,并收集统计信息;
- Selectivity值的例子:
- 因为CBO是基于数据均匀的概率分布的,所以它估计的Selectivity是一个理论值;
- 创建一个1200条记录的表,里面的值分布是1-12,代表1-12月出生的人;
- 如果要查找某个月份出生的人,那么在不明白任何情况下,每个月份出生的人的概率都是1/12,即有100个人,CBO也是这么思考问题的;
- 实际值往往跟理论值不相符,但是数据量越大,越接近;
- 10053事件测试:
- 开启10053事件,并执行一条sql语句;
- 查看当前的session_id和process_id来确定生成的内容是放在哪个udump文件中;
- 查看udump文件,里面包含了一些缩写的含义和当前生效的优化参数的值;
- 关闭10053事件;
- 开启10053事件,并执行一条sql语句;
— Estimator例子的脚本;
SELECT * FROM hr.employees WHERE job_id = ‘SH_CLERK’;
SELECT COUNT(DISTINCT job_id) FROM hr.employees;
SELECT owner, table_name, column_name, num_distinct, density
FROM dba_tab_col_statistics
WHERE owner = ‘HR’ AND table_name = ‘EMPLOYEES’ AND column_name = ‘JOB_ID’;
FROM dba_tab_col_statistics
WHERE owner = ‘HR’ AND table_name = ‘EMPLOYEES’ AND column_name = ‘JOB_ID’;
SELECT owner, table_name, num_rows, blocks, avg_row_len
FROM dba_tab_statistics
WHERE owner = ‘HR’ AND table_name = ‘EMPLOYEES’;
FROM dba_tab_statistics
WHERE owner = ‘HR’ AND table_name = ‘EMPLOYEES’;
— OPTIMIZER_INDEX_COST_ADJ参数的例子;
CREATE TABLE t1 AS
SELECT MOD(ROWNUM, 200) n1, MOD(ROWNUM, 200) n2
FROM dba_objects WHERE ROWNUM <= 3000;
CREATE INDEX t_i1 ON t1(n1);
EXEC dbms_stats.gather_table_stats(USER, ‘t1’, CASCADE=>TRUE);
SET autotrace traceonly exp;
SELECT * FROM t1 WHERE n1 = 50;
ALTER SESSION SET optimizer_index_cost_adj = 50;
SELECT * FROM t1 WHERE n1 = 50;
SELECT MOD(ROWNUM, 200) n1, MOD(ROWNUM, 200) n2
FROM dba_objects WHERE ROWNUM <= 3000;
CREATE INDEX t_i1 ON t1(n1);
EXEC dbms_stats.gather_table_stats(USER, ‘t1’, CASCADE=>TRUE);
SET autotrace traceonly exp;
SELECT * FROM t1 WHERE n1 = 50;
ALTER SESSION SET optimizer_index_cost_adj = 50;
SELECT * FROM t1 WHERE n1 = 50;
— Selectivity的例子;
CREATE TABLE t2(ID, month_id) AS
SELECT ROWNUM, trunc(dbms_random.value(1, 13))
FROM dba_objects WHERE ROWNUM <= 1200;
EXEC dbms_stats.gather_table_stats(USER, ‘t2’, CASCADE => TRUE);
SET autotrace traceonly exp;
SELECT * FROM t2 WHERE month_id = 5;
SELECT COUNT(*) FROM t2 WHERE month_id = 5;
SELECT ROWNUM, trunc(dbms_random.value(1, 13))
FROM dba_objects WHERE ROWNUM <= 1200;
EXEC dbms_stats.gather_table_stats(USER, ‘t2’, CASCADE => TRUE);
SET autotrace traceonly exp;
SELECT * FROM t2 WHERE month_id = 5;
SELECT COUNT(*) FROM t2 WHERE month_id = 5;
— 10053时间测试例子;
ALTER SYSTEM/SESSION SET EVENTS ‘10053 trace name context forever, level 8’;
SELECT * FROM employees WHERE employee_id = 100;
ALTER SYSTEM/SESSION SET EVENTS ‘10053 trace name context off’;
SELECT s.sid, p.spid FROM v$session s
INNER JOIN v$process p ON s.paddr = p.addr AND
s.sid = (SELECT sid FROM v$mystat WHERE ROWNUM = 1);
SELECT * FROM employees WHERE employee_id = 100;
ALTER SYSTEM/SESSION SET EVENTS ‘10053 trace name context off’;
SELECT s.sid, p.spid FROM v$session s
INNER JOIN v$process p ON s.paddr = p.addr AND
s.sid = (SELECT sid FROM v$mystat WHERE ROWNUM = 1);