当我需要去做优化时,有一个固定的优化思路:SQL优化->索引优化->分区优化->优化器hints优化
SQL 语句优化
- 选用适合的 ORACLE 优化器
ORACLE 的优化器共有 3 种 : a. RULE ( 基于规则 ) b. COST ( 基于成本 ) c. CHOOSE ( 选
择性)
设置缺省的优化器, 可以通过对 init.ora 文件中 OPTIMIZER_MODE 参数的各种声明 , 如
RULE,COST,CHOOSE,ALL_ROWS,FIRST_ROWS . 你当然也在 SQL 句级或是会话
(session) 级对其进行覆盖 . 为了使用基于成本的优化器 (CBO, Cost-Based Optimizer) , 你
必须经常运行 analyze 命令 , 以增加数据库中的对象统计信息 (object statistics)的准确性 . 如
果数据库的优化器模式设置为选择性 (CHOOSE), 那么实际的优化器模式将和是否运行过
analyze 命令有关 . 如果 table 已经被 analyze 过 , 优化器模式将自动成为 CBO , 反之 , 数据
库将采用 RULE 形式的优化器 . 在缺省情况下 ,ORACLE 采用 CHOOSE 优化器 , 为了避免那些
不必要的全表扫描 (full table scan) , 你必须尽量避免使用 CHOOSE 优化器 , 而直接采用基于
规则或者基于成本的优化器.
2 . 访问 Table 的方式
a. 全表扫描 全表扫描就是顺序地访问表中每条记录 . ORACLE 采用一次读入多个数据块
(database block) 的方式优化全表扫描 , 这样的访问方式是效率最低的 .
b. 通过 ROWID 访问表 你可以采用基于 ROWID 的访问方式情况 , 提高访问表的效率 , ,
ROWID 包含了表中记录的物理位置信息 ..ORACLE 采用索引(INDEX)实现了数据和存放数据
的物理位置(ROWID)之间的联系 . 通常索引提供了快速访问 ROWID 的方法 , 因此那些基于索
引列的查询就可以得到性能上的提高 . - 共享 SQL 语句
为了不重复解析相同的 SQL 语句 , 在第一次解析之后 , ORACLE 将 SQL 语句存放在内存中 .
这块位于系统全局区域 SGA(system global area)的共享池(shared buffer pool) 中的内存可
以被所有的数据库用户共享 . 因此 , 当你执行一个 SQL 语句 ( 有时被称为一个游标 ) 时 , 如果它 和
之前的执行过的语句完全相同 , ORACLE 就能很快获得已经被解析的语句以及最好的 执行路径 .
ORACLE 的这个功能大大地提高了 SQL 的执行性能并节省了内存的使用 . 可惜的是 ORACLE
只对简单的表提供高速缓冲 (cache buffering) , 这个功能并不适用于多表连接查询 . 数据库管
理员必须在 init.ora 中为这个区域设置合适的参数 , 当这个内存区域越大 , 就可以保留更多的语
句 , 当然被共享的可能性也就越大了
共享的语句必须满足三个条件 :
- A. 字符级的比较: 当前被执行的语句和共享池中的语句必须完全相同.
- B. 两个语句所指的对象必须完全相同
- C. 两个 SQL 语句中必须使用相同的名字的绑定变量(bind variables)
- 选择基础表
COST 优化器会检查 SQL 中每个表的物理大小 , 索引状态 , 然后选择花费最底的执行路
径 . 他会自己选择基础表
RULE 优化器 , 在所有连接条件都有索引对应的前提下 , 选择 FROM 子句中最后那个表
作为基础表 .
ORACLE 的解析器按照从右到左的顺序处理 FROM 子句中的表名 , 因此 FROM 子句中写在
最后的表 ( 基础表 driving table) 将被最先处理 . 在 FROM 子句中包含多个表的情况下 , 你必须
选择记录条数最少的表作为基础表.如果有 3 个以上的表连接查询, 那就需要选择 交叉表 (intersection table)作为基础表, 交叉表是指那个被其他表所引用的表. - WHERE 子句中的连接顺序.
ORACLE 采用自下而上的顺序解析 WHERE 子句 , 根据这个原理 , 表之间的连接必须写在其
他 WHERE 条件之前 , 那些可以过滤掉最大数量记录的条件必须写在 WHERE 子句的末尾 . - SELECT 子句中避免使用 ' * '
- 减少访问数据库的次数
当执行每条 SQL 语句时 , ORACLE 在内部执行了许多工作 : 解析 SQL 语句 , 估算索引的利
用率, 绑定变量 , 读数据块等等 . 由此可见 , 减少访问数据库的次数 , 就能实际上减少
ORACLE 的工作量 . - 使用 DECODE 函数来减少处理时间
SELECT COUNT(*) , SUM(SAL) FROM EMP WHERE DEPT_NO = 20 AND ENAME
LIKE 'SMITH%';
SELECT COUNT(*) , SUM(SAL) FROM EMP WHERE DEPT_NO = 30 AND ENAME
LIKE 'SMITH%';
高效:
SELECT COUNT(DECODE(DEPT_NO,0020,'X',NULL)) D0020_COUNT,
COUNT(DECODE(DEPT_NO,0030,'X',NULL)) D0030_COUNT,
SUM(DECODE(DEPT_NO,0020,SAL,NULL)) D0020_SAL,
SUM(DECODE(DEPT_NO,0030,SAL,NULL)) D0030_SAL
FROM EMP
WHERE ENAME LIKE 'SMITH%'; - 删除重复记录 最高效的删除重复记录方法 ( 因为使用了 ROWID)
DELETE FROM EMP E WHERE E.ROWID > (SELECT MIN(X.ROWID) FROM EMP X WHERE X.EMP_NO = E.EMP_NO);
10 用 TRUNCATE (DDL) 替代 DELETE(DML)
当删除表中的记录时, 在通常情况下 , 回滚段 (rollback segments ) 用来存放可以被恢复
的信息 . 如果你没有 COMMIT 事务 ,ORACLE 会将数据恢复到删除之前的状态 ( 准确地说是 恢
复到执行删除命令之前的状况 ) 而当运用 TRUNCATE 时 , 回滚段不再存放任何可被恢复的信息 .
当命令运行后 , 数据不能被恢复 . 因此很少的资源被调用 , 执行时间也会很短 - 尽量多使用 COMMIT
只要有可能, 在程序中尽量多使用 COMMIT, 这样程序的性能得到提高 , 需求也会因为
COMMIT 所释放的资源而减少 : COMMIT 所释放的资源 :
a. 回滚段上用于恢复数据的信息 .
b. 被程序语句获得的锁
c. redo log buffer 中的空间
d. ORACLE 为管理上述 3 种资源中的内部花费 - 用 Where 子句替换 HAVING 子句
避免使用 HAVING 子句 , HAVING 只会在检索出所有记录之后才对结果集进行过滤 . 这个
处理需要排序 , 总计等操作 . 如果能通过 WHERE 子句限制记录的数目 , 那就能减少这方面的开
销 .
低效:
SELECT REGION, AVG(LOG_SIZE) FROM LOCATION GROUP BY REGION
HAVING REGION != 'SYDNEY' AND REGION != 'PERTH'
高效:
SELECT REGION, AVG(LOG_SIZE) FROM LOCATION WHERE REGION != 'SYDNEY' AND REGION != 'PERTH' GROUP BY REGION - 减少对表的查询
低效:
SELECT TAB_NAME FROM TABLES WHERE TAB_NAME = ( SELECT TAB_NAME FROM TAB_COLUMNS WHERE VERSION = 604) AND DB_VER= ( SELECT DB_VER FROM TAB_COLUMNS WHERE VERSION = 604)
高效:
SELECT TAB_NAME FROM TABLES WHERE (TAB_NAME,DB_VER) = ( SELECT TAB_NAME,DB_VER FROM TAB_COLUMNS WHERE VERSION = 604)
Update 多个 Column 例子 :
低效 :
UPDATE EMP SET EMP_CAT = (SELECT MAX(CATEGORY) FROM EMP_CATEGORIES), SAL_RANGE = (SELECT MAX(SAL_RANGE) FROM EMP_CATEGORIES) WHERE EMP_DEPT = 0020;
高效 :
UPDATE EMP SET (EMP_CAT, SAL_RANGE) = (SELECT MAX(CATEGORY) , MAX(SAL_RANGE) FROM EMP_CATEGORIES) WHERE EMP_DEPT = 0020; - 使用表的别名 (Alias)
当在 SQL 语句中连接多个表时 , 请使用表的别名并把别名前缀于每个 Column 上 . - 用 EXISTS 替代 IN
在许多基于基础表的查询中, 为了满足一个条件 , 往往需要对另一个表进行联接 . 在这种情况下 ,
使用 EXISTS( 或 NOT EXISTS) 通常将提高查询的效率 . 因为 EXISTS 只返回 true or false 不用返
回结果集 , 而 IN 和 NOT IN 需要返回结果集并对结果集进行排序和合并
低效 :
SELECT * FROM EMP (基础表) WHERE EMPNO > 0 AND DEPTNO IN (SELECT DEPTNO
FROM DEPT WHERE LOC = 'MELB')
高效 :
SELECT * FROM EMP (基础表) WHERE EMPNO > 0 AND EXISTS (SELECT 'X' FROM DEPT WHERE DEPT.DEPTNO = EMP.DEPTNO AND LOC = 'MELB') - 用表连接替换 EXISTS 采用表连接的方式比 EXISTS 更有效率
SELECT ENAME FROM EMP E WHERE EXISTS (SELECT 'X' FROM DEPT WHERE DEPT_NO = E.DEPT_NO AND DEPT_CAT = 'A');
( 更高效 )
SELECT ENAME FROM DEPT D,EMP E WHERE E.DEPT_NO = D.DEPT_NO AND DEPT_CAT = 'A' ; - 用 EXISTS 替换 DISTINCT
当提交一个包含一对多表信息( 比如部门表和雇员表 ) 的查询时 , 避免在 SELECT 子句中使用
DISTINCT 这样的高消耗的执行语句 , 尽量使用其他方法替换 . 比如使用 EXISTS. 即使在 EXISTS 的
WHERE 子句中使用子查询也比 DISTINCT 的效率要高 .
低效 :
SELECT DISTINCT DEPT_NO,DEPT_NAME FROM DEPT D,EMP E WHERE D.DEPT_NO = E.DEPT_NO
高效 :
SELECT DEPT_NO,DEPT_NAME FROM DEPT D WHERE EXISTS ( SELECT 'X' FROM EMP E WHERE E.DEPT_NO = D.DEPT_NO);
EXISTS 使查询更为迅速 , 因为 RDBMS 核心模块将在子查询的条件一旦满足后 , 立刻返回结果 . - 用 UNION-ALL 替换 UNION
当使用 UNION 的时候 , 这两个结果集会以 UNION-ALL 的方式合并 , 然后对输出结果进行排
序 , 而用 UNION-ALL 的话 , 排序就可以避免了
索引优化
用索引来提高效率, 是最普遍的提高效率的方法 , 但是同时带来的弊端就是降低空间效率 , 通
过索引可以避免全表扫描 , 并且它提供了主键的唯一性验证 .(LONG 和 LONG RAW 类型字段是
不能作为索引的 ). 通常大型表中使用索引特别有效 .但是,索引提高的执行效率是用空间效率换来的 . 索引需要空间来存储 . 也需要定期维护 . 每当 有记录在表中增加或者删减的时候都会修改索引本身, 这也就意味着每次执行 INSERT,DELETE,UPDATE 将多付出几次磁盘的 I/O 操作 . 因为索引需要额外的存储空间和处理, 所以那些不必要的索引往往也会影响查询的执行效率
ORACLE 对索引的访问有两种 :
A.索引唯一扫描 (INDEX UNIQUE SCAN)
在一个表中有两个索引 , 在列 C1 上建立一个唯一性索引 INDEX_KEY 和 C2 列的一个非
唯一性索引 INDEX_01, 在执行如下语句的时候
select * from table where C1=1;
这个时候唯一性索引 INDEX_KEY 将被访问 , 获得响应的 ROWID 然后通过 ROWID 访问
的方式进行下一步检索 . 如果被索引返回的列包含于 INDEX 列中 , 那么 ORACLE 是不会通过
ROWID 访问表的,直接访问索引即可
B. 索引的范围查询 (INDEX RANGE SCAN)
基于一个范围的检索和基于一个非唯一性索引的检索 . 这两种情况适用索引的范围查询 .
select C1 from table where C2=3;
这个 SQL 首先按照非唯一性索引进行索引范围查询 ( 得到所有符合条件记录的 ROWID),
然后通过 ROWID 访问表 , 得到 C1 的值 . 由于 C2 是一个非唯一索引 , 所以数据库不进行索引唯一
扫描 . 由于返回列 C1 不在 INDEX_01 中 , 所以在索引范围查询后会进行一个 ROWID 访问表的
操作
多个平等的索引
在 SQL 语句包含多个表时,且使用分布在表上的多个索引时,ORACLE 会同时使用多个索引 并运行时对他们的记录进行合并,检索出仅对全部索引有效的记录.
唯一性索引的等级是最高的,但是,只有当索引在 WHERE 子句中与常量比较的时候才有效, 如果与其他表的索引相比较,那么这个子句在优化器中等级是最低的.
如果不同表的两个同等级的索引被引用,那么将有 FROM 子句中表的顺序决定,既 FROM 最 后面的表的索引优先级最高
如果同表的两个相同等级的索引被引用,那么 WHERE 子句中最先被引用的索引将有最高优先级.
select name from emp where age=25( 非唯一性索引 ) and g_id='1001'( 非唯一性索
引 ) 这个时候优化器会先访问索引 age 然后在与索引 g_id 的结果集进行合并 .
等式比较和范围比较
当WHERE子句中存在索引列的比较时,ORACLE是不会优先对进行比较的索引进行访问的. 所以尽量避免对索引列进行范围比较.进行范围比较的索引访问级别很低.即使是唯一性索引也 是一样.
强制索引失效
如果存在两个以上相同级别的索引,可以使用强制命令 ORACLE 只使用其中一个 ( 检索出的
记录数量少 )
select emp.name from emp where age = 25 and g_id||''='a';
索引的自动选择规则
如果一个表中有很多索引,但是只有一个是唯一性索引,那么 ORACLE 只会对唯一性索引进 行访问而完全忽略其他非唯一性索引.......
- 避免对索引使用 NOT
- 避免在索引列上使用 IS NULL OR IS NOT NULL
- 使用索引的第一个列,
- 避免改变索引列的类型
分区优化
改善查询性能:对分区对象的查询可以仅搜索自己关心的分区,提高检索速度(个人评价只针对条件中带分区字段的查询性能)。
增强可用性:如果表的某个分区出现故障,表在其他分区的数据仍然可用(鸡蛋不放一个篮子里的概念)。
维护方便:如果表的某个分区出现故障,需要修复数据,只修复该分区即可(各数据文件之间相互独立,互不影响)。
均衡I/O:可以把不同的分区映射到不同磁盘以平衡I/O,改善整个系统性能(这个针对整体的查询性能,但是分区必须放在不同的表空间,每个表空间尽量分布在不同的磁盘上,这样可以充分利用各个磁盘的I/O来提高效率)
① 分区键的选择: 选择合适的分区键 对于表分区的效果至关重要。分区键应该是经常被查询和过滤的列 ,以便在查询时能够快速定位到目标分区。此外,分区键的选择还应考虑到数据的均匀分布和未来的扩展性 。例如,在销售数据表中,可以选择按照日期、地区或产品类别等字段进行分区。
② 子分区: 在某些情况下,单一分区可能仍然过大,导致查询性能下降。这时可以考虑使用子分区来进一步细分数据。子分区可以按照更具体的条件对分区进行进一步划分,例如按照地区、销售人员或产品类型进行子分区。通过细粒度的子分区,可以更精确地定位到需要查询的数据,提高查询效率。
③ 分区策略: 根据数据的特性和查询需求 ,选择适当的分区策略 也是优化表分区的关键。常见的分区策略包括范围分区、列表分区、哈希分区和复合分区 等。根据具体情况选择合适的分区策略,可以更好地满足业务需求,提高查询和维护的效率。
④ 分区维护: 定期对分区进行维护操作是保证表分区性能 的重要环节。维护操作包括分区合并、分区拆分、分区移动和分区压缩 等。通过合理的维护操作,可以减少分区数目,优化存储空间的利用率,提高查询性能和维护效率。
⑤ 分区索引: 为分区表创建适当的索引也是优化表分区的关键。根据查询的模式和频率,选择合适的索引类型和列,以提高查询的效率。对于分区表,通常会为分区键和常用查询条件创建索引,以实现快速的分区导航和查询性能的提升。
⑥ 分区交换:分区交换是一种高效的数据加载和数据归档技术,可用于快速插入大量数据或归档历史数据。通过分区交换,可以在两个分区之间直接交换数据,而无需实际移动数据的物理位置,从而极大地减少了数据加载和归档的时间和资源消耗。
优化器hints的优化
不要过分依赖Hint
当遇到SQL执行计划不好的情况,应优先考虑统计信息等问题,而不是直接加Hint了事。如果统计信息无误,应该考虑物理结构是否合理,即没有合适的索引。只有在最后仍然不能SQL按优化的执行计划执行时,才考虑Hint。
毕竟使用Hint,需要应用系统修改代码,Hint只能解决一条SQL的问题,并且由于数据分布的变化或其他原因(如索引更名)等,会导致SQL再次出现性能问题。
Hint的弊端
-
Hint是比较"暴力"的一种解决方式,不是很优雅。需要开发人员手工修改代码。
-
Hint不会去适应新的变化。比如数据结构、数据规模发生了重大变化,但使用Hint的语句是感知变化并产生更优的执行计划。
-
Hint随着数据库版本的变化,可能会有一些差异、甚至废弃的情况。此时,语句本身是无感知的,必须人工测试并修正。
Hint功能
Hint提供的功能非常丰富,可以很灵活地调整语句的执行过程。通过Hint,我们可以调整:
-
优化器类型
-
优化器优化目标
-
数据读取方式(访问路径)
-
查询转换类型
-
表间关联的顺序
-
表间关联的类型
-
并行特性
-
其他特性
Hint用法
关键字说明
-
DELETE、INSERT、SELECT和UPDATE是标识一个语句块开始的关键字,包含提示的注释只能出现在这些关键字的后面,否则提示无效。
-
"+"号表示该注释是一个提示,该加号必须立即跟在"/*"的后面,中间不能有空格。
-
hint是下面介绍的具体提示之一,如果包含多个提示,则每个提示之间需要用一个或多个空格隔开。
-
text是其它说明hint的注释性文本
提示中的错误
提示中的语法错误不会报错,如果解析器不能解析它,就会把它看做一个普通注释处理。这也是容易造成困惑的一点,使用的Hint到底是否起效?可以采用一些手段,检查提示的有效性。需要注意的是,那些语法正确但引用对象错误的提示是不会被报告的。
- explain plan + dbms_xplan
使用dbms_xplan输出中的note选项。
- 10132事件
在10g中,这个事件产生的输出文档的末尾有一部分内容专门讲提示。通过它可以检查两个方面:一是每个用到的提示都会被列出来。如果漏掉了哪个,就说明这个提示没有被识别;二是检查是否有一些信息指明了出现提示错误(如果出错,err值将大于0)。
提示中的对象
SELECT /*+ INDEX(table_name index_name) */ ...
-
table_name是必须要写的,且如果在查询中使用了表的别名 ,在hint也要用表的别名来代替表名。
-
index_name可以不必写,Oracle会根据统计值选一个索引。
-
如果索引名或表名写错了,那这个hint就会被忽略。
-
如果指定对象是视图,需要按此方法指定。/*+hint view.table ...*/,其中table是view中的表。
-
一个很常见的错误时,在使用提示的时候最易犯的错误是与表的别名有关。正确的规则是,当在提示中使用表时,只要表有别名就应该使用别名而不是表名。
Hint分类
和优化器相关的
当对优化器为某个语句所制定的基本执行计划不满意时,最好的办法就是通过提示来转换优化器的模式,并观察其转换后的结果,看是否已经达到期望程度。如果只通过转换优化器的模式就可以获得非常好的执行计划,则就没有必要额外使用更为复杂的提示了。
- OPT_PARAM
这个提示的作用就是使我们在某条语句中指定某个系统参数值。
- ALL_ROWS
为实现查询语句整体最优化而引导优化器制定最少成本的执行计划。这个提示会使优化器选择一条可最快检索所有查询行的路径,而代价就是在检索一行数据时,速度很慢。
- FIRST_ROWS
为获得最佳响应时间而引导优化器制定最少成本的执行计划。这个提示会使优化器选择可最快检索出查询的第一行(或指定行)数据的路径,而代价就是检索很多行时速度就会很慢 。利用FIRST_ROWS来优化的行数,默认值为1,这个值介于10到1000之间,这个使用FIRST_ROWS(n)的新方法是完全基于代价 的方法。它对n很敏感,如果n值很小,CBO就会生成包含嵌套循环以及索引查找的计划;如果n很大,CBO会生成由哈希连接和全表扫描组成的计划(类似ALL_ROWS)。
- CHOOSE
依据SQL中所使用到的表的统计信息存在与否,来决定使用RBO还是CBO。在CHOOSE模式下,如果能够参考表的统计信息,则将按照ALL_ROWS方式执行。除非在查询中的所有表都没有经过分析,否则choose提示会对整个查询使用基于代价的优化。如果在多表连接中有一个表经过分析过,那么就会对整个查询进行基于代价的优化。
- RULE
使用基于规则的优化器 来实现最优化执行,即引导优化器根据优先顺序规则来决定查询条件中所使用到的索引或运算符的执行顺序 来制定执行计划。这个提示强制oracle优先使用预定义的一组规则,而不是对数据进行统计;同时该提示还会使这个语句避免使用其他提示,除了DRIVING_SITE和ORDERED(不管是否进行基于规则的优化,这两个提示都可使用)。
和访问路径相关的
- FULL
告诉优化器通过全表扫描方式 访问数据。这个提示只对所指定的表进行全表扫描,而不是查询中的所有表。FULL提示可以改善性能。这主要是因为它改变了查询中的驱动表,而不是因为全表扫描。在使用其他某些提示时,也必须使用FULL提示。只有访问整个表时,才可利用CACHE提示将表进行缓存。并行组中的某些提示也必须使用全表扫描。
- CLUSTER
引导优化器通过扫描聚簇索引来从索引表中读取数据。
- HASH
引导优化器按照哈希扫描的方式从表中读取数据。
- INDEX
告诉优化器对指定表通过索引的方式访问数据。当访问数据会导致结果集不完整时,优化器将忽略这个Hint。
- NO_INDEX
告诉优化器对指定表不允许使用索引。这个提示会禁止优化器使用指定索引。可以在删除不必要的索引之前在许多查询中禁止索引。如果使用了NO_INDEX,但是没有指定任何索引,则会执行全表扫描。如果对某个索引同时使用了NO_INDEX和会之产生冲突的提示(如INDEX),这时两个提示都会被忽略掉。
- INDEX_ASC
利用索引 从表中读取数据时,引导优化器对提示中所指定索引的索引列值按照升序使用范围扫描。
- INDEX_COMBINE
告诉优化器强制选择位图索引 。这个提示会使优化器合并表上的多个位图索引,而不是选择其中最好的索引(这是INDEX提示的用途)。还可以使用index_combine指定单个索引(对于指定位图索引,该提示优先于INDEX提示)。对于B树索引,可以使用AND_EQUAL提示而不是这个提示。
- INDEX_JOIN
索引关联 ,当谓词中引用的列上都有索引的时候,可以通过索引关联的方式来访问数据。这个提示可以将同一个表的各个不同索引进行合并,这样就只需要访问这些索引就可以了,节省了回表查询的时间。但只能在基于代价的优化器中使用该提示。这个提示不仅允许只访问表上的索引,这样可以扫描更少的代码块,并且它比使用索引并通过rowid扫描整个表快5倍。
- INDEX_DESC
利用索引从表中读取数据时,引导优化器对提示中所指定索引的索引列值按照降序使用范围扫描。
- INDEX_FFS
告诉优化器以**INDEX FFS(index fast full scan)**的方式访问数据。INDEX_FFS提示会执行一次索引的快速全局扫描。这个提示只访问索引,而不是对应的表。只有查询需要检索的信息都在索引上时,才使用这个提示。特别在表有很多列时,使用该提示可以极大地改善性能。
- INDEX_SS
强制使用index skip scan的方式访问索引。当在一个联合索引中,某些谓词条件并不在联合索引的第一列时(或者谓词并不在联合索引的第一列时),可以通过index skip scan来访问索引获得数据。当联合索引第一列的唯一值很少时,使用这种方式比全表扫描的方式效率要高。
和查询转换相关的
- USE_CONCAT
将含有多个OR或者IN运算符所连接起来的查询语句分解为多个单一查询语句,并为每个单一查询语句选择最优化查询路径,然后再将这些最优化查询路径结合在一起,以实现整体查询语句的最优化目的。只有在驱动查询条件中包含OR的时候,才可以使用该提示。
- NO_EXPAND
引导优化器不要为使用OR运算符号(或IN运算符)的条件制定相互结合的执行计划。正好和USE_CONCAT相反。
- REWRITE
当表连接的对象是数据量比较大的表 或者需要获得使用统计函数处理过的结果时 ,为了提高执行速度可预先创建物化视图。当用户要求查询某个查询语句时,优化器会在从表中和从物化视图中读取数据的两种方法中选择一个更有效的方法来读取数据。该执行方法称之为查询重写。使用REWRITE提示引导优化器按照该方式执行。
- MERGE
为了能以最优方式从视图或者嵌套视图中 读取数据,通过变换查询语句来直接读取视图使用的基表数据,该过程被称之为视图合并。不同的情况其具体使用类型也有所不同。该提示主要在视图未发生合并时被使用。尤其是对比较复杂的视图或者嵌套视图(比如使用了GROUP BY或DISTINC的视图)使用该提示,有时会取得非常好的效果。
- UNNEST
提示优化器将子查询转换为连接的方式。也就是引导优化器合并子查询和主查询并且将其向连接类型转换。
- NO_UNNEST
引导优化器让子查询能够独立地执行完毕之后再跟外围的查询做FILTER。
- PUSH_PRED
使用该提示可以将视图或嵌套视图以外的查询条件推入到视图之内。
- NO_PUSH_PRED
使用该提示确保视图或嵌套视图以外的查询条件不被推入到视图内部。
- PUSH_SUBQ
使用该提示引导优化器为不能合并的子查询制定执行计划 。不能合并的子查询被优先执行之后,该子查询的执行结果将扮演缩减主查询数据查询范围的提供者角色。通常在无法执行子查询合并的情况下,子查询扮演的都是检验者角色,所以子查询一般被放在最后执行。在无法被合并的子查询拥有较少的结果行,或者该子查询可以缩减主查询查询范围的情况下,可以使用该提示引导优化器最大程度地将该子查询放在前面执行,以提高执行速度。但如果子查询执行的是远程表或者排序合并连接的一部分连接结果,则该提示将不起任何作用。
- NO_PUSH_SUBQ
使用该提示将引导优化器将不能实现合并的子查询放在最后执行。在子查询无法缩减主查询的查询范围,或者执行子查询开销较大的情况下,将这样的子查询放在最后执行可以在某种程度上提高整体的执行效率。也就是说,尽可能地使用其他查询条件最大程度地缩减查询范围之后,再执行子查询。
和表连接顺序相关的
这些提示可以调整表连接的顺序。调整表连接的顺序并不是只能使用这些提示,在嵌套循环连接方式中也可以让提示来引导优化器使用由驱动查询条件所创建的索引。然而,该方法只有在使用的索引和表连接顺序同时被调整的情况下才比较有效。一般而言,这些提示主要在执行多表连接和表之间的连接顺序比较混乱的情况下才使用,也在排序合并连接或哈希连接方式下,为引导优化器优先执行数据量比较少得表时使用。
- LEADING
在一个多表关联的查询中,这个Hint指定由哪个表作为驱动表,即告诉优化器首先要访问那个表上的数据。引导优化器使用LEADING指定的表作为表连接顺序中的第一个表。该提示既与FROM中所描述的表的顺序无关,也与作为调整表连接顺序的ORDERED提示不同,并且在使用该提示时并不需要调整FROM中所描述的表的顺序。当该提示与ORDERED提示同时使用时,该提示被忽略。
这个提示类似ORDERED提示,它允许指定驱动查询的表,然后由优化器来判断下一个要访问的表。如果使用这个提示指定多张表,那么就可以忽略这个提示。
- ORDERED
引导优化器按照FROM中所描述的表的顺序执行连接。如果和LEADING提示被一起使用,则LEADING提示将被忽略。由于ORDERED只能调整表连接的顺序并不能改变表连接的方式,所以为了改变表的连接方式,经常将USE_NL、USE_MERGE提示与ORDERED提示放在一起使用。
和表连接操作相关的
- USE_NL
使用该提示引导优化器按照嵌套循环连接方式执行表连接。它只是指出表连接的方式,对于表连接顺序不会有任何影响。
- USE_MERGE
引导优化器按照排序合并连接方式执行连接。在有必要的情况下,推荐将该提示与ORDERED提示一起使用。提示通常用于获得查询的最佳吞吐量。假设将两个表连接在一起,从每个表返回的行集将被排序,然后再被合并(也就是合并排序),从而组成最终的结果集。由于每个行先被排序之后才进行合并,所以在给定查询中检索所有行时,速度将会最快。如果需要以最快速度返回第一行,就应该使用USE_NL提示。
- USE_HASH
该提示引导优化器按照哈希连接方式执行连接。在执行哈希连接时,如果由于某一边的表比较小,从而可以在内存中实现哈希连接,那么就能够获得非常好的执行速度。由于在大部分情况下优化器会通过对统计信息的分析来决定Build Input和Prove Input,所以建议不要使用ORDERED提示随意改变表的连接顺序。但是当优化器没能做出正确判断时,或者像从嵌套视图中所获得的结果集合那样不具备统计信息时,可以使用该提示。
和并行相关的
- PARALLEL
指定SQL执行的并行度 ,这个值将会覆盖表自身设定的并行度。如果这个值为default,CBO使用系统参数。从表中读取大量数据和执行DML操作 时使用该提示来指定SQL的并行操作。一般情况下需要在该提示中指定将要使用的并行线程个数 。如果在该提示中没有指定并行度的个数,则优化器将使用PARALLEL_THREADS_PER_CPU参数 所指定的值进行自动计算。如果在定义表时指定了PARALLEL,那么在能够使用并行操作的情况下,即使没有使用该提示,优化器也会按照指定的并行级别选择并行操作。但是如果想在DELETE、INSERT、UPDATE、MERGE等DML操作中使用并行操作,则必须要在会话中设置ALTER SESSION ENABLE PARALLEL DML。在某个会话中所设置的并行级别也可以被引用在内部的GROUP BY或者排序操作中。在并行操作中如果出现了某个限制要素,则该提示将被忽略。
- NO PARALLEL/NO_PARALLEL
在SQL语句禁止使用并行。在有些版本中用NO_PARALLEL提示来代替NOPARALLEL提示。
- PQ_DISTRIBUTE
为了提高并行连接的执行速度,使用该提示来定义使用何种方法在主从进程之间(例如生产者进程和消费者进程)分配各连接表的数据行。
- PARALLEL_INDEX
为了按照并行操作的方式对分区索引进行索引范围扫描而使用该提示,并且可以指定进程的个数。
其他相关的
- APPEND
让数据库以直接加载的方式(direct load)将数据加载入库。这个提示不会检查当前是否有插入所需要的块空间,相反它会直接将数据添加到新块中。这样会浪费空间,但可以提高插入的性能。需要注意的是,数据将被存储在HWM之上的位置。
- APPEND_VALUES
在11.2中,Oracle新增了APPEND_VALUES提示,使得INSERT INTO VALUES语句也可以使用直接路径插入。
- CACHE
在全表扫描之后,数据块将留在LRU列表的最活跃端 。如果设置表的CACHE属性,它的作用和HINT一样。这个提示会将全表扫描全部缓存到内存中。如果表很大,会占用大量内存。因此适用于用户经常访问的较小的表。
- NOCACHE
引导优化器将通过全表扫描方式获取的数据块缓存在LRU列表的最后位置 ,这样可以让数据库实例缓存中的这些数据块被优先清除。这是优化器在Buffer Cache中管理数据块的默认方法(仅针对全表扫描)。
- QB_NAME
使用该提示为查询语句块命名,在其他查询语句块可以直接使用该查询语句块的名称。
- DRIVING_SITE
这个提示在分布式数据库操作中有用。指定表是处理连接所在的位置。可以限制通过网络处理的信息量。此外,还可以建立远程表的本地视图来限制从远程站点检索的行。本地视图应该有where子句,从而视图可以在将行发送回本地数据库之前限制从远程数据库返回的行。
- DYNAMIC_SAMPLING
提示SQL执行时动态采样的级别。这个级别为0~10,它将覆盖系统默认的动态采样级别。等级越高,所获得统计信息的准确率越高。该提示的功能就是为了确保将动态采样原理应用在单个SQL中。
- AND_EQUAL
这个提示会使优化器合并表上的多个索引 ,而不是选择其中最好的索引(这是INDEX提示的用途)。这个提示与前面的INDEX_JOIN提示有区别,以此指定的合并索引随后需访问表 ,而INDEX_JOIN提示则只需访问索引。如果发现需经常用到这个提示,可能需要删除这些单个索引而改用一个组合索引。需要查询条件里面包括所有索引列,然后取得每个索引中得到的rowid列表。然后对这些对象做merge join,过滤出相同的rowid后再去表中获取数据或者直接从索引中获得数据。在10g中,and_equal已经废弃了,只能通过hint才能生效。
- CARDINALITY
向优化器提供对某个查询语句的整体或部分的预测基数值,并通过参考该基数值来为查询语句制定执行计划。如果在该提示中没有指定表的名称,则该基数值将被视为从该查询语句所获得的最终结果行数。
CSDNhttps://mp.csdn.net/mp_blog/creation/editor/138309193
高水位调节
什么是 高水位标记 High Water Mark
块Block
块:是粒度最小的存储单位,现在标准的块大小是8K,ORACLE每一次I/O操作也是按块来操作的,也就是说当ORACLE从数据文件读数据时,是读取多少个块,而不是多少行. 每一个Block里可以包含多个row.
区Extent
由一系列相邻的块而组成,这也是ORACLE空间分配的基本单位.区extent是比数据块大一级的存储结构,表示的是一连串连续的数据块集合。
段Segment
段: 是由一系列的区extent所组成. 数据段是与数据库对象相对应,一般一个数据库对象对应一个数据段。多个extent是对应一个数据段,每个数据段实际上就是数据库一个对象的代表。一般来说, 当创建一个对象时(表,索引),就会分配一个段给这个对象.
表空间tableSpace
表空间: 包含段,区及块.表空间的数据物理上储存在其所在的数据文件中.一个数据库至少要有一个表空间.
Oracle 数据库 逻辑结构包括:数据库块(block),扩展(extent 区),段(segment),表空间(tablespace)。高水位线就存在于段(segment)中,它用于标识段中已使用过的数据块与未使用过的数据块二者间交界,扫描表数据的时候,高水位线以下的所有数据块都必须被扫描。
高水位线存在于段 ,且高水位线的位置记录在段头,也就是段的第一个数据块中。因此,我们可以转存储段头信息来看高水位信息。
HWM通常增长的幅度为一次5个数据块 ,原则上HWM只会增大,不会缩小,即使将表中的数据全部删除,HWM还是为原值,由于这个特点,使HWM很象一个水库的历史最高水位,这也就是HWM的原始含义,当然不能说一个水库没水了,就说该水库的历史最高水位为0。但是如果我们在表上使用了truncate命令,则该表的HWM会被重新置为0。
高水位标记的影响
- 全表扫描要读出直到HWM标记的所有的属于该表的数据块(used space),即使该表中没有任何数据;
- 即使HWM下有空闲的数据块,如果在插入数据时使用了append参数,则在插入数据时使用HWM以上的数据块,HWM会自动增大。
Oracle表段中的高水位线
每个Oracle数据块在ASSM段中都属于下面的一种状态:
- 高水位线以上
这些块未被格式化且从来没有被使用过。
- 高水位线以下(3类)
--已经分配,但是未格式化和未使用;
--格式化,且含有数据;
--格式化,不含有数据,因为数据被删除了。
-
在创建表的时候,HWM位于segment左边的起始处,因为没有数据插入,segment中全部的block未被格式化和从未被使用
-
假设一个事务将行数据插入到segmnet中,数据库必须分配一组数据块去保存行信息,被分配的数据块全在HWM之下,数据库格式化一个位图块来保存元数据,但是没有指定是哪一个数据块。
在HWM以下的数据块是被分配的,在HWM以上的数据块从未被分配和格式化。当insert时,数据可以写到有可用空间的任何块中。low HWM以下的部分,所有的块都被格式化,因为它们要么含有数据,要么以前包含数据。
-
当insert时,数据库选择在HWM和low HWM之间 的任意一个块进行写入,或者是HWM以下的一个有空闲空间的块进行写入。
-
low HWM 对于全表扫描是非常重要的。因为HWM以下的数据块 只有在要使用时才格式化,有一些块还未格式化。基于这个原因,数据库会去bitmap块查询low HWM的位置,然后会去读low HWM以下的全部数据块,因为数据库已经知道这些块全部被格式化了,对于在low HWM和HWM之间的数据块,数据库会挑选那些已经格式化了的数据块进行读操作。
-
假设一个新的事务进行插入操作,但是bitmap指示目前在HWM以下已经没有足够的空间了,数据库会向右移动该segment的HWM,分配一组新的未格式化的数据块。
降低HWM
SHRINK
shrink技术是一种段收缩技术,可将表与索引高水位以下的碎片进行有效压缩,并将高水位进行回退。
step1. 启用行移动
SQL > ALTER TABLE table_name ENABLE ROW MOVEMENT;
step2. 压缩数据及下调HWM
SQL > ALTER TABLE table_name SHRINK SPACE CASCADE; --压缩表及相关数据段并下调HWM
或
SQL > ALTER TABLE table_name SHRINK SPACE COMPACT; --只压缩数据不下调HWM
SQL > ALTER TABLE table_name SHRINK SPACE; --下调HWM
即该技术可以一次性压缩数据及下调HWM,也可以分两个阶段进行,第一阶段:在业务高峰,只压缩数据不下调HWM ,第二阶段:在业务空闲时,下调HWM。
MOVE Tablespace
SQL > ALTER TABLE table_name MOVE TABLESPACE tablepsace_name;
--MOVE后不跟参数也行,不跟参数还是原来的表空间;
--MOVE后需要重建索引;
--如果以后还要向表里插入数据,没必要MOVE。MOVE释放出来的空间,只能这个表使用,其它的表或者segment无法使用。
CTAS技术
即重建表技术。
SQL > CREATE TABLE new_table_name
AS
SELECT * FROM old_table_name; --将表的数据写入到一张新的表里
SQL > DROP TABLE old_table_name; --删除旧表
SQL > RENAME table new_table_name TO old_table_name --将新表名更改为旧表名
DEALLOCATE技术
利用DEALLOCATE技术可以回收HWM以上从未使用过的数据块。语法如下
SQL > ALTER TABLE table_name DEALLOCATE UNUSED [KEEP integer];
SQL > ALTER INDEX index_name DEALLOCATE UNUSED [KEEP integer];
MOVE与SHRINK的区别
|------|------------------------------------------------------------------------------------------|---------------------------------------------------------------------------|
| | MOVE | SHRINK(仅对ASSM有效) |
| 本质 | move实际上是block级别的数据块拷贝,对表进行move后,该表所在blockid会发生改变数据的rowid自然也会发生改变,但是数据在table中的存储顺序并没有发生改变 | shrink是对行数据进行移动。对表进行shrink后,部分行数据的rowid发生了变化,而table所位于的block区域的位置却没有发生变化。 |
| 重建索引 | 需要重建 | 可以通过cascade关键字重建 |
| 锁 | TM(exclusive) | TM(SX) |
| 空间要求 | 需要有原表大的空闲空间 | 不需要额外的空间 |
| 效果 | 压缩后会回收空间 | 压缩后会回收空间 |