Kubernetes如何利用iptables

Linux内置的防火墙可以对IP数据包做一系列如过滤、更改、转发这样的操作,防火墙在对数据包做过滤决定时,有一套遵循的规则,这些规则存储在专用的数据包过滤表(table)中,而这些表集成在Linux 内核中。在数据包过滤表中,规则被分组放在我们所谓的链(chain)中。

我们通常说的iptables就是指Linux内置的防火墙,它实际上由两个组件netfilteriptables组成。netfilter组件也称为内核空间(kernel space),它是内核的一部分,由一些数据包过滤表组成,这些表包含内核用来控制数据包过滤处理的规则集。iptables组件是一个工具,也称为用户空间(user space),它使插入、修改和删除数据包过滤表中的规则变得容易,即我们通常用iptables来对chain中的规则进行添加、修改和删除以实现我们自己的防火墙逻辑。

   

iptables内置了5table,可以用man来查看这5table的名字和作用(其中我们最常用的就是filternat这两个表):

clip_image002

 

filternatmangle这三个表的作用为如下所示:

filter表是专门过滤包的,它也是iptables缺省的表,它内建三个链,可以毫无问题地对包进行DROPLOGACCEPTREJECT等操作。FORWARD链过滤所有不是本地产生的并且目的地不是本地的包,而 INPUT是针对那些目的地是本地的包。OUTPUT 用来过滤所有本地生成的包。

nat表的主要用处是网络地址转换,即Network Address Translation,缩写为NAT。做过NAT操作的数据包的地址就被改变了,当然这种改变是根据我们的规则进行的。属于一个流的包只会经过这个表一次。如果第一个包被允许做NATMasqueraded,那么余下的包都会自动地被做相同的操作。也就是说,余下的包不会再通过这个表,一个一个的被NAT,而是自动地完成。PREROUTING 链的作用是在包刚刚到达防火墙时改变它的目的地址(DNAT),如果需要的话。OUTPUT链改变本地产生的包的目的地址(DNAT)。POSTROUTING链在包就要离开防火墙之前改变其源地址(SNAT)。DNAT 是做目的网络地址转换的,就是重写包的目的IP地址。如果一个包被匹配了,那么和它属于同一个流的所有的包都会被自动转换,然后就可以被路由到正确的主机或网络DNAT是非常有用的。比如,你的Web服务器在LAN内部,而且没有可在Internet上使用的真实IP地址,那就可以使用DNAT让防火墙把所有到它自己HTTP端口的包转发给LAN内部真正的Web服务器。DNAT的目的地址也可以是一个范围,这样的话,DNAT会为每一个流随机分配一个地址。因此,我们可以用这个DNAT做某种类型的负载均衡

mangle这个表主要用来修改数据包。我们可以改变不同的包及包头的内容,比如TTLTOSMARK 注意MARK并没有真正地改动数据包,它只是在内核空间为包设了一个标记。防火墙内的其他的规则或程序可以使用这种标记对包进行过滤或高级路由。这个表有五个内建的链:PREROUTINGPOSTROUTINGOUTPUTINPUTFORWARDPREROUTING在包进入防火墙之后、路由判断之前改变包,POSTROUTING是在所有路由判断之后。 OUTPUT在确定包的目的之前更改数据包。INPUT在包被路由到本地之后,但在用户空间的程序看到它之前改变包。FORWARD在最初的路由判断之后、最后一次更改包的目的之前mangle包。注意,mangle表不能做任何NAT,它只是改变数据包的TTLTOSMARK

 

数据包在iptables中的各个表、各个chain中的流转过程如下图所示:

clip_image004

从上图可以看出,如果我们要让某台Linux主机充当路由和负载均衡角色的话,我们显然应该在该主机的nat表的prerouting链中对数据包做DNAT操作。

 

Kubernetes可以利用iptables来做针对service的路由和负载均衡,其核心逻辑是通过kubernetes/pkg/proxy/iptables/proxier.go中的函数syncProxyRules来实现的(这部分代码做的事情主要就是在service所在nodenat表的prerouting链中对数据包做DNAT操作)。

 

下面我们就来分析一下syncProxyRules中的主要逻辑:

Part1:

clip_image006

这里定义了kubernetes将创建的自定义链的名称。

Part2:

clip_image008

上述代码做了如下事情:

1、  分别在表filternat中创建名为“KUBE-SERVICES”的自定义链

2、  调用iptablesfilter表的output链、对nat表的outputprerouting链创建了如下三条规则:

//将经过filter表的output链的数据包重定向到自定义链KUBE-SERVICES

//这里-j的意思是jump

iptables -I OUTPUT -m comment –comment “kubernetes service portals” -j KUBE-SERVICES

 

//将经过nat表的prerouting链的数据包重定向到自定义链KUBE-SERVICES

//显然kubernetes这里要做DNAT

iptables –t nat -I PREROUTING -m comment –comment “kubernetes service portals” -j KUBE-SERVICES

 

//将经过nat表的output链的数据包重定向到自定义链KUBE-SERVICES

iptables –t nat –I OUTPUT -m comment –comment “kubernetes service portals” -j KUBE-SERVICES

Part3:

clip_image010

上述代码做了如下事情:

1、  在表nat中创建了名为“KUBE-POSTROUTING”的自定义链

2、  调用iptablesnat表的postrouting链创建了如下规则:

//将经过nat表的postrouting链的数据包重定向到自定义链KUBE-POSTROUTING

//显然kubernetes这里要做SNAT

iptables –t nat -I POSTROUTING -m comment –comment “kubernetes postrouting rules” -j KUBE-POSTROUTING

Part4:

clip_image012

上述代码做的事情是:kubernetes调用iptables-save命令解析当前nodeiptablesfilter表和nat表中已经存在的chainkubernetes会将这些chain存在两个map中(existingFilterChainsexistingNATChains),然后再创建四个protobuf中的buffer(分别是filterChainsfilterRulesnatChainsnatRules),后续kubernetes会往这四个buffer中写入大量iptables规则,最后再调用iptables-restore写回到当前nodeiptables中。

Part5:

clip_image014

clip_image016

上述代码做了如下事情:

1、  如果当前nodeiptablesfilter表和nat表中已经存在名为“KUBE-SERVICES”“KUBE-NODEPORTS”“KUBE-POSTROUTING”“KUBE-MARK-MASQ”的自定义链,那就原封不动将它们按照原来的形式(:<chain-name> <chain-policy> [<packet-counter>:<byte-counter>])写入到filterChainsnatChains中;如果没有,则以“:<chain-name> <chain-policy> [0:0]”的格式写入上述4chain(即将“:KUBE-SERVICES – [0:0]”、“:KUBE-NODEPORTS – [0:0]”、“:KUBE-POSTROUTING – [0:0]”和“:KUBE-MARK-MASQ – [0:0]”写入到filterChainsnatChains中,这相当于在filter表和nat表中创建了上述4个自定义链);

2、  nat表的自定义链“KUBE-POSTROUTING”写入如下规则:

-A KUBE-POSTROUTING -m comment –comment “kubernetes service traffic requiring SNAT” -m mark –mark 0x4000/0x4000 -j MASQUERADE

3、  nat表的自定义链“KUBE-MARK-MASQ”写入如下规则:

-A KUBE-MARK-MASQ -j MARK –set-xmark 0x4000/0x4000

这里23做的事情的实际含义是kubernetes会让所有kubernetes集群内部产生的数据包流经nat表的自定义链“KUBE-MARK-MASQ”,然后在这里kubernetes会对这些数据包打一个标记(0x4000/0x4000),接着在nat的自定义链“KUBE-POSTROUTING”中根据上述标记匹配所有的kubernetes集群内部的数据包,匹配的目的是kubernetes会对这些包做SNAT操作。

Part6:

clip_image018

clip_image020

clip_image022

clip_image024

clip_image026

clip_image028

clip_image030

clip_image032

clip_image034

clip_image036

clip_image038

上述代码主要做了如下事情:

1、  遍历所有服务,对每一个服务,在nat表中创建名为“KUBE-SVC-XXXXXXXXXXXXXXXX”的自定义链(这里的XXXXXXXXXXXXXXXX是一个16位字符串,kubernetes使用SHA256 算法对“服务名+协议名”生成哈希值,然后通过base32对该哈希值编码,最后取编码值的前16位,kubernetes通过这种方式保证每个服务对应的“KUBE-SVC-XXXXXXXXXXXXXXXX”都不一样)。然后对每个服务,根据服务是否有cluster ip、是否有external ip、是否启用了外部负载均衡服务在nat表的自定义链“KUBE-SERVICES”中加入类似如下这样的规则:

//所有流经自定义链KUBE-SERVICES的来自于服务“kongxl/test2:8778-tcp”的数据包都会

//跳转到自定义链KUBE-SVC-XAKTM6QUKQ53BZHS

-A KUBE-SERVICES -d 172.30.32.92/32 -p tcp -m comment –comment “kongxl/test2:8778-tcp cluster IP” -m tcp –dport 8778 -j KUBE-SVC-XAKTM6QUKQ53BZHS

 

2、  在遍历每一个服务的过程中,还会检查该服务是否启用了nodeports,如果启用了且该服务有对应的endpoints,则会在nat表的自定义链“KUBE-NODEPORTS”中加入如下两条规则:

//所有流经自定义链KUBE-NODEPORTS的来自于服务“ym/echo-app-nodeport”的数据包

//都会跳转到自定义链KUBE-MARK-MASQ中,即kubernetes会对来自上述服务的这些

//数据包打一个标记(0x4000/0x4000

-A KUBE-NODEPORTS -p tcp -m comment –comment “ym/echo-app-nodeport:” -m tcp –dport 30001 -j KUBE-MARK-MASQ

 

//所有流经自定义链KUBE-NODEPORTS的来自于服务“ym/echo-app-nodeport”的数据包

//都会跳转到自定义链KUBE-SVC-LQ6G5YLNLUHHZYH5

-A KUBE-NODEPORTS -p tcp -m comment –comment “ym/echo-app-nodeport:” -m tcp –dport 30001 -j KUBE-SVC-LQ6G5YLNLUHHZYH5

 

3、  在遍历每一个服务的过程中,还会检查该服务是否启用了nodeports,如果启用了但该服务没有对应的endpoints,则会在filter表的自定义链“KUBE-SERVICES”中加入如下规则:

//如果service没有配置endpoints,那么kubernetes这里会REJECT所有数据包,这意味着//没有endpointsservice是无法被访问到的

-A KUBE-SERVICES -d 172.30.32.92/32 -p tcp -m comment –comment “kongxl/test2:8080-tcp has no endpoints” -m tcp –dport 8080 -j REJECT

 

4、  在遍历每一个服务的过程中,对每一个服务,如果这个服务有对应的endpoints,那么在nat表中创建名为“KUBE-SEP-XXXXXXXXXXXXXXXX”的自定义链(这里的XXXXXXXXXXXXXXXX是一个16位字符串,kubernetes使用SHA256 算法对“服务名+协议名+端口”生成哈希值,然后通过base32对该哈希值编码,最后取编码值的前16位,kubernetes通过这种方式保证每个服务的endpoint对应的“KUBE-SEP-XXXXXXXXXXXXXXXX”都不一样)。然后对每个endpoint,如果该服务配置了session affinity,则在nat表的该service对应的自定义链“KUBE-SVC-XXXXXXXXXXXXXXXX”中加入类似如下这样的规则:

//所有流经自定义链KUBE-SVC-ECTPRXTXBM34L34Q的来自于服务

//”default/docker-registry:5000-tcp”的数据包都会跳转到自定义链 //KUBE-SEP-LPCU5ERTNL2YBWXG中,

//且会在一段时间内保持session affinity,保持时间为180

//这里kubernetes用“-m recent –rcheck –seconds 180 –reap”实现了会话保持

-A KUBE-SVC-ECTPRXTXBM34L34Q -m comment –comment “default/docker-registry:5000-tcp” -m recent –rcheck –seconds 180 –reap –name KUBE-SEP-LPCU5ERTNL2YBWXG –mask 255.255.255.255 –rsource -j KUBE-SEP-LPCU5ERTNL2YBWXG

 

5、  在遍历每一个服务的过程中,对每一个服务,如果这个服务有对应的endpoints,且没有配置session affinity,则在nat表的该service对应的自定义链“KUBE-SVC-XXXXXXXXXXXXXXXX”中加入类似如下这样的规则(如果该服务对应的endpoints大于等于2,则还会加入负载均衡规则):

//所有流经自定义链KUBE-SVC-VX5XTMYNLWGXYEL4的来自于服务“ym/echo-app”的数据//包既可能会跳转到自定义链KUBE-SEP-27OZWHQEIJ47W5ZW,也可能会跳转到自定义

//KUBE-SEP-AA6LE4U3XA6T2EZB这里kubernetes用“-m statistic –mode random //–probability 0.50000000000”实现了对该服务访问的负载均衡

-A KUBE-SVC-VX5XTMYNLWGXYEL4 -m comment –comment “ym/echo-app:” -m statistic –mode random –probability 0.50000000000 -j KUBE-SEP-27OZWHQEIJ47W5ZW

 

-A KUBE-SVC-VX5XTMYNLWGXYEL4 -m comment –comment “ym/echo-app:” -j KUBE-SEP-AA6LE4U3XA6T2EZB

 

6、  最后,在遍历每一个服务的过程中,对每一个服务的endpoints,在nat表的该endpoint对应的自定义链“KUBE-SEP-XXXXXXXXXXXXXXXX”中加入如下规则,实现到该服务最终目的地的DNAT

//服务“ym/echo-app”有两个endpoints,之前kubernetes已经对该服务做了负载均衡,

//所以这里一共会产生4条跳转规则

-A KUBE-SEP-27OZWHQEIJ47W5ZW -s 10.1.0.8/32 -m comment –comment “ym/echo-app:” -j KUBE-MARK-MASQ

-A KUBE-SEP-27OZWHQEIJ47W5ZW -p tcp -m comment –comment “ym/echo-app:” -m tcp -j DNAT –to-destination 10.1.0.8:8080

 

-A KUBE-SEP-AA6LE4U3XA6T2EZB -s 10.1.1.4/32 -m comment –comment “ym/echo-app:” -j KUBE-MARK-MASQ

-A KUBE-SEP-AA6LE4U3XA6T2EZB -p tcp -m comment –comment “ym/echo-app:” -m tcp -j DNAT –to-destination 10.1.1.4:8080

Part7:

clip_image040

clip_image042

上述代码做的事情是:

1、  删掉当前节点中已经不存在的服务所对应的“KUBE-SVC-XXXXXXXXXXXXXXXX”链和“KUBE-SEP- XXXXXXXXXXXXXXXX”链;

2、  nat表的自定义链“KUBE-SERVICES”中写入如下这样规则:

//将目的地址是本地的数据包跳转到自定义链KUBE-NODEPORTS

-A KUBE-SERVICES -m comment –comment “kubernetes service nodeports; NOTE: this must be the last rule in this chain” -m addrtype –dst-type LOCAL -j KUBE-NODEPORTS

 

Part8:

clip_image044

上述代码做的事情是:合并已经被写入了大量规则的四个protobuf中的buffer(分别是filterChainsfilterRulesnatChainsnatRules),然后调用iptables-restore写回到当前nodeiptables中。

总结

Kubernetes使用iptables的主要用法用一句话总结就是:

Kubernetes通过在目标nodeiptables中的nat表的PREROUTINGPOSTROUTING链中创建一系列的自定义链 (这些自定义链主要是“KUBE-SERVICES”链、“KUBE-POSTROUTING”链、每个服务所对应的“KUBE-SVC-XXXXXXXXXXXXXXXX”链和“KUBE-SEP-XXXXXXXXXXXXXXXX”链),然后通过这些自定义链对流经到该node的数据包做DNATSNAT操作以实现路由、负载均衡和地址转换。

 

上述总结可以用如下这张图来说明:

clip_image046


关于直方图统计信息的两个有趣的知识点

有朋友问了我如下这样一个问题,最后的解决过程挺有意思的,让我发现了直方图统计信息里我之前没有注意到的两个知识点,这里跟大家分享一下。

 

这个问题是这样:

数据库的版本是11.2.0.3

SQL> select * from v$version;

 

BANNER

——————————————————————————–

Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 – 64bit Production

PL/SQL Release 11.2.0.3.0 – Production

CORE     11.2.0.3.0       Production

TNS for Linux: Version 11.2.0.3.0 – Production

NLSRTL Version 11.2.0.3.0 – Production

 

创建一个测试表T1

SQL> create table t1 as select * from dba_users;

 

Table created

 

从如下查询结果中我们可以看到,表T1OBJECT_ID104192

SQL> select object_id from dba_objects where owner=’SCOTT’ and object_name=’T1′;

 

 OBJECT_ID

——————-

  104192

 

T1的列user_id所对应的INTCOL#2

SQL> select name,intcol# from sys.col$ where obj#=104192 and name=’USER_ID’;

 

NAME                              INTCOL#

——————————                ———-

USER_ID                                 2

 

从如下结果里可以看到,SYS.COL_USAGE$现在还没有列USER_ID的使用记录:

SQL> select obj#,intcol#,equality_preds from sys.col_usage$ where obj#=104192;

 

      OBJ#    INTCOL# EQUALITY_PREDS

———- ———- ————–

 

 

我们现在来使用一下列USER_ID

SQL> select count(*) from t1 where user_id=5;

 

  COUNT(*)

—————–

         1

 

使用完后,我们发现SYS.COL_USAGE$还是没有列USER_ID的使用记录:

SQL> select obj#,intcol#,equality_preds from sys.col_usage$ where obj#=104192;

 

      OBJ#    INTCOL# EQUALITY_PREDS

———- ———- ————–

 

 

这个是正常的,这里不是没有USER_ID的使用记录,是已经有了但只是还没有被持久化到SYS.COL_USAGE$中,这里需要我们手工执行一下dbms_stats.gather_table_stats,这样就能将USER_ID的使用记录flushSYS.COL_USAGE$中了,然后我们就能看到了:

SQL> exec dbms_stats.gather_table_stats(ownname=>’SCOTT’,tabname=>’T1′,estimate_percent=>100);

 

PL/SQL procedure successfully completed

 

SQL> select obj#,intcol#,equality_preds from sys.col_usage$ where obj#=104192;

 

      OBJ#    INTCOL# EQUALITY_PREDS

———- ———- ————–

    104192          2              1

 

但现在的问题是无论我们怎么执行dbms_stats.gather_table_stats,列user_id上的直方图统计信息就是没有(这也是那位朋友问的问题):

SQL> exec dbms_stats.gather_table_stats(ownname=>’SCOTT’,tabname=>’T1′,method_opt=>’for all columns size auto‘,estimate_percent=>100);

 

PL/SQL procedure successfully completed

 

SQL> select table_name,column_name,num_distinct,num_buckets,histogram from dba_tab_col_statistics where owner=’SCOTT’ and table_name=’T1′ and column_name=’USER_ID’;

 

TABLE_NAME COLUMN_NAM NUM_DISTINCT NUM_BUCKETS HISTOGRAM

———- ———- ———— ———– ———-

T1         USER_ID              39           1 NONE

 

SQL> exec dbms_stats.gather_table_stats(ownname=>’SCOTT’,tabname=>’T1′,method_opt=>’for columns size auto USER_ID‘,estimate_percent=>100);

 

PL/SQL procedure successfully completed

 

SQL> select table_name,column_name,num_distinct,num_buckets,histogram from dba_tab_col_statistics where owner=’SCOTT’ and table_name=’T1′ and column_name=’USER_ID’;

 

TABLE_NAME COLUMN_NAM NUM_DISTINCT NUM_BUCKETS HISTOGRAM

———- ———- ———— ———– ———-

T1         USER_ID              39           1 NONE

 

这里除非我们手工指定user_id列所用的bucket的数量:

SQL> exec dbms_stats.gather_table_stats(ownname=>’SCOTT’,tabname=>’T1′,method_opt=>’for columns size 39 USER_ID‘,estimate_percent=>100);

 

PL/SQL procedure successfully completed

 

SQL> select table_name,column_name,num_distinct,num_buckets,histogram from dba_tab_col_statistics where owner=’SCOTT’ and table_name=’T1′ and column_name=’USER_ID’;

 

TABLE_NAME COLUMN_NAM NUM_DISTINCT NUM_BUCKETS HISTOGRAM

———- ———- ———— ———– ——————–

T1         USER_ID              39          39 HEIGHT BALANCED

 

手工指定了直方图统计信息的bucket的数量为39后,明明列user_iddistinct值的数量也是39,为什么这里直方图的类型居然是HEIGHT BALANCED?按道理讲应该是FREQUENCY啊!

 

当看到上述测试结果的时候,我意识到一定是什么地方出了问题,因为上述现象的出现已经颠覆了我之前对直方图统计信息的如下两个认识:

1、我原先一直以为如果METHOD_OPT的值是默认的“FOR ALL COLUMNS SIZE AUTO”的话,那么只要SYS.COL_USAGE$中有目标列的使用记录,则Oracle在自动收集直方图统计信息的时候就会去收集该列的直方图统计信息;

2、在手工收集直方图统计信息的时候,如果我手工指定的bucket的数量等于目标列的distinct值的数量,且这个值是小于等于254的话,那么Oracle此时收集的直方图统计信息的类型应该是FREQUENCY

 

到底是什么地方出了问题?

 

我们来复习一下Oracle关于自动收集直方图统计信息的定义:

Oracle在“SIZE Clause in METHOD_OPT Parameter of DBMS_STATS Package (Doc ID 338926.1)”中明确指出,METHOD_OPT的值中的AUTO的含义为如下所示:

AUTO: Oracle determines the columns to collect histograms based on data distribution and the workload of the columns.

 

这里的“workload of the columns”指的应该就是目标列是否在SYS.COL_USAGE$中有使用记录。注意到Oracle这里还提到了另外一个条件——“based on data distribution”(这也是我之前没有注意到的条件),但这里的具体含义是什么?

based on data distribution”直译过来就是目标列数据的分布,说白了就是目标列的数据分布确实得是倾斜的,只有满足这个前提条件,再加上该目标列在SYS.COL_USAGE$中有使用记录,Oracle在自动收集直方图统计信息的时候才会对该列收集直方图统计信息

 

Oracle是怎么来判断某列的数据分布是否是倾斜的呢?

我研究了一下,发现Oracle采用了一种很简单的方法——就是判断目标列的distinct值的数量是否和目标表的数据量相同,如果相同,Oracle就认为该列的数据分布不是倾斜的,否则就是倾斜的。也就是说,如果目标列的distinct值的数量和目标表的数据量相同,即使该目标列在SYS.COL_USAGE$中有使用记录,Oracle在自动收集直方图统计信息的时候也不会对该列收集直方图统计信息。

 

搞清楚了上述知识点,那位朋友问的问题自然就有答案了——对于表T1的列user_id而言,其distinct值的数量和表T1的数据量相同,所以这里即使user_idSYS.COL_USAGE$中有使用记录,Oracle在自动收集直方图统计信息的时候也不会对user_id收集直方图统计信息:

SQL> select count(distinct user_id) from t1;

 

COUNT(DISTINCTUSER_ID)

———————-

             39

 

SQL> select count(*) from t1;

 

  COUNT(*)

———-

        39

 

现在我们来验证一下上述理论,往表T1中插入一条记录,使得user_iddistinct值的数量小于表T1的数据量,这样当我们再次对表T1收集统计信息的时候,user_id列的直方图统计信息应该就有了。

 

先把之前对user_id列手工指定bucket数量收集的直方图统计信息删掉:

SQL> exec dbms_stats.gather_table_stats(ownname=>’SCOTT’,tabname=>’T1′,method_opt=>’for columns size 1 USER_ID‘,estimate_percent=>100);

 

PL/SQL procedure successfully completed

 

SQL> select table_name,column_name,num_distinct,num_buckets,histogram from dba_tab_col_statistics where owner=’SCOTT’ and table_name=’T1′ and column_name=’USER_ID’;

 

TABLE_NAME COLUMN_NAM NUM_DISTINCT NUM_BUCKETS HISTOGRAM

———- ———- ———— ———– ——————–

T1         USER_ID              39           1 NONE

 

对表T1插入一条user_id列的值和现有值重复的记录:

SQL> insert into t1 select * from t1 where user_id=5;

 

1 row inserted

 

SQL> commit;

 

Commit complete

 

现在user_id列的distinct值的数量已经小于表T1的数据量了:

SQL> select count(distinct user_id) from t1;

 

COUNT(DISTINCTUSER_ID)

———————-

            39

 

SQL> select count(*) from t1;

 

  COUNT(*)

———-

        40

 

此时对表T1再次收集统计信息:

SQL> exec dbms_stats.gather_table_stats(ownname=>’SCOTT’,tabname=>’T1′,estimate_percent=>100);

 

PL/SQL procedure successfully completed

 

从如下查询结果里我们可以看到,现在user_id列上终于有了直方图统计信息,且其类型就是FREQUENCY,这就和我们以前的认知匹配上了,同时也验证了我们刚才的分析结论:

SQL> select table_name,column_name,num_distinct,num_buckets,histogram from dba_tab_col_statistics where owner=’SCOTT’ and table_name=’T1′ and column_name=’USER_ID’;

 

TABLE_NAME COLUMN_NAM NUM_DISTINCT NUM_BUCKETS HISTOGRAM

———- ———- ———— ———– ——————–

T1         USER_ID              39          39 FREQUENCY

 

再次删除user_id列上的直方图统计信息:

SQL> exec dbms_stats.gather_table_stats(ownname=>’SCOTT’,tabname=>’T1′,method_opt=>’for columns size 1 USER_ID‘,estimate_percent=>100);

 

PL/SQL procedure successfully completed

 

SQL> select table_name,column_name,num_distinct,num_buckets,histogram from dba_tab_col_statistics where owner=’SCOTT’ and table_name=’T1′ and column_name=’USER_ID’;

 

TABLE_NAME COLUMN_NAM NUM_DISTINCT NUM_BUCKETS HISTOGRAM

———- ———- ———— ———– ——————–

T1         USER_ID              39           1 NONE

 

我们再次以手工指定bucket数量的方式收集user_id列上的直方图统计信息:

SQL>  exec dbms_stats.gather_table_stats(ownname=>’SCOTT’,tabname=>’T1′,method_opt=>’for columns size 39 USER_ID‘,estimate_percent=>100);

 

PL/SQL procedure successfully completed

 

从如下查询结果我们可以看到,现在user_id列上的直方图统计信息的类型已经不是之前的HEIGHT BALANCED了,而是变成了FREQUENCY

SQL> select table_name,column_name,num_distinct,num_buckets,histogram from dba_tab_col_statistics where owner=’SCOTT’ and table_name=’T1′ and column_name=’USER_ID’;

 

TABLE_NAME COLUMN_NAM NUM_DISTINCT NUM_BUCKETS HISTOGRAM

———- ———- ———— ———– ——————–

T1         USER_ID              39          39 FREQUENCY

 

这说明我们之前的认识(在手工收集直方图统计信息的时候,如果我手工指定的bucket的数量等于目标列的distinct值的数量,且这个值是小于等于254的话,那么Oracle此时收集的直方图统计信息的类型应该是FREQUENCY)成立的前提条件是该列的数据分布是倾斜的。

 

总结一下,通过这篇文章,我们介绍了如下两个关于直方图统计信息的有趣知识点:

1、如果目标列的distinct值的数量和目标表的数据量相同,即使该目标列在SYS.COL_USAGE$中有使用记录,Oracle在自动收集直方图统计信息的时候也不会对该列收集直方图统计信息;

2、在手工收集直方图统计信息的时候,如果我手工指定的bucket的数量等于目标列的distinct值的数量,且这个值是小于等于254的话,那么Oracle此时收集的直方图统计信息的类型应该是FREQUENCY——这个结论成立的前提条件是该列的数据分布是倾斜的。