TiDB 无统计信息时执行计划如何生成

作者: weiyinghua 原文来源: https://tidb.net/blog/4c49ac0d

一、Pseudo 统计信息总体生成规则

TiDB 在表无统计信息时,不会进行动态采样,而是用静态的、预设规则以及经验假设来生成计划。用函数 PseudoTable 创建一个伪统计表对象,通过默认 1万行,并填充列和索引 pseudo 统计信息,提供一个粗略的估算依据。

二、 使用 pseudo 统计信息原因

  1. 表或索引刚创建还没收集完统计信息,或数据刚导入未收集统计信息;
  2. 修改过的行数 / 表总行数比值超过 pseudo-estimate-ratio 默认值 0.8 时会认为统计信息过期而用 pseudo 统计信息;
  3. 在 TiDB Server 启动时,由于统计信息加载未完成,可能会使用 pseudo 统计信息。

三、Pseudo 统计信息生成过程

PseudoTable 函数创建一个伪表统计信息对象,用于无统计信息情况下生成统计信息对象。

初始化 pseudo 统计信息

  • 创建一个 HistColl 对象,设置其属性,如行数 RealtimeCount 、物理ID PhysicalID 、是否允许触发加载 CanNotTriggerLoad 等。
  • 初始化列和索引的映射表 columns indices

创建统计信息表对象

  • 创建一个 Table 对象,并将其 HistColl 属性设置为前面创建的 HistColl 对象。
  • 初始化列和索引的存在映射表 ColAndIdxExistenceMap

生成列信息

  • 遍历表的列信息 tblInfo.Columns

  • 对于状态为 StatePublic 且不是隐藏列的列:

    • 在存在映射表中插入列信息。
    • 如果 allowFillHistMeta true ,则为该列创建一个 Column 对象,并填充直方图元数据。

生成索引信息

  • 遍历表的索引信息 tblInfo.Indices

  • 对于状态为 StatePublic 的索引:

    • 在存在映射表中插入索引信息。
    • 如果 allowFillHistMeta true ,则为该索引创建一个 Index 对象,并填充直方图元数据。

返回统计信息表对象

最后返回创建好的 Table 对象。

源码地址

https://github.com/pingcap/tidb/blob/426ce3e57069afbd8f061d7ae39c79d3f9e2ff5d/pkg/statistics/table.go#L1004

四、Pseudo 估算规则示例

示例表结构如下,并删除统计信息:

CREATE TABLE t1 (id bigint not null auto_random primary key,k char(64),v varchar(255),update_time datetime,key idx_k_update_time(k, update_time),key idx_update_time(update_time)
);
drop stats t1;

索引等值查询

普通索引等值查询,基于经验的假设,固定估算为10行,源码中常量 pseudoEqualRate 控制:

mysql> explain select count(1) from t1 where k='tom';
+-----------------------------+---------+-----------+---------------------------------------------------+-----------------------------------------------------+
| id                          | estRows | task      | access object                                     | operator info                                       |
+-----------------------------+---------+-----------+---------------------------------------------------+-----------------------------------------------------+
| StreamAgg_17                | 1.00    | root      |                                                   | funcs:count(Column#10)->Column#5                    |
| └─IndexReader_18            | 1.00    | root      |                                                   | index:StreamAgg_9                                   |
|   └─StreamAgg_9             | 1.00    | cop[tikv] |                                                   | funcs:count(1)->Column#10                           |
|     └─IndexRangeScan_16     | 10.00   | cop[tikv] | table:t1, index:idx_k_update_time(k, update_time) | range:["tom","tom"], keep order:false, stats:pseudo |
+-----------------------------+---------+-----------+---------------------------------------------------+-----------------------------------------------------+

如果是组合索引,第一个字段固定估算为全表的 1/1000 ,第二个字段固定估算为 1/100:

mysql> explain select count(1) from t1 where k='tom' and update_time='2024-11-25';
+---------------------------+---------+-----------+---------------------------------------------------+---------------------------------------------------------------------------------------------+
| id                        | estRows | task      | access object                                     | operator info                                                                               |
+---------------------------+---------+-----------+---------------------------------------------------+---------------------------------------------------------------------------------------------+
| StreamAgg_10              | 1.00    | root      |                                                   | funcs:count(1)->Column#5                                                                    |
| └─IndexReader_15          | 0.10    | root      |                                                   | index:IndexRangeScan_14                                                                     |
|   └─IndexRangeScan_14     | 0.10    | cop[tikv] | table:t1, index:idx_k_update_time(k, update_time) | range:["tom" 2024-11-25 00:00:00,"tom" 2024-11-25 00:00:00], keep order:false, stats:pseudo |
+---------------------------+---------+-----------+---------------------------------------------------+---------------------------------------------------------------------------------------------+

索引大于或小于

对于大于或小于查询,固定估算为1万行的三分之一,由源码中常量 pseudoLessRate 控制:

mysql> explain select count(1) from t1 where update_time < '2024-11-25';
+-----------------------------+---------+-----------+----------------------------------------------+------------------------------------------------------------------+
| id                          | estRows | task      | access object                                | operator info                                                    |
+-----------------------------+---------+-----------+----------------------------------------------+------------------------------------------------------------------+
| HashAgg_12                  | 1.00    | root      |                                              | funcs:count(Column#6)->Column#5                                  |
| └─IndexReader_13            | 1.00    | root      |                                              | index:HashAgg_6                                                  |
|   └─HashAgg_6               | 1.00    | cop[tikv] |                                              | funcs:count(1)->Column#6                                         |
|     └─IndexRangeScan_11     | 3323.33 | cop[tikv] | table:t1, index:idx_update_time(update_time) | range:[-inf,2024-11-25 00:00:00), keep order:false, stats:pseudo |
+-----------------------------+---------+-----------+----------------------------------------------+------------------------------------------------------------------+

如果是组合索引,第一个字段估算为全表的 1/100,第二个字段再估算1/3,即:10000 * 1/100 * 1/3:

mysql> explain select count(1) from t1 where k='tom' and update_time>'2024-11-25';
+-----------------------------+---------+-----------+---------------------------------------------------+------------------------------------------------------------------------------+
| id                          | estRows | task      | access object                                     | operator info                                                                |
+-----------------------------+---------+-----------+---------------------------------------------------+------------------------------------------------------------------------------+
| StreamAgg_17                | 1.00    | root      |                                                   | funcs:count(Column#7)->Column#5                                              |
| └─IndexReader_18            | 1.00    | root      |                                                   | index:StreamAgg_9                                                            |
|   └─StreamAgg_9             | 1.00    | cop[tikv] |                                                   | funcs:count(1)->Column#7                                                     |
|     └─IndexRangeScan_16     | 33.33   | cop[tikv] | table:t1, index:idx_k_update_time(k, update_time) | range:("tom" 2024-11-25 00:00:00,"tom" +inf], keep order:false, stats:pseudo |
+-----------------------------+---------+-----------+---------------------------------------------------+------------------------------------------------------------------------------+

索引范围查询

对于 between 写法,固定估算为1万行的 1/40,由源码中常量 pseudoBetweenRate 控制:

mysql> explain select count(1) from t1 where update_time BETWEEN '2024-11-25' and '2024-11-26';
+-----------------------------+---------+-----------+----------------------------------------------+---------------------------------------------------------------------------------+
| id                          | estRows | task      | access object                                | operator info                                                                   |
+-----------------------------+---------+-----------+----------------------------------------------+---------------------------------------------------------------------------------+
| StreamAgg_17                | 1.00    | root      |                                              | funcs:count(Column#7)->Column#5                                                 |
| └─IndexReader_18            | 1.00    | root      |                                              | index:StreamAgg_9                                                               |
|   └─StreamAgg_9             | 1.00    | cop[tikv] |                                              | funcs:count(1)->Column#7                                                        |
|     └─IndexRangeScan_16     | 250.00  | cop[tikv] | table:t1, index:idx_update_time(update_time) | range:[2024-11-25 00:00:00,2024-11-26 00:00:00], keep order:false, stats:pseudo |
+-----------------------------+---------+-----------+----------------------------------------------+---------------------------------------------------------------------------------+

普通列

对于非索引的普通列,固定估算全表扫描1万行,并预估过滤10行:

mysql> explain select count(1) from t1 where v = 'happy';
+------------------------------+----------+-----------+---------------+---------------------------------+
| id                           | estRows  | task      | access object | operator info                   |
+------------------------------+----------+-----------+---------------+---------------------------------+
| StreamAgg_20                 | 1.00     | root      |               | funcs:count(Column#7)->Column#5 |
| └─TableReader_21             | 1.00     | root      |               | data:StreamAgg_9                |
|   └─StreamAgg_9              | 1.00     | cop[tikv] |               | funcs:count(1)->Column#7        |
|     └─Selection_19           | 10.00    | cop[tikv] |               | eq(test.t1.v, "happy")          |
|       └─TableFullScan_18     | 10000.00 | cop[tikv] | table:t1      | keep order:false, stats:pseudo  |
+------------------------------+----------+-----------+---------------+---------------------------------+

唯一键

对于唯一键,固定返回1行:

mysql> explain select count(1) from t1 where id = 100;
+--------------------+---------+------+---------------+--------------------------+
| id                 | estRows | task | access object | operator info            |
+--------------------+---------+------+---------------+--------------------------+
| StreamAgg_9        | 1.00    | root |               | funcs:count(1)->Column#5 |
| └─Point_Get_11     | 1.00    | root | table:t1      | handle:100               |
+--------------------+---------+------+---------------+--------------------------+

其它情形

对于索引第一个字段 k 估算10行,对于索引第二个字符型字段 v 传入数字进行范围查找,在执行计划中出现了 cast(test.t1.v, double BINARY) 表示有隐式类型转换,不属于任何一种预设规则,在10行基础上乘以 0.8 估算得到8行,源码中常量 selectionFactor 控制:

mysql> explain select count(1) from t1 where k='tom' and v >10;
+----------------------------------+---------+-----------+---------------------------------------------------+-----------------------------------------------------+
| id                               | estRows | task      | access object                                     | operator info                                       |
+----------------------------------+---------+-----------+---------------------------------------------------+-----------------------------------------------------+
| StreamAgg_10                     | 1.00    | root      |                                                   | funcs:count(1)->Column#5                            |
| └─IndexLookUp_37                 | 8.00    | root      |                                                   |                                                     |
|   ├─IndexRangeScan_34(Build)     | 10.00   | cop[tikv] | table:t1, index:idx_k_update_time(k, update_time) | range:["tom","tom"], keep order:false, stats:pseudo |
|   └─Selection_36(Probe)          | 8.00    | cop[tikv] |                                                   | gt(cast(test.t1.v, double BINARY), 10)              |
|     └─TableRowIDScan_35          | 10.00   | cop[tikv] | table:t1                                          | keep order:false, stats:pseudo                      |
+----------------------------------+---------+-----------+---------------------------------------------------+-----------------------------------------------------+

源码地址

https://github.com/pingcap/tidb/blob/426ce3e57069afbd8f061d7ae39c79d3f9e2ff5d/pkg/planner/cardinality/pseudo.go

五、总结

与 MySQL 8.0 类似,TiDB 7.1 在表缺乏统计信息时,并不会像 Oracle 那样动态采样生成执行计划。总的来说,无统计信息时 TiDB 生成的执行计划基于假设数据规模和经验规则,可能存在较大的误差。TiDB 8.2 将引入 统计信息并发加载功能 ,加载统计信息效率将大幅度提升,有效减少使用 Pseudo 统计信息的可能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/482424.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

服务器密码错误被锁定怎么解决?

当服务器密码错误多次导致账号被锁定时&#xff0c;解决方法需要根据服务器的操作系统&#xff08;如 Linux 或 Windows &#xff09;和具体服务器环境来处理。以下是常见的解决办法&#xff1a; 一、Linux 服务器被锁定的解决方法 1. 使用其他用户账号登录 如果有其他未被…

认识redis 及 Ubuntu安装redis

文章目录 一. redis概念二. redis应用场景二. redis的特性四. 使用Ubuntu安装redis 一. redis概念 redis 是在内存中存储数据的中间件, 用在分布式系统 redis是客户端服务器结构的程序, 客户端服务器之间通过网络来通信 二. redis应用场景 redis可用作数据库 类似MySQL, 但…

LabVIEW内燃机气道试验台测控系统

基于LabVIEW软件开发的内燃机气道试验台测控系统主要应用于内燃机气道的性能测试和数据分析&#xff0c;通过高精度的测控技术&#xff0c;有效提升内燃机的测试精度和数据处理能力。 项目背景 随着内燃机技术的发展&#xff0c;对其气道性能的精准测量需求日益增加。该系统通…

Rust vs Java:后端开发应该选哪个?

后端技术的发展迅速。根据JetBrains 2024年开发者调查,尽管Java仍然占据约34.5%的市场份额,但Rust在高性能应用中的应用逐渐增多。过去四年中,Rust在企业中的采用增长了240%(根据Stack Overflow 2024开发者调查)。随着组织更加注重效率和可扩展性,选择Rust还是Java已成为…

触觉智能亮相OpenHarmony人才生态大会2024

11月27日&#xff0c;OpenHarmony人才生态大会2024在武汉隆重举行。本次大会汇聚了政府领导、学术大咖、操作系统技术专家、高校及企业代表&#xff0c;围绕新时代背景下的操作系统人才培养进行了深入探讨&#xff0c;分享高校、企业在产学研融合方面的先进经验&#xff0c;全面…

springboot366高校物品捐赠管理系统(论文+源码)_kaic

毕 业 设 计&#xff08;论 文&#xff09; 高校物品捐赠管理系统设计与实现 摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff…

深入理解AIGC背后的核心算法:GAN、Transformer与Diffusion Models

深入理解AIGC背后的核心算法&#xff1a;GAN、Transformer与Diffusion Models 前言 随着人工智能技术的发展&#xff0c;AIGC&#xff08;AI Generated Content&#xff0c;人工智能生成内容&#xff09;已经不再是科幻电影中的幻想&#xff0c;而成为了现实生活中的一种新兴力…

企业网站面临的爬虫攻击及安全防护策略

在当今数字化时代&#xff0c;企业网站不仅是展示企业形象的窗口&#xff0c;更是进行商业活动的重要平台。然而&#xff0c;企业网站在日常运营中面临着多种类型的爬虫攻击&#xff0c;这些攻击不仅会对网站的正常访问造成影响&#xff0c;还可能窃取敏感数据&#xff0c;给企…

STM32的CAN波特率计算

公式&#xff1a; CAN波特率 APB总线频率 / &#xff08;BRP分频器 1&#xff09;/ (SWJ BS1 BS2) SWJ一般为1。 例如STM32F407的&#xff0c;CAN1和CAN2都在在APB1下&#xff0c;频率是42000000 如果想配置成1M波特率&#xff0c;则计算公式为&#xff1a;

《操作系统 - 清华大学》6 -3:局部页面置换算法:最近最久未使用算法 (LRU, Least Recently Used)

文章目录 1. 最近最久未使用算法的工作原理2. 最近最久未使用算法示例3.LRU算法实现3.1 LRU的页面链表实现3.2 LRU的活动页面栈实现3.3 链表实现 VS 堆栈实现 1. 最近最久未使用算法的工作原理 最近最久未使用页面置换算法&#xff0c;简称 LRU&#xff0c; 算法思路&#xff…

数据集-目标检测系列- 海边漫步锻炼人检测数据集 person >> DataBall

数据集-目标检测系列- 海边漫步锻炼人检测数据集 person >> DataBall DataBall 助力快速掌握数据集的信息和使用方式&#xff0c;会员享有 百种数据集&#xff0c;持续增加中。 需要更多数据资源和技术解决方案&#xff0c;知识星球&#xff1a; “DataBall - X 数据球…

【赵渝强老师】PostgreSQL的段、区和块

PostgreSQL的逻辑存储结构主要是指数据库集群、数据库、表空间、段、区、块等&#xff1b;同时PostgreSQL的逻辑存储结构也包括数据库中的各种数据库对象&#xff0c;如&#xff1a;表、索引、视图等等。所有数据库对象都有各自的对象标识符oid&#xff08;object identifiers&…

【YOLO系列复现】二、基于YOLOv6的目标检测:YOLOv6训练自己的数据集(史诗级详细教程)

官方模型&#xff1a;YOLOv6/README_cn.md at main meituan/YOLOv6 目录 1、模型和环境准备 1.1 模型下载 1.2 依赖环境安装 1.3 权重文件下载 1.4 环境测试 2、配置文件和数据集准备 2.1 准备数据集 2.2 配置文件准备 2.3 BUG修改 3、模型训练 3.1 模型训练 3.2 …

Flink常见面试题

1、Flink 的四大特征&#xff08;基石&#xff09; 2、Flink 中都有哪些 Source&#xff0c;哪些 Sink&#xff0c;哪些算子&#xff08;方法&#xff09; 预定义Source 基于本地集合的source&#xff08;Collection-based-source&#xff09; 基于文件的source&#xff08;…

【C语言】扫雷游戏(一)

我们先设计一个简单的9*9棋盘并有10个雷的扫雷游戏。 1&#xff0c;可以用数组存放&#xff0c;如果有雷就用1表示&#xff0c;没雷就用0表示。 2&#xff0c;排查(2,5)这个坐标时&#xff0c;我们访问周围的⼀圈8个位置黄色统计周围雷的个数是1。排查(8,6)这个坐标时&#xf…

【博主推荐】C#中winfrom开发常用技术点收集

文章目录 前言1.打开文件夹并选中文件2.窗体之间传参3.异步调用&#xff1a;让数据处理不影响页面操作4.创建一个多文档界面(MDI) 应用程序5.在WinForms中使用数据绑定6.在WinForms中后台使用控件的事件处理7.在WinForms中窗体跳转的几种方式8.后台处理方法中&#xff0c;调用窗…

Matlab 绘制雷达图像完全案例和官方教程(亲测)

首先上官方教程链接 polarplothttps://ww2.mathworks.cn/help/matlab/ref/polarplot.html 上实例 % 定义角度向量和径向向量 theta linspace(0, 2*pi, 5); r1 [1, 2, 1.5, 2.5, 1]; r2 [2, 1, 2.5, 1.5, 2];% 绘制两个雷达图 polarplot(theta, r1, r-, LineWidth, 2); hold …

乌班图单机(不访问外网)部署docker和服务的方法

面向对象:Ubuntu不能访问外网的机子,部署mysql、redis、jdk8、minio 过程: 1、安装docker(照着图去这里找对应的下载下来https://download.docker.com/linux/static/stable/),将7个docker官网下载的文件下载下来后,传上去服务器随便一个文件夹或者常用的opt或者/usr/lo…

响应式编程一、Reactor核心

目录 一、前置知识1、Lambda表达式2、函数式接口 Function3、StreamAPI4、Reactive-Stream1&#xff09;几个实际的问题2&#xff09;Reactive-Stream是什么&#xff1f;3&#xff09;核心接口4&#xff09;处理器 Processor5&#xff09;总结 二、Reactor核心1、Reactor1&…

Docker for Everyone Plus——No Enough Privilege

直接告诉我们flag在/flag中&#xff0c;访问第一小题&#xff1a; sudo -l查看允许提权执行的命令&#xff1a; 发现有image load命令 题目指明了有rz命令&#xff0c;可以用ZMODEM接收文件&#xff0c;看到一些write up说可以用XShell、MobaXterm、Tabby Terminal等软件连接上…