fej*_*oco 16 oracle optimization oracle-11g-r2
我的测试数据库中有一个包含 250K 行的表。(生产中有几亿个,我们可以在那里观察到同样的问题。)该表有一个 nvarchar2(50) 字符串标识符,不为空,上面有一个唯一索引(不是 PK)。
标识符由第一部分组成,它在我的测试数据库中有 8 个不同的值(生产中大约有 1000 个),然后是一个 @ 符号,最后是一个 1 到 6 位长的数字。例如,可能有 5 万行以“ABCD_BGX1741F_2006_13_20110808.xml@”开头,后面跟着 5 万个不同的数字。
当我根据其标识符查询单行时,基数估计为1,成本很低,工作正常。当我在 IN 表达式或 OR 表达式中查询多个具有多个标识符的行时,对索引的估计是完全错误的,因此使用了全表扫描。如果我用提示强制索引,它会非常快,全表扫描实际上执行的速度要慢一个数量级(并且在生产中要慢得多)。所以这是一个优化器问题。
作为测试,我使用完全相同的 DDL 和完全相同的内容复制了表(在相同的模式+表空间中)。我在第一个表上重新创建了唯一索引以获得良好的度量,并在克隆表上创建了完全相同的索引。我做了一个DBMS_STATS.GATHER_SCHEMA_STATS('schemaname',estimate_percent=>100,cascade=>true);. 您甚至可以看到索引名称是连续的。所以现在这两个表唯一的区别是第一个是在很长一段时间内以随机顺序加载的,块分散在磁盘上(与其他几个大表一起在一个表空间中),第二个是作为一个批处理加载的插入-选择。除此之外,我想不出有什么不同。(自上次大删除以来,原始表已缩小,此后没有进行过一次删除。)
这里是sick和clone表的查询计划(黑色笔刷下的字符串全图相同,灰色笔刷下也是如此。):

(在这个例子中,有1867行以刷黑的标识符开始。2行查询产生1867*2的基数,3行查询产生1867*3的基数,等等。不能巧合的是,Oracle 似乎并不关心标识符的结尾。)
什么可能导致这种行为?显然,在生产中重新创建表会非常昂贵。
USER_TABLES:http : //i.stack.imgur.com/nDWze.jpg USER_INDEXES:http : //i.stack.imgur.com/DG9um.jpg我只更改了架构和表空间名称。您可以看到表和索引名称与查询计划屏幕截图中的相同。
我找到了解决方案!它太漂亮了,我实际上学到了很多关于 Oracle 的知识。
一句话:直方图。
我开始阅读大量有关 Oracle 的 CBO 工作原理的文章,并偶然发现了直方图。我没有完全理解,所以我查看了 USER_HISTOGRAMS 表,瞧。生病的桌子有几行,克隆的桌子几乎没有。对于病床,8 个不同的标识符起始部分中的每一个都有一行。这就是关键:在@ 符号之前,它们被截断为 32 个字符。正如我所说,键的第一部分是高度重复的,它们在@ 符号之后变得不同。
直方图似乎比唯一索引对于给定值的基数始终为 0 或 1 的简单事实更强大。当我查询 2+ 行时,Oracle 查看了直方图,它认为该标识符起始部分可能有数万个值,并且它让 CBO 偏离了轨道。
我删除了旧表中该列的直方图,问题就解决了!
更多阅读:https : //blogs.oracle.com/optimizer/entry/how_do_i_drop_an_existing_histogram_on_a_column_and_stop_the_auto_stats_gathering_job_from_creating
(这回答了关于直方图为何不同的另一个问题。)
默认情况下,直方图是根据列偏斜以及该列是否用于相关谓词中创建的。复制 DDL 和数据是不够的,工作负载信息也很重要。
根据性能调优指南:
删除表时,自动直方图收集功能使用的工作负载信息和 RESTORE_*_STATS 过程使用的已保存统计历史记录都将丢失。如果没有这些数据,这些功能将无法正常运行。
例如,这是一个包含偏斜数据但没有直方图的表:
drop table test1;
create table test1(a date);
insert into test1 select date '2000-01-01'+level from dual connect by level <= 10;
insert into test1 select date '2000-01-01' from dual connect by level <= 1000;
begin
dbms_stats.gather_table_stats(user, 'TEST1');
end;
/
select histogram from user_tab_columns where table_name = 'TEST1';
HISTOGRAM
---------
NONE
Run Code Online (Sandbox Code Playgroud)
运行相同的事情,但在收集统计信息之前进行查询,将生成直方图。
drop table test1;
create table test1(a date);
insert into test1 select date '2000-01-01'+level from dual connect by level <= 10;
insert into test1 select date '2000-01-01' from dual connect by level <= 1000;
select count(*) from test1 where a = sysdate; --Only new line
begin
dbms_stats.gather_table_stats(user, 'TEST1');
end;
/
select histogram from user_tab_columns where table_name = 'TEST1';
HISTOGRAM
---------
FREQUENCY
Run Code Online (Sandbox Code Playgroud)
我就此事给 Jonathan Lewis 发了电子邮件,并得到了非常有帮助的回复:
计算中的奇怪之处是基于字符的直方图限制的结果,特别是:
http://jonathanlewis.wordpress.com/2010/10/13/frequency-histogram-5/ http://jonathanlewis.wordpress.com/2010/10/19/frequency-histograms-6/
查看示例,查询是针对 IN 列表,而不是针对单行,因此我最初的猜测是优化器使用了通用策略来计算多行选择性,而不是为主键上的 IN 列表。我想他们不会太难识别这种情况,但开发人员可能不认为值得付出努力。
我强烈建议阅读他链接的博客文章,它们详细描述了您遇到的直方图的限制,例如:
结论:如果您在适合频率直方图候选的列中有相当长且相似的字符串(例如,非常描述性的状态列),那么如果非常罕见的值看起来与非常流行的值相同,那么您就会遇到问题值最多为前 32 个字符。您可能会发现唯一的解决方案是更改合法值列表(尽管涉及虚拟列或基于函数的索引的各种策略可以绕过该问题)。