(一)问题背景

在使用impdp进行数据导入的时候,往往在导入表和索引的统计信息的时候,速度非常慢,因此我在使用impdp进行导入时,会使用exclude=table_statistics排除表的统计信息,从而加快导入速度,之后再手动收集统计信息。

图.impdp导入数据的时导入统计信息速度非常慢

导入语句如下:

  1. impdp user/password directory=DUMPDIR dumpfile=TEST01.dmp logfile=TEST01.log remap_schema=TEST_USER:TEST_USER123 exclude=table_statistics

手动收集统计信息语句如下:

  1. EXEC dbms_stats.gather_table_stats(ownname => 'LIJIAMAN',tabname => 'TEST01');

最近使用以上方法将数据还原到测试环境后,发现与生产环境执行计划存在偏差,本来应该走全表扫描的,却走了索引范围扫描。经过确认,是由于列的直方图统计信息未收集引发的执行计划偏差。


(二)列的直方图统计信息

什么是列的直方图统计信息呢?在Oracle数据库中,Oracle默认列上的值是在最小值与最大值之间均分布的,当在计算cardinatity时,会以均匀分布的方式计算,但是在实际生活中某些场景下数据并非均匀分布。举个列子,某公司有员工10000人,表A的列COL1记录员工的绩效(分别是:A、B、C、D,A最好,D最差),那么可能A占了15%,B占了60,C占了20%,D占了5%。很明显在该场景下数据并非均匀分布,假如以均匀分布的方式去统计员工的绩效,可能会导致执行计划失准。

当列的数据分布不均匀的时候,就需要统计列上的数据分布情况,从而走出正确的执行计划,列的直方图统计信息就是记录列上的数据分布情况的。


(三)异常模拟

STEP1:创建测试表test01

  1. create table test01
  2. (id number,
  3. name varchar2(10)
  4. );
  5. create index idx_test01_id on test01(id);

向test01中插入测试数据

  1. begin
  2. insert into test01 values(1,'a');
  3.  
  4. for i in 1..10 loop
  5. insert into test01 values(2,'b');
  6. end loop;
  7.  
  8. for i in 1..100 loop
  9. insert into test01 values(3,'c');
  10. end loop;
  11.  
  12. for i in 1..1000 loop
  13. insert into test01 values(4,'d');
  14. end loop;
  15.  
  16. commit;
  17. end;

查看数据分布情况:

  1. SQL> SELECT ID,NAME,COUNT(*) FROM test01 GROUP BY ID,NAME ORDER BY COUNT(*);
  2.  
  3. ID NAME COUNT(*)
  4. ---------- ---------- ----------
  5. 1 a 1
  6. 2 b 10
  7. 3 c 100
  8. 4 d 1000

STEP2:收集统计信息,因为上面查询过id列,故在收集统计信息的时候,会收集直方图的统计信息

  1. EXEC dbms_stats.gather_table_stats(ownname => 'LIJIAMAN',tabname => 'TEST01');

查看是否已经收集了直方图信息,发现id列上已经收集

  1. SQL> SELECT a.OWNER,a.TABLE_NAME,a.COLUMN_NAME,a.LOW_VALUE,a.HIGH_VALUE,a.NUM_BUCKETS,a.HISTOGRAM
  2. 2 FROM dba_tab_columns a
  3. 3 WHERE a.OWNER = 'LIJIAMAN' AND a.TABLE_NAME = 'TEST01';
  4.  
  5. OWNER TABLE_NAME COLUMN_NAME LOW_VALUE HIGH_VALUE NUM_BUCKETS HISTOGRAM
  6. --------- ----------- ------------ ------------ ------------ ----------- ---------------
  7. LIJIAMAN TEST01 ID C102 C105 4 FREQUENCY
  8. LIJIAMAN TEST01 NAME 61 64 1 NONE

查看直方图,已经将id列的4个值放入了4个bucket中:

  1. SQL> SELECT * FROM dba_tab_histograms a WHERE a.OWNER = 'LIJIAMAN' AND a.TABLE_NAME = 'TEST01';
  2.  
  3. OWNER TABLE_NAME COLUMN_NAME ENDPOINT_NUMBER ENDPOINT_VALUE ENDPOINT_ACTUAL_VALUE
  4. ----------- ------------ ------------- --------------- -------------- ----------------------
  5. LIJIAMAN TEST01 ID 1 1
  6. LIJIAMAN TEST01 ID 11 2
  7. LIJIAMAN TEST01 ID 111 3
  8. LIJIAMAN TEST01 ID 1111 4
  9. LIJIAMAN TEST01 NAME 0 5.036527952778
  10. LIJIAMAN TEST01 NAME 1 5.192296858534

STEP3:查看id=1和id=4的执行计划,当id=1时,走索引范围扫描,当id=4时,走全表扫描

id列存在直方图统计信息,当id=1时,走索引范围扫描 id列存在直方图统计信息,当id=4时,走全表扫描
  1. SELECT * FROM test01 WHERE ID=1
  2.  
  3. Plan Hash Value : 1151852672
  4.  
  5. ----------------------------------------------------------------------------------------
  6. | Id | Operation | Name | Rows | Bytes | Cost | Time |
  7. ----------------------------------------------------------------------------------------
  8. | 0 | SELECT STATEMENT | | 1 | 5 | 2 | 00:00:01 |
  9. | 1 | TABLE ACCESS BY INDEX ROWID | TEST01 | 1 | 5 | 2 | 00:00:01 |
  10. | * 2 | INDEX RANGE SCAN | IDX_TEST01_ID | 1 | | 1 | 00:00:01 |
  11. ----------------------------------------------------------------------------------------
  12.  
  13. Predicate Information (identified by operation id):
  14. ------------------------------------------
  15. * 2 - access("ID"=1)
  1. SELECT * FROM test01 WHERE ID=4
  2.  
  3. Plan Hash Value : 262542483
  4.  
  5. -----------------------------------------------------------------------
  6. | Id | Operation | Name | Rows | Bytes | Cost | Time |
  7. -----------------------------------------------------------------------
  8. | 0 | SELECT STATEMENT | | 1000 | 5000 | 3 | 00:00:01 |
  9. | * 1 | TABLE ACCESS FULL | TEST01 | 1000 | 5000 | 3 | 00:00:01 |
  10. -----------------------------------------------------------------------
  11.  
  12. Predicate Information (identified by operation id):
  13. ------------------------------------------
  14. * 1 - filter("ID"=4)

STEP4:接下来模拟数据迁移,排除统计信息

导出表test01

  1. expdp lijiaman/lijiaman directory=DUMPDIR tables=LIJIAMAN.TEST01 dumpfile =test01.dmp

删除原来的表:

  1. SQL> drop table test01;
  2. Table dropped

再次导入表,排除统计信息:

  1. impdp lijiaman/lijiaman directory=DUMPDIR dumpfile =test01.dmp exclude=table_statistics

查看表的统计信息,不存在统计信息:

  1. SQL> SELECT a.OWNER,a.TABLE_NAME,a.COLUMN_NAME,a.LOW_VALUE,a.HIGH_VALUE,a.NUM_BUCKETS,a.HISTOGRAM
  2. 2 FROM dba_tab_columns a
  3. 3 WHERE a.OWNER = 'LIJIAMAN' AND a.TABLE_NAME = 'TEST01';
  4.  
  5. OWNER TABLE_NAME COLUMN_NAME LOW_VALUE HIGH_VALUE NUM_BUCKETS HISTOGRAM
  6. -------------- --------------- --------------- ------------ ------------ ----------- ---------------
  7. LIJIAMAN TEST01 ID NONE
  8. LIJIAMAN TEST01 NAME NONE

STEP5:手动收集统计信息

  1. EXEC dbms_stats.gather_table_stats(ownname => 'LIJIAMAN',tabname => 'TEST01');

发现统计信息已经收集,但是不存在直方图的统计信息

  1. SQL> SELECT a.OWNER,a.TABLE_NAME,a.COLUMN_NAME,a.LOW_VALUE,a.HIGH_VALUE,a.NUM_BUCKETS,a.HISTOGRAM
  2. 2 FROM dba_tab_columns a
  3. 3 WHERE a.OWNER = 'LIJIAMAN' AND a.TABLE_NAME = 'TEST01';
  4.  
  5. OWNER TABLE_NAME COLUMN_NAME LOW_VALUE HIGH_VALUE NUM_BUCKETS HISTOGRAM
  6. --------- ----------- ----------- ----------- ----------- ----------- ---------------
  7. LIJIAMAN TEST01 ID C102 C105 1 NONE
  8. LIJIAMAN TEST01 NAME 61 64 1 NONE

STEP6:再次查看id=1和id=4的执行计划,当id=1或id=4时,都走索引范围扫描

id列未收集直方图统计信息,当id=1时,走索引范围扫描 id列未收集直方图统计信息,当id=4时,走索引范围扫描
  1. SELECT * FROM test01 WHERE ID=1
  2. Plan Hash Value : 1151852672
  3.  
  4. ----------------------------------------------------------------------------------------
  5. | Id | Operation | Name | Rows | Bytes | Cost | Time |
  6. ----------------------------------------------------------------------------------------
  7. | 0 | SELECT STATEMENT | | 278 | 1390 | 2 | 00:00:01 |
  8. | 1 | TABLE ACCESS BY INDEX ROWID | TEST01 | 278 | 1390 | 2 | 00:00:01 |
  9. | * 2 | INDEX RANGE SCAN | IDX_TEST01_ID | 278 | | 1 | 00:00:01 |
  10. ----------------------------------------------------------------------------------------
  11.  
  12. Predicate Information (identified by operation id):
  13. ------------------------------------------
  14. * 2 - access("ID"=1)
  1. SELECT * FROM test01 WHERE ID=4
  2.  
  3. Plan Hash Value : 1151852672
  4.  
  5. ----------------------------------------------------------------------------------------
  6. | Id | Operation | Name | Rows | Bytes | Cost | Time |
  7. ----------------------------------------------------------------------------------------
  8. | 0 | SELECT STATEMENT | | 278 | 1390 | 2 | 00:00:01 |
  9. | 1 | TABLE ACCESS BY INDEX ROWID | TEST01 | 278 | 1390 | 2 | 00:00:01 |
  10. | * 2 | INDEX RANGE SCAN | IDX_TEST01_ID | 278 | | 1 | 00:00:01 |
  11. ----------------------------------------------------------------------------------------
  12.  
  13. Predicate Information (identified by operation id):
  14. ------------------------------------------
  15. * 2 - access("ID"=4)

STEP7:再次收集统计信息,因为使用过了id列作为查询条件,故再次收集统计信息时,会收集id列的直方图信息:

  1. EXEC dbms_stats.gather_table_stats(ownname => 'LIJIAMAN',tabname => 'TEST01');

可以看到,此时已经收集了id列的直方图统计信息:

  1. SQL> SELECT a.OWNER,a.TABLE_NAME,a.COLUMN_NAME,a.LOW_VALUE,a.HIGH_VALUE,a.NUM_BUCKETS,a.HISTOGRAM
  2. 2 FROM dba_tab_columns a
  3. 3 WHERE a.OWNER = 'LIJIAMAN' AND a.TABLE_NAME = 'TEST01';
  4.  
  5. OWNER TABLE_NAME COLUMN_NAME LOW_VALUE HIGH_VALUE NUM_BUCKETS HISTOGRAM
  6. ------------------------------ ------------------------------ ------------------------------ ------------- ------------- ----------- ---------------
  7. LIJIAMAN TEST01 ID C102 C105 4 FREQUENCY
  8. LIJIAMAN TEST01 NAME 61 64 1 NONE

执行计划已经按照我们想要的方式走:

id列重新收集直方图统计信息,当id=1时,走索引范围扫描 id列重新收集直方图统计信息,当id=4时,走全表扫描
  1. SELECT * FROM test01 WHERE ID=1
  2.  
  3. Plan Hash Value : 1151852672
  4.  
  5. ----------------------------------------------------------------------------------------
  6. | Id | Operation | Name | Rows | Bytes | Cost | Time |
  7. ----------------------------------------------------------------------------------------
  8. | 0 | SELECT STATEMENT | | 1 | 5 | 2 | 00:00:01 |
  9. | 1 | TABLE ACCESS BY INDEX ROWID | TEST01 | 1 | 5 | 2 | 00:00:01 |
  10. | * 2 | INDEX RANGE SCAN | IDX_TEST01_ID | 1 | | 1 | 00:00:01 |
  11. ----------------------------------------------------------------------------------------
  12.  
  13. Predicate Information (identified by operation id):
  14. ------------------------------------------
  15. * 2 - access("ID"=1)
  1. SELECT * FROM test01 WHERE ID=4
  2.  
  3. Plan Hash Value : 262542483
  4.  
  5. -----------------------------------------------------------------------
  6. | Id | Operation | Name | Rows | Bytes | Cost | Time |
  7. -----------------------------------------------------------------------
  8. | 0 | SELECT STATEMENT | | 1000 | 5000 | 3 | 00:00:01 |
  9. | * 1 | TABLE ACCESS FULL | TEST01 | 1000 | 5000 | 3 | 00:00:01 |
  10. -----------------------------------------------------------------------
  11.  
  12. Predicate Information (identified by operation id):
  13. ------------------------------------------
  14. * 1 - filter("ID"=4)

(四)总结

在使用expdp/impdp进行导出/导入数据的时,统计信息是非常重要的,对于大部分统计信息,我们可以在导入结束之后收集获得。但是对于列的直方图统计信息,Oracle默认收集的方式是auto,即Oracle会根据用户对列的使用情况进行判断是否收集直方图统计信息,然而数据刚迁移完成,在表还未使用的情况下收集统计信息,往往收集不到列的直方图信息,这就造成了执行计划异常,这种情况通常在下一次收集统计信息之后会有所改变。

参考文档:

DBMS_STATS With METHOD_OPT =>'..SIZE auto' May Not Collect Histograms (Doc ID 557594.1)

Oracle数据迁移后由列的直方图统计信息引起的执行计划异常的更多相关文章

  1. Oracle数据迁移至HBase操作记录

    Oracle数据迁移至HBase操作记录 @(HBase) 近期需要把Oracle数据库中的十几张表T级别的数据迁移至HBase中,过程中遇到了许多苦难和疑惑,在此记录一下希望能帮到一些有同样需求的兄 ...

  2. Oracle数据迁移expdp/impdp

    Oracle数据迁移expdp/impdp目的:指导项目侧自行进行简单的数据泵迁移工作. 本文实验环境:Oracle 11.2.0.4,利用数据库自带的scott示例用户进行试验测试. 1.首先需要创 ...

  3. UNIQUEIDENTIFIER列上的统计信息

    UNIQUEIDENTIFIER列上的统计信息非常有意思,在它上面有一些很令人讨厌的行为.我们来看下. 问题重现(The repro) 为了向你展示我们刚抱怨的行为,我用下列简单的表定义创建了一个数据 ...

  4. Oracle数据迁移至MySQL

    ORACLE DB: 11.2.0.3.0 MYSQL DB: 5.5.14 因项目需求,需要将ORACLE生产中数据迁移至MYSQL数据库中作为初始数据,方法有如下几种: 1.ORACLE OGG ...

  5. Oracle数据迁移笔记-Rownum与序列的自增长的组合用法技巧

    Rownum与序列的自增长的组合用法技巧 根据序列自增长的步长规律,结合表行记录Rownum值的规则批量生成表的行记录主键的用法技巧 案例如下: CREATE OR REPLACE PROCEDURE ...

  6. oracle数据迁移之Exp和Expdp导出数据的性能对比与优化

    https://wangbinbin0326.github.io/2017/03/31/oracle%E6%95%B0%E6%8D%AE%E8%BF%81%E7%A7%BB%E4%B9%8BExp%E ...

  7. 【Oracle 数据迁移】环境oracle 11gR2,exp无法导出空表的表结构【转载】

    今天做数据迁移,但是发现有些空表无法exp,后来找到问题所在. [原文]:http://www.cnblogs.com/wenlong/p/3684230.html 11GR2中有个新特性,当表无数据 ...

  8. Mysql 数据迁移后 启动出错

    今天上班后不知道为什么,mysql一直无法启动,折腾了半天于是决定重装 我本地的server用的是wamp , 重装的时候, 要进行数据备份 , 我使用的最简单粗暴的备份方式, 就是直接进入到mysq ...

  9. Oracle 数据迁移(从Oracle11G迁移到更高的版本号Oracle10G低版本号)

    1.数据库状况    生产环境是11G,linux系统,測试环境是10G,windows系统,须要从生产环境导出一个用户下全部的数据,导入測试环境中. 由于数据量比較小,准备採用EXP和IMP工具来做 ...

随机推荐

  1. 实验三 Java基本程序设计

    第一部分:理论知识复习部分 第一章:第一章介绍的是Java程序设计的概述,通过两周的Java学习中,重温了Java“白皮书的关键术语,更深一步理解乐11个关键术语. 第二章:本章主要介绍如何安装JDK ...

  2. HTML5移动端最新兼容问题解决方案

    1.安卓浏览器看背景图片,有些设备会模糊.用同等比例的图片在PC机上很清楚,但是手机上很模糊,原因是什么呢?经过研究,是devicePixelRatio作怪,因为手机分辨率太小,如果按照分辨率来显示网 ...

  3. 还不会K8S吗?先从kubeadm开始吧

    目录 1. 准备工作 1.1 机器准备 1.2 系统配置 1.2.1 主机名及域名解析 1.2.2 免密登录 1.2.3 配置yum源 1.2.4 安装必要依赖包 1.2.5 关闭防火墙.SELinu ...

  4. 程序员使用IDEA这些插件后,办公效率提升100%(持续更新中)

    IDEA一些不错的插件分享 目录 IDEA一些不错的插件分享 插件集合 CamelCase Translation LiveEdit MarkDown Navigator Jrebel CheckSt ...

  5. Java——使用ObjectMapper.writeValueAsString时报错The type com.fasterxml.jackson.core.JsonProcessingException cannot be resolved. It is indirectly referenced from required .class files

    报错信息: The type com.fasterxml.jackson.core.JsonProcessingException cannot be resolved. It is indirect ...

  6. 项目工程化之git提交规范以及 CHANGELOG生成

    事先声明,本文是参考了其他大神的博客之后自己尝试的记录,具体可以参考如下 链接 先说说git 提交规范把,这里基本都是这个工具 cz-customizable 1,安装 npm install cz- ...

  7. 自定义cursor鼠标 图片

    1.CSS3自定义鼠标样式 最近想要使用自定义鼠标样式,看了cursor的样式不好看,就想到cursor属性能不能自定义图片,翻看了下CSS3文档,发现是可以的 格式为:cursor:url('图片u ...

  8. 安装superset遇到的坑

    实验环境:ubuntu16.04 python环境: 3.6.7 安装参考:https://superset.incubator.apache.org/installation.html 特别提醒: ...

  9. [自动化-脚本]002.cocos2dx-lua lua代码windows加密批处理

    在开发软件的时候,我们都会在项目上线时候对代码进行加密,用来防止被不法分子盗走牟利.不同的语言有不同的加密方式,比较出名的有加壳,代码混淆等.在Lua开发cocos2dx的时候,框架会有提供加密的脚本 ...

  10. [工具推荐]001.FlipPDF使用教程

    FlipPDF是一个什么样的软件呢,他有什么实际用途呢?顾名思义,这是一个跟PDF有关的软件,没错它是一款把PDF转换成酷炫书籍的软件,他还支持PDF中的目录,也就是转换成的书籍,目录一样可以跳转的. ...