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调优指南笔记9:Joins

SQL调优指南笔记9:Joins

SQL调优指南笔记1:Introduction to SQL Tuning

SQL调优指南笔记6:Explaining and Displaying Execution Plans

SQL调优指南笔记11:Histograms

SQL调优指南笔记11:Histograms