一、问题

  1. Oracle在收集统计信息时默认的采样比例是DBMS_STATS.AUTO_SAMPLE_SIZE,那么AUTO_SAMPLE_SIZE的值具体是多少?
  2. 假设采样比例为10%,那么在计算单个列的distinct时与实际的差别大吗?
  3. 有哪些采样算法?

二、实验

准备三张实验表,t1/t2/t3,这三张表的数据内容完全一致,我们分别使用100%、10%、AUTO_SAMPLE_SIZE的比例去收集他们的统计信息。

SQL> begin
2 dbms_stats.gather_table_stats(
3 ownname => 'BAO',
4 tabname => 'T1',
5 estimate_percent => 100
6 );
7 end;
8 / PL/SQL procedure successfully completed. SQL> begin
2 dbms_stats.gather_table_stats(
3 ownname => 'BAO',
4 tabname => 'T2',
5 estimate_percent => 10
6 );
7 end;
8 / PL/SQL procedure successfully completed. SQL> begin
2 dbms_stats.gather_table_stats(
3 ownname => 'BAO',
4 tabname => 'T3',
5 estimate_percent => dbms_stats.auto_sample_size
6 );
7 end;
8 / PL/SQL procedure successfully completed.

查看这三张表的统计信息,可以看到采用100%和AUTO_SAMPLE_SIZE这两种方式收集的统计信息的SAMPLE_SIZE相同,都是全量收集。

SQL> select table_name, num_rows, sample_size from user_tables where table_name in ('T1', 'T2', 'T3');

TABLE_NAME        NUM_ROWS  SAMPLE_SIZE
-------------- ---------- -----------
T1 145334 145334
T2 146190 14619
T3 145334 145334

官方文档并没有说明AUTO_SAMPLE_SIZE具体的值是多少,但是从实验结果来看,这个值就是100。这就回答了文章的第一个问题。

Oracle为什么会默认采用100%的方式来收集统计信息呢,在ASKTOM有同行就提出过这个问题“DBMS_STATS.AUTO_SAMPLE_SIZE seems to always generate 100%”,他们的回复是为了得到精确的distinct列值。接下来我们就来看下全量采集和部分采集列的distinct区别。

SQL> select a.column_name, a.num_distinct "t1.num_distinct", b.num_distinct "t2.num_distinct",
2 round((a.num_distinct - b.num_distinct) * 100 / a.num_distinct, 1) "diff",
3 a.sample_size "t1.sample_size", b.sample_size "t2.sample_size"
4 from (select table_name, column_name, num_distinct, sample_size from user_tab_col_statistics where table_name in ('T1')) a,
5 (select table_name, column_name, num_distinct, sample_size from user_tab_col_statistics where table_name in ('T2')) b
6 where a.column_name = b.column_name and a.num_distinct > 0 order by "diff" desc; COLUMN_NAME t1.num_distinct t2.num_distinct diff t1.sample_size t2.sample_size
------------------------------ --------------- --------------- ---------- -------------- --------------
OBJECT_NAME 64552 10300 84 145334 14619
SUBOBJECT_NAME 1015 385 62.1 68251 6856
TIMESTAMP 2585 1240 52 145212 14610
LAST_DDL_TIME 2490 1257 49.5 145212 14610
CREATED 2312 1209 47.7 145334 14619
NAMESPACE 21 15 28.6 145212 14610
OBJECT_TYPE 45 39 13.3 145334 14619
OWNER 80 71 11.3 145334 14619
TEMPORARY 2 2 0 145334 14619
DUPLICATED 1 1 0 145334 14619
STATUS 2 2 0 145334 14619
SHARDED 1 1 0 145334 14619
GENERATED 2 2 0 145334 14619
SECONDARY 1 1 0 145334 14619
SHARING 4 4 0 145334 14619
EDITIONABLE 2 2 0 25433 2531
ORACLE_MAINTAINED 2 2 0 145334 14619
APPLICATION 1 1 0 145334 14619
DEFAULT_COLLATION 1 1 0 16886 1705
DATA_OBJECT_ID 77785 78100 -.4 77822 7813
OBJECT_ID 145212 146100 -.6 145212 14610

T1表是全量收集,T2表是按10%的比例收集,从上面的结果可以看到,对于大部分字段通过部分采样的方式都能估算得很准确。但对于OBJECT_NAME这个列,估算出来的值和全量统计的差别很大,我们来看一下是什么原因导致的。

SQL> select count(*), object_name from t1 group by object_name order by count(*) desc;

  COUNT(*) OBJECT_NAME
---------- -----------------
690 S_AAA_CCD
690 S_ABA_CED
690 S_ACA_CCD
690 S_ADA_CCD
690 PK_AEA_CED
... 1 GV_$CON_SYSSTAT
1 GV_$DATAFILE
1 GV_$TABLESPACE
1 GV_$ROLLSTAT
1 GV_$PARAMETER

可以看到OBJECT_NAME这个列的数据分布极不均匀。因此对于分布不均匀的列,通过部分采样方式得到的distinct值与实际的distinct值差别就会比较大。这就回答了文章的第二个问题。

三、采样算法

以下是个人的一些娱乐性思考

  1. 等比放大,即(采样得到distinct值 / 采样行数) x 总行数。

    举个例子,假设表有1000行数据,只采样100行,A列有95个不同的值,即count(distinct A) / count(A) = 95%,那么等比放大很容易推导出1000行数据,有950个A的不同值。但是如果这100行中B列只有2个不同的值,即count(distinct B) / count(B) = 2%,那么对于1000行的表来讲,B的不同值是不是等于2% * 1000呢?很有可能不是,说不定全表就这两个不同值,例如性别。所以通过等比放大得到的distinct值就不准。这种算法有明显的缺陷。

  2. 按增长率估算,即将采样得到的前5%作为一个基数,采样得到的后5%作为一个增长率。(假设采样比例是10%)

    还是举个例子,假设表有1000行数据,只采样100行,采样的前50行,C列有40个不同值。采样的后50行,C列又多了30个不同值,即总共有70个不同值。那么后面的90%都会保持这个增长速度。则总体的C列不同值为40 + 30 * ((100-5)/5) = 610。再来看一种情况,假设采样的前50行,D列有2个不同值。采样的后50行,D列多了0个不同值,即不同值总数保持不变。那么后面的90%都会保持这个增长速度。则总体的D列不同值仍为2。这种方式似乎比等比采样更加合乎实际情况一点。接下来就用python去实现这个算法,看看与oracle的估算差别有多大。以下为python代码。

import random
import cx_Oracle def func(ins): SAMPLE_PERCENT = 10 # 采样比例%
sample_size = int(len(ins) * SAMPLE_PERCENT / 100)
# 对数据进行采样
sample = random.sample(ins, sample_size) head_half_sample = sample[0:int(len(sample)/2)] # 采样数据的前一半
head_half_sample_distinct = len(set(head_half_sample)) # 采样数据的前一半的distinct值
full_sample_distinct = len(set(sample)) # 采样数据的全量distinct值
tail_half_inc = full_sample_distinct - head_half_sample_distinct # 采样数据的distinct增量
estimate_distinct = round(head_half_sample_distinct + tail_half_inc * (100 - SAMPLE_PERCENT/2) / (SAMPLE_PERCENT/2)) return estimate_distinct def test(colname): DATABASE_URL = 'xxxxx'
conn = cx_Oracle.connect(DATABASE_URL)
curs = conn.cursor()
sql = 'select {} from t1'.format(colname)
curs.execute(sql)
tmpdata = []
for i in curs.fetchall():
tmpdata.append(i[0])
res = func(tmpdata)
curs.close()
conn.close()
return res for i in ['OBJECT_NAME', 'SUBOBJECT_NAME', 'TIMESTAMP', 'LAST_DDL_TIME', 'CREATED', 'NAMESPACE', 'OBJECT_TYPE',
'OWNER', 'TEMPORARY', 'DUPLICATED', 'STATUS', 'SHARDED', 'GENERATED', 'SECONDARY', 'SHARING', 'EDITIONABLE',
'ORACLE_MAINTAINED', 'APPLICATION', 'DEFAULT_COLLATION', 'DATA_OBJECT_ID', 'OBJECT_ID']:
print(i, '估算的distinct->', test(i))

运行结果

再来跟之前的一个表格进行对比,按增长率的方式估算的distinct值看上去也能接受。

COLUMN_NAME                     实际的distinct    数据库估算的distinct     python估算的distinct
------------------------------ --------------- -------------------- --------------------
OBJECT_NAME 64552 10300 74865
SUBOBJECT_NAME 1015 385 1210
TIMESTAMP 2585 1240 5641
LAST_DDL_TIME 2490 1257 5536
CREATED 2312 1209 4983
NAMESPACE 21 15 89
OBJECT_TYPE 45 39 92
OWNER 80 71 176
TEMPORARY 2 2 2
DUPLICATED 1 1 1
STATUS 2 2 2
SHARDED 1 1 1
GENERATED 2 2 2
SECONDARY 1 1 1
SHARING 4 4 4
EDITIONABLE 2 2 3
ORACLE_MAINTAINED 2 2 2
APPLICATION 1 1 1
DEFAULT_COLLATION 1 1 2
DATA_OBJECT_ID 77785 78100 77752
OBJECT_ID 145212 146100 145178

限于时间,测试到此结束。后面有时间再学点统计相关的知识。

Oracle收集统计信息的一些思考的更多相关文章

  1. 验证Oracle收集统计信息参数granularity数据分析的力度

    最近在学习Oracle的统计信息这一块,收集统计信息的方法如下: DBMS_STATS.GATHER_TABLE_STATS ( ownname VARCHAR2, ---所有者名字 tabname ...

  2. ORACLE收集统计信息

    1.     理解什么是统计信息 优化器统计信息就是一个更加详细描述数据库和数据库对象的集合,这些统计信息被用于查询优化器,让其为每条SQL语句选择最佳的执行计划.优化器统计信息包括: ·       ...

  3. ORACLE 收集统计信息

    1.     理解什么是统计信息优化器统计信息就是一个更加详细描述数据库和数据库对象的集合,这些统计信息被用于查询优化器,让其为每条SQL语句选择最佳的执行计划.优化器统计信息包括: ·        ...

  4. Oracle 收集统计信息11g和12C在差异

    Oracle 基于事务临时表11g和12C下,能看到临时表后收集的统计数据,前者记录被清除,后者没有,这是一个很重要的不同. 关于使用企业环境12C,11g,使用暂时表会造成时快时慢.之前我有帖子ht ...

  5. Oracle重建表索引及手工收集统计信息

    Oracle重建所有表的索引的sql: SELECT 'alter index ' || INDEX_NAME || ' rebuild online nologging;' FROM USER_IN ...

  6. Oracle自动统计信息的收集原理及实验

    [日期:2014-11-21]来源:Linux社区  作者:stevendbaguo[字体:大 中 小] 从Oracle Database 10g开始,Oracle在建库后就默认创建了一个名为GATH ...

  7. Oracle 手动收集统计信息

    收集oracle统计信息 优化器统计范围: 表统计: --行数,块数,行平均长度:all_tables:NUM_ROWS,BLOCKS,AVG_ROW_LEN: 列统计: --列中唯一值的数量(NDV ...

  8. Oracle 11g系统自己主动收集统计信息的一些知识

    在11g之前,当表的数据量改动超过总数据量的10%,就会晚上自己主动收集统计信息.怎样推断10%.之前的帖子有研究过:oracle自己主动统计信息的收集原理及实验.这个STALE_PERCENT=10 ...

  9. Oracle 判断 并 手动收集 统计信息 脚本

    CREATE OR REPLACE PROCEDURE SchameB.PRC_GATHER_STATS AUTHID CURRENT_USER IS BEGIN SYS.DBMS_STATS.GAT ...

随机推荐

  1. Think PHP 完整的带富文本格式以及图片上传,并且在页面上分页展示

    Think php6.0官网网址:序言 · ThinkPHP6.0完全开发手册 · 看云 (kancloud.cn) 下面是基础配置 第一步:创建TP框架,命名为tp composer create- ...

  2. 10_Linux基础-SHELL入门1

    @ 目录 10_Linux基础-SHELL入门1 一. 输入输出重定向 二. 2个特殊文件 三. here document 四. tee命令 五. 清空文件内容 六. SHELL入门 SHELL的变 ...

  3. ar9485 win10 笔记本电脑 无线网间歇性掉线

    问题  新安装了系统,刚开始上网正常,下载东西或者待机一段时间后掉线了. wifi都能搜到,就是连不上,重启电脑可以解决,但是每次重启很麻烦,必须找到治本的方法. 排除问题 1.手机连接没有问题,排除 ...

  4. Linux 配置ODBC连接Oracle

    在使用kdb_database_link 扩展插件连接Oracle数据库时,必须先配置ODBC,确保通过ODBC能连接Oracle数据库.以下是配置ODBC的过程. 一.安装ODBC 1.安装 [ro ...

  5. KingbaseES R6 集群主机锁冲突导致的主备切换案例

    ​ 案例说明: 主库在业务高峰期间,客户执行建表等DDL操作,主库产生"AccessExclusiveLock "锁,导致大量的事务产生锁冲突,大量的会话堆积,客户端session ...

  6. 使用Dockfile构建mysql镜像与初始化运行mysql容器

    使用docker 构建mysql镜像,并在容器初次创建时初始化数据 Dockerfile FROM mysql:5.7.23 MAINTAINER gradyjiang "jiangzhon ...

  7. 引擎之旅 前传:C++代码规范

    自己以前写代码时,一个项目一个风格.单人开发的工作使得我并没有注意到代码规范性和可读性的问题.每当项目结束后,看到自己杂乱无章的代码,完全没有二次开发和重构的欲望. 写代码就应该像写诗一样优雅. by ...

  8. 第四章:Django表单

    一.HTML表单概述 Django开发的是动态Web服务,而非单纯提供静态页面.动态服务的本质在于和用户进行互动,接收用户的输入,根据输入的不同,返回不同的内容给用户.返回数据是我们服务器后端做的,而 ...

  9. ELasticsearch忘记密码后重置超级用户密码

    创建一个临时的超级用户TestSuper: [root@cfeea elasticsearch]# ./bin/elasticsearch-users useradd TestSuper -r sup ...

  10. python动态参数

    Python的动态参数有两种,分别是*args和**kwargs,这里面的关键是一个和两个星号的区别,而不是args和kwargs在名字上的区别,实际上你可以使用*any或**whatever的方式. ...