SQL调优指南笔记15:Controlling the Use of Optimizer Statistics
Posted dingdingfish
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SQL调优指南笔记15:Controlling the Use of Optimizer Statistics相关的知识,希望对你有一定的参考价值。
本文为SQL Tuning Guide第15章“Controlling the Use of Optimizer Statistics”的笔记。
重要基本概念
- pending statistics
Unpublished optimizer statistics. By default, the optimizer uses published statistics but does not use pending statistics.
未发布的优化器统计信息。 默认情况下,优化器使用已发布的统计信息,但不使用待定的统计信息。
使用 DBMS_STATS,您可以指定优化器何时以及如何使用统计信息。
15.1 Locking and Unlocking Optimizer Statistics
您可以锁定统计信息以防止它们发生变化。
统计信息被锁定后,您无法对统计信息进行修改,直到统计信息被解锁。 当您希望保证统计信息和生成的计划永远不会改变时,锁定过程在静态环境中很有用。 例如,您可能希望防止 DBMS_STATS_JOB 进程在表或模式上收集新的统计信息,例如高度易变的表。
当您锁定表上的统计信息时,所有相关的统计信息都被锁定。 锁定统计包括表统计、列统计、直方图和依赖索引统计。 要在统计信息被锁定时覆盖它们,您可以将各种 DBMS_STATS 过程(例如 DELETE_STATS 和 RESTORE_STATS)中的 FORCE 参数的值设置为 true。
15.1.1 Locking Statistics
DBMS_STATS 包提供了两个用于锁定统计信息的过程:LOCK_SCHEMA_STATS 和 LOCK_TABLE_STATS。
本教程假定以下内容:
- 您收集了有关 oe.orders 表和 hr 模式的统计信息。
- 您希望防止 oe.orders 表统计信息和 hr 模式统计信息发生更改。
BEGIN
DBMS_STATS.LOCK_TABLE_STATS('OE','ORDERS');
END;
/
BEGIN
DBMS_STATS.LOCK_SCHEMA_STATS('HR');
END;
/
15.1.2 Unlocking Statistics
DBMS_STATS 包提供了两个解锁统计信息的过程:UNLOCK_SCHEMA_STATS 和 UNLOCK_TABLE_STATS。
BEGIN
DBMS_STATS.UNLOCK_TABLE_STATS('OE','ORDERS');
END;
/
BEGIN
DBMS_STATS.UNLOCK_SCHEMA_STATS('HR');
END;
/
15.2 Publishing Pending Optimizer Statistics
默认情况下,当统计信息收集结束时,数据库会自动发布统计信息。
或者,您可以使用挂起的统计信息来保存统计信息,而不是在收集后立即发布它们。 此技术对于在会话中测试具有未决统计信息的查询很有用。 当测试结果令人满意时,您可以发布统计信息以使它们可用于整个数据库。
15.2.1 About Pending Optimizer Statistics
数据库将待处理的统计信息存储在数据字典中,就像已发布的统计信息一样。
默认情况下,优化器使用已发布的统计信息。 您可以通过将 OPTIMIZER_USE_PENDING_STATISTICS 初始化参数设置为 true(默认为 false)来更改默认行为。
下图的上半部分显示了优化器收集 sh.customers 表的统计信息并将它们存储在数据字典中并处于挂起状态。 该图的底部显示优化器仅使用已发布的统计信息来处理对 sh.customers 的查询。
在某些情况下,优化器可以结合使用已发布和待处理的统计信息。 例如,数据库存储客户表的已发布和未决统计信息。 对于订单表,数据库仅存储已发布的统计信息。 如果 OPTIMIZER_USE_PENDING_STATS = true,则优化器使用客户的待定统计信息和订单的已发布统计信息。 如果 OPTIMIZER_USE_PENDING_STATS = false,则优化器使用已发布的客户和订单统计信息。
15.2.2 User Interfaces for Publishing Optimizer Statistics
您可以使用 DBMS_STATS 包来执行与发布统计信息相关的操作。
下表列出了相关的程序单元。
表 15-1 与发布优化器统计信息相关的 DBMS_STATS 程序单元
程序单元 | 描述 |
---|---|
GET_PREFS | 检查统计信息是否在 DBMS_STATS 收集它们后立即自动发布。 对于参数 PUBLISH,true 表示必须在数据库收集统计信息时发布统计信息,而 false 表示数据库必须保持统计信息挂起。 |
SET_TABLE_PREFS | 在表级别将 PUBLISH 设置设置为 true 或 false。 |
SET_SCHEMA_PREFS | 在模式级别将 PUBLISH 设置设置为 true 或 false。 |
PUBLISH_PENDING_STATS | 发布所有对象或仅指定对象的有效待定统计信息。 |
DELETE_PENDING_STATS | 删除待定统计信息。 |
EXPORT_PENDING_STATS | 导出待定统计信息。 |
初始化参数 OPTIMIZER_USE_PENDING_STATISTICS 确定数据库在可用时是否使用挂起的统计信息。 默认值为 false,这意味着优化器仅使用已发布的统计信息。 设置为 true 以指定优化器改为使用任何现有的挂起统计信息。 最佳实践是在会话级别而不是在数据库级别设置此参数。
您可以使用来自数据字典视图的已发布统计信息的访问信息。 表 15-2 列出了相关视图。
表 15-2 与发布优化器统计信息相关的视图
视图 | 描述 |
---|---|
USER_TAB_STATISTICS | 显示当前用户可访问的表的优化器统计信息。 |
USER_TAB_COL_STATISTICS | 显示从 ALL_TAB_COLUMNS 中提取的列统计信息和直方图信息。 |
USER_PART_COL_STATISTICS | 显示当前用户拥有的表分区的列统计信息和直方图信息。 |
USER_SUBPART_COL_STATISTICS | 描述当前用户拥有的分区对象的子分区的列统计信息和直方图信息。 |
USER_IND_STATISTICS | 显示当前用户可访问的索引的优化器统计信息。 |
USER_TAB_PENDING_STATS | 描述当前用户可访问的表、分区和子分区的未决统计信息。 |
USER_COL_PENDING_STATS | 描述当前用户可访问的列的未决统计信息。 |
USER_IND_PENDING_STATS | 描述使用 DBMS_STATS 包收集的当前用户可访问的表、分区和子分区的未决统计信息。 |
15.2.3 Managing Published and Pending Statistics
本节说明如何使用 DBMS_STATS 程序单元来更改优化器统计信息的发布行为,以及如何导出和删除这些统计信息。
本教程假定以下内容:
- 您想更改 sh.customers 和 sh.sales 表的首选项,以便新收集的统计信息具有待定状态。
- 您希望当前会话使用待定统计信息。
- 您希望在 sh.customers 表上收集和发布待定统计信息。
- 您收集了 sh.sales 表上的待定统计信息,但决定删除它们而不发布它们。
- 您想要更改 sh.customers 和 sh.sales 表的首选项,以便发布新收集的统计信息。
使用 GET_PREFS 时,您还可以指定模式和表名。 如果已设置,该函数将返回表首选项。 否则,该函数返回全局首选项。值 true 表示数据库在收集统计信息时发布统计信息。 除非设置了特定的表首选项,否则每个表都使用此值。
SELECT DBMS_STATS.GET_PREFS('PUBLISH') PUBLISH FROM DUAL;
PUBLISH
-------
TRUE
查询待定统计信息。此示例显示数据库当前未存储 sh 模式的待定统计信息。
SELECT * FROM USER_TAB_PENDING_STATS;
no rows selected
更改 sh.customers 表的发布首选项。
EXEC DBMS_STATS.SET_TABLE_PREFS('sh', 'customers', 'publish', 'false');
随后,当您在客户表上收集统计信息时,数据库不会在收集作业完成时自动发布统计信息。 相反,数据库将新收集的统计信息存储在 USER_TAB_PENDING_STATS 表中。
收集 sh.customers 的统计信息。
EXEC DBMS_STATS.GATHER_TABLE_STATS('sh','customers');
查询待定的统计信息。
SELECT TABLE_NAME, NUM_ROWS FROM USER_TAB_PENDING_STATS;
TABLE_NAME NUM_ROWS
------------------------------ ----------
CUSTOMERS 55500
指示优化器使用此会话中的待定统计信息。
ALTER SESSION SET OPTIMIZER_USE_PENDING_STATISTICS = true;
发布 sh.customers 的待定统计信息。
EXEC DBMS_STATS.PUBLISH_PENDING_STATS('SH','CUSTOMERS');
删除待定统计信息。
EXEC DBMS_STATS.DELETE_PENDING_STATS('SH','CUSTOMERS');
恢复默认值:
EXEC DBMS_STATS.SET_TABLE_PREFS('sh', 'customers', 'publish', null);
15.3 Creating Artificial Optimizer Statistics for Testing
要为优化器提供用户创建的统计信息以进行测试,您可以使用 DBMS_STATS.SET_*_STATS 过程。 这些过程为优化器提供了指定统计信息的人工值。
15.3.1 About Artificial Optimizer Statistics
出于测试目的,您可以使用 DBMS_STATS.SET_*_STATS 过程为表、索引或系统手动创建人工统计信息。
当 stattab 为空时,DBMS_STATS.SET_*_STATS 过程直接将人工统计数据插入数据字典。 或者,您可以指定用户创建的表。
注意:DBMS_STATS.SET_*_STATS 过程仅用于开发测试。 不要在生产数据库中使用它们。 如果您在数据字典中设置统计信息,那么 Oracle 数据库会将设置的统计信息视为“真实”统计信息,这意味着当统计信息收集作业不符合陈旧标准时,它们可能不会重新收集人工统计信息。
DBMS_STATS.SET_*_STATS 过程的典型用例是:
- 显示执行计划如何随着表中的行数或块数的变化而变化
例如,SET_TABLE_STATS 可以将小表或空表中的行数和块数设置为大数。当您使用更改的统计信息执行查询时,优化器可能会更改执行计划。例如,增加的行数可能会导致优化器选择索引扫描而不是全表扫描。通过试验不同的值,您可以看到优化器将如何随时间改变其执行计划。 - 为临时表创建真实的统计信息
您可能想看看在多个 SQL 语句中引用大型临时表时优化器会做什么。您可以创建一个常规表,加载代表性数据,然后使用 GET_TABLE_STATS 检索统计信息。创建临时表后,您可以通过调用 SET_TABLE_STATS 来“欺骗”优化器使用这些统计信息。
或者,您可以为用户创建的表中的统计信息指定唯一 ID。 SET_*_STATS
过程具有相应的 GET_*_STATS
过程。
DBMS_STATS过程 | 描述 |
---|---|
SET_TABLE_STATS | 使用 numrows、numblks 和 avgrlen 等参数设置表或分区统计信息。 如果数据库使用In-Memory Column store,可以设置im_imcu_count为表或分区的IMCU个数,im_block_count为表或分区的块数。 对于外部表,scanrate 指定了以 MB/秒为单位扫描数据的速率。 优化器使用缓存数据来估计用于索引或统计表访问的缓存块的数量。 总成本是从磁盘读取数据块的 I/O 成本、从缓冲区缓存中读取缓存块的 CPU 成本和处理数据的 CPU 成本。 |
SET_COLUMN_STATS | 使用 distcnt、密度、nullcnt 等参数设置列统计信息。 在此过程处理用户定义统计的版本中,使用 stattypname 指定要存储在数据字典中的统计类型。 |
SET_SYSTEM_STATS | 使用 iotfrspeed、sreadtim 和 cpuspeed 等参数设置系统统计信息。 |
SET_INDEX_STATS | 使用 numrows、numlblks、avglblk、clstfct 和 indlevel 等参数设置索引统计信息。 在此过程处理用户定义统计的版本中,使用 stattypname 指定要存储在数据字典中的统计类型。 |
15.3.2 Setting Artificial Optimizer Statistics for a Table
本主题说明如何使用 DBMS_STATS.SET_TABLE_STATS 为表设置人工统计信息。 SET_INDEX_STATS 和 SET_SYSTEM_STATS 的基本步骤相同。
请注意以下任务先决条件:
- 对于不属于 SYS 的对象,您必须是该对象的所有者,或者具有 ANALYZE ANY 权限。
- 对于 SYS 拥有的对象,您必须具有 ANALYZE ANY DICTIONARY 权限或 SYSDBA 权限。
- 为表、列或索引调用 GET_*_STATS 时,引用的对象必须存在。
此任务假定以下内容:
- 您拥有对指定表使用 DBMS_STATS.SET_TABLE_STATS 所需的权限。
- 您打算将统计信息存储在数据字典中。
15.3.3 Setting Optimizer Statistics: Example
此示例说明如何收集表的优化器统计信息、设置人工统计信息,然后比较优化器根据不同的统计信息选择的计划。
此示例假设:
- 您以具有 DBA 权限的用户身份登录到数据库。
- 您想测试优化器何时选择索引扫描。
CREATE TABLE contractors (
con_id NUMBER,
last_name VARCHAR2(50),
salary NUMBER,
CONSTRAINT cond_id_pk PRIMARY KEY(con_id) );
CREATE INDEX salary_ix ON contractors(salary);
INSERT INTO contractors VALUES (8, 'JONES',1000);
COMMIT;
EXECUTE DBMS_STATS.GATHER_TABLE_STATS( user, tabname => 'CONTRACTORS' );
-- 输出为1
SELECT NUM_ROWS FROM USER_TABLES WHERE TABLE_NAME = 'CONTRACTORS';
-- 输出为1
SELECT NUM_ROWS FROM USER_INDEXES WHERE INDEX_NAME = 'SALARY_IX';
-- 查询薪水为 1000 的承包商,使用 dynamic_sampling 提示禁用动态采样:
SELECT /*+ dynamic_sampling(contractors 0) */ *
FROM contractors
WHERE salary = 1000;
-- 原文说:因为表中只存在 1 行,所以优化器选择全表扫描而不是索引范围扫描。
-- 可是我的结果表明优化器正确选择了访问路径
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR);
---------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 2 (100)| |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| CONTRACTORS | 1 | 12 | 2 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | SALARY_IX | 1 | | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------
-- 手工设置统计信息
BEGIN
DBMS_STATS.SET_TABLE_STATS(
ownname => user
, tabname => 'CONTRACTORS'
, numrows => 2000
, numblks => 10 );
END;
/
BEGIN
DBMS_STATS.SET_INDEX_STATS(
ownname => user
, indname => 'SALARY_IX'
, numrows => 2000 );
END;
/
-- 输出均为2000
SELECT NUM_ROWS FROM USER_TABLES WHERE TABLE_NAME = 'CONTRACTORS';
SELECT NUM_ROWS FROM USER_INDEXES WHERE INDEX_NAME = 'SALARY_IX';
-- 刷新共享池以消除计划重用的可能性,然后执行相同的查询
ALTER SYSTEM FLUSH SHARED_POOL;
SELECT /*+ dynamic_sampling(contractors 0) */ *
FROM contractors
WHERE salary = 1000;
-- 基于人工生成的行数和块分布统计信息,优化器认为索引范围扫描更具成本效益。
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR);
---------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 2 (100)| |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| CONTRACTORS | 2000 | 24000 | 2 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | SALARY_IX | 2000 | | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------
-- 清理
drop table contractors purge;
以上是关于SQL调优指南笔记15:Controlling the Use of Optimizer Statistics的主要内容,如果未能解决你的问题,请参考以下文章
SQL调优指南笔记1:Introduction to SQL Tuning