news 2026/8/10 9:03:05

数据库如何根据全表 NDV 估算子集的 NDV

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
数据库如何根据全表 NDV 估算子集的 NDV

以前我们讨论过 数据库如何根据样本的 NDV 来估计总体的 NDV,也就是以一个小集合的 NDV 去估算一个更大集合的 NDV,但有的时候会反过来,会要求用全表的 NDV 要去估算表中某个子集的 NDV,什么情况下会用到呢?比如在多表关联的时候,JOIN 条件的选择率为(假设是等值连接):
j o i n _ s e l e c t i v i t y = m i n ( l e f t _ s e l e c t i v i t y , r i g h t _ s e l e c t i v i t y ) join\_selectivity=min(left\_selectivity, right\_selectivity)join_selectivity=min(left_selectivity,right_selectivity)
换而言之,也就是:
j o i n _ n d v = m a x ( l e f t _ n d v , r i g h t _ n d v ) join\_ndv=max(left\_ndv, right\_ndv)join_ndv=max(left_ndv,right_ndv)
但不管是 JOIN 的左支还是右支,是可能有本地谓词(local predicate)的,连接时用的 NDV 就不再是全表的 NDV,而是经过本地谓词过滤后的子集的 NDV。
举个例子:

SELECTe.employee_id,e.first_name||' '||e.last_nameASfull_name,e.salary,d.department_name,d.location_idFROMemployees eJOINdepartments dONe.department_id=d.department_idWHEREd.location_id=1700ANDe.salary=12008;

在计算 e.department_id = d.department_id 的 NDV 的时候,就不能使用 employees 表和 departments 表的全表 NDV,因为这里是经过本地谓词(WHERE 条件中)过滤后的部分数据,那我们如何来估算它呢?

这个问题可以简单地抽象成一个概率问题:N 个 D 种颜色的球,不放回的抽取 n 个球,里面有多少种(d)颜色?

也就是:N 表示全量数据的个数,D 表示全量数据的 NDV,n 表示部分数据的个数,d 表示部分数据的 NDV,我们要用 N、D、n 来估算 d。

在假设数据分布比较均衡的前提下,可以按如下方法推导:
对于某一种颜色的球来说,有两种可能,一种是落在抽到的子集中,一种是落在抽到的子集外,落在子集外的概率是:1 − n N 1-\frac{n}{N}1Nn
平均而言,每一种颜色的球有N D \frac{N}{D}DN个,
所以该种颜色的球全部落在抽到的子集外的概率是:
( 1 − n N ) N D (1-\frac{n}{N})^{\frac{N}{D}}(1Nn)DN
于是,该种颜色的球至少有一个落在抽到的子集中的概率就是:
1 − ( 1 − n N ) N D 1-(1-\frac{n}{N})^{\frac{N}{D}}1(1Nn)DN
一共有 D 种颜色的球,落在抽到子集中的颜色种数的数学期望就是:
d = D × ( 1 − ( 1 − n N ) N D ) d=D\times(1-(1-\frac{n}{N})^{\frac{N}{D}})d=D×(1(1Nn)DN)

看个例子:
100 个 5 种颜色的球,每种颜色 20 个,不放回的抽取 10 个球,里面有多少种颜色?按照上述公式可得:d = 5 × ( 1 − ( 1 − 0.1 ) 20 ) ≈ 4.4 d=5\times(1-(1-0.1)^{20})\approx 4.4d=5×(1(10.1)20)4.4
拿 Excel 做个实验,随机 20 次,平均 4.65,比较接近。如果用D × n N = 0.5 D\times\frac{n}{N}=0.5D×Nn=0.5来估,就会差很多。

实际使用上,开源的 Apache Impala 就使用了这种方式:
https://github.com/apache/impala/blob/master/fe/src/main/java/org/apache/impala/planner/AggregationNode.java

doubleperInstanceInputCard=Math.ceil((double)inputCardinality/totalInstances);doubleglobalNdvInDouble=(double)globalNdv;doubleprobValExist=1.0-Math.pow((globalNdvInDouble-1.0)/globalNdvInDouble,perInstanceInputCard);doubleperInstanceNdv=Math.ceil(probValExist*globalNdvInDouble);

再来看之前的 SQL:

SELECTe.employee_id,e.first_name||' '||e.last_nameASfull_name,e.salary,d.department_name,d.location_idFROMemployees eJOINdepartments dONe.department_id=d.department_idWHEREd.location_id=1700ANDe.salary=12008;|=================================================================|||ID|OPERATOR|NAME|EST.ROWS|EST.TIME(us)|||----------------------------------------------------------------- |||0|HASHJOIN||3|73||||1|├─TABLERANGE SCAN|D(DEPT_LOCATION_IX)|21|59||||2|└─TABLEFULLSCAN|E|2|9|||=================================================================|D:||table_rows:27||physical_range_rows:21||logical_range_rows:21||index_back_rows:21||output_rows:21|E:||table_rows:107||physical_range_rows:107||logical_range_rows:107||output_rows:2

employees 表有 107 条记录,department_id 的 NDV=11,应用本地谓词 e.salary = 12008 剩 2 条,departments 表有 27 条记录,department_id 的 NDV=26,应用本地谓词 d.location_id = 1700 后剩 21 条,问优化器如何估算 e.department_id = d.department_id 连接后的 NDV、selectivity 和行数?

套用上述公式,也就是:
n e w _ l e f t _ n d v = 11 × ( 1 − ( 1 − 2 107 ) 107 11 ) new\_left\_ndv=11\times(1-(1-\frac{2}{107})^{\frac{107}{11}})new_left_ndv=11×(1(11072)11107)
= 1.844485 =1.844485=1.844485
n e w _ r i g h t _ n d v = 26 × ( 1 − ( 1 − 21 27 ) 27 26 ) new\_right\_ndv=26\times(1-(1-\frac{21}{27})^{\frac{27}{26}})new_right_ndv=26×(1(12721)2627)
= 20.546978 =20.546978=20.546978
o u t _ r o w s = l e f t _ r o w s × r i g h t _ r o w s × 1 m a x ( n e w _ l e f t _ n d v , n e w _ r i g h t _ n d v ) out\_rows=left\_rows\times right\_rows\times \frac{1}{max(new\_left\_ndv, new\_right\_ndv)}out_rows=left_rows×right_rows×max(new_left_ndv,new_right_ndv)1
= 2 × 21 × 1 20.546978 = 2.04409622 =2\times 21\times\frac{1}{20.546978}=2.04409622=2×21×20.5469781=2.04409622

从 optimizer trace 中也能看到:

E :rows:2.000000baserows:107.000000statistype: OPTIMIZER version:0used partitions:[502555]normal stat partitions:[]histogram stat partitions:[]DEPARTMENT_ID : NDV:1.844485BASE NDV:11.000000D :rows:21.000000baserows:27.000000statistype: OPTIMIZER version:0used partitions:[502534]normal stat partitions:[]histogram stat partitions:[]DEPARTMENT_ID : NDV:20.546978BASE NDV:26.000000

分毫不差。

可以推算,当 NDV << rows 的时候,这个方法会更精确。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/10 9:02:38

揭秘上海网站建设yes404:如何避开技术陷阱,打造真正转化率高且用户体验极佳的网站解决方案

在这个数字化浪潮席卷全球的今天,如果你还在问“我们还需要一个网站吗?”,那可能真的需要好好反思一下了。对于绝大多数在上海乃至全国扎根的企业来说,网站早已不仅仅是一个挂在服务器上的几页HTML代码,它是你的24小时不打烊的销售顾问,是你品牌形象的数字名片,更是你获…

作者头像 李华
网站建设 2026/8/10 8:59:03

VMware去虚拟化实战:打造隐形Win10虚拟机绕过软件检测

如果你在虚拟机里运行Windows 10&#xff0c;只是为了测试软件、学习系统或者搭建一个干净的开发环境&#xff0c;却频繁遇到软件报错、游戏闪退&#xff0c;甚至被某些应用直接识别为“虚拟机”而拒绝运行&#xff0c;那么这篇文章就是为你准备的。这背后的问题&#xff0c;通…

作者头像 李华
网站建设 2026/8/10 8:55:54

Unity DoTween回调函数全解析:从原理到实战避坑指南

1. 项目概述&#xff1a;为什么DoTween的回调函数值得你花时间研究&#xff1f;如果你在Unity里做过动画&#xff0c;大概率用过DoTween。它确实让移动、旋转、缩放这些基础动画变得无比简单&#xff0c;一行代码就能搞定。但很多开发者&#xff0c;尤其是刚上手的朋友&#xf…

作者头像 李华
网站建设 2026/8/10 8:55:29

从零配置OGRE 3D引擎:C++图形开发入门与旋转立方体实战

1. 项目概述&#xff1a;为什么选择OGRE作为你的3D图形起点&#xff1f; 如果你正在用C做3D图形开发&#xff0c;无论是想写个小游戏、做个数据可视化工具&#xff0c;还是单纯想研究一下渲染引擎的底层&#xff0c;OGRE这个名字你大概率绕不开。我第一次接触OGRE还是十多年前&…

作者头像 李华
网站建设 2026/8/10 8:55:09

DLSS Swapper终极指南:一键智能升级游戏画质与性能的完整教程

DLSS Swapper终极指南&#xff1a;一键智能升级游戏画质与性能的完整教程 【免费下载链接】dlss-swapper 项目地址: https://gitcode.com/GitHub_Trending/dl/dlss-swapper 还在为游戏画面模糊、帧率不稳而烦恼吗&#xff1f;DLSS Swapper这款革命性工具能够让你轻松管…

作者头像 李华
网站建设 2026/8/10 8:54:28

小学生学C++编程语法知识(什么是多态(Polymorphism))

C面向对象核心&#xff1a;什么是多态&#xff08;Polymorphism&#xff09;——同一个指令&#xff0c;不同对象表现不同对于小学生&#xff0c;可以先记一句话&#xff1a;多态就是&#xff1a;同一个动作&#xff0c;让不同的对象表现出不同的结果。例如&#xff1a;我们说&…

作者头像 李华