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

news2025/1/9 17:16:49

作者: 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.coloradmin.cn/o/2251245.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

org.apache.commons.lang3包下的StringUtils工具类的使用

前言 相信平时在写项目的时候&#xff0c;一定使用到StringUtils.isEmpty()&#xff1b;StringUtils.isBlank();但是你真的了解他们吗&#xff1f; 也许你两个都不知道&#xff0c;也许你除了isEmpty/isNotEmpty/isNotBlank/isBlank外&#xff0c;并不知道还有isAnyEmpty/isNon…

【工具推荐】dnsx——一个快速、多用途的 DNS 查询工具

basic/基本使用方式 echo baidu.com | dnsx -recon # 查询域名所有记录echo baidu.com | dnsx -a -resp # 查询域名的a记录echo baidu.com | dnsx -txt -resp # 查询域名的TXT记录echo ip | dnsx -ptr -resp # ip反查域名 A记录查询 TXT记录查询 ip反查域名 help/帮助信息 输…

Hive高可用配置

在hive的商用上没有集群一说&#xff0c;而且它本身也不是数据库&#xff0c;只是hadoop的数据sql化工具&#xff0c;但是hive可以配置高可用&#xff0c;通常业内对元数据服务会开5个&#xff0c;而HS2服务开3个&#xff0c;来保证hive服务的高可用 配置方式也很简单&#xf…

使用数学方法实现K-Nearest Neighbors(KNN)算法

目录 ​编辑 引言 KNN算法的数学基础 1. 距离度量 欧氏距离 曼哈顿距离 2. 寻找最近邻 3. 决策规则 分类 回归 4. 权重 KNN算法的实现步骤 1. 参数选择 2. 实现 导入必要的库 加载数据集 划分训练集和测试集 创建KNN模型 训练模型 预测测试集 计算准确率 …

提升用户体验、创新产品与高效运营,企业发展三驾马车

​在当今竞争激烈的市场环境中&#xff0c;企业要想脱颖而出并持续发展&#xff0c;需同时在提升用户体验、推动产品创新以及实现内部高效运营方面下功夫。 提升用户体验至关重要。它能提高用户满意度和忠诚度&#xff0c;增加用户口碑与推荐&#xff0c;提升企业品牌形象。可通…

在 Mac(ARM 架构)上安装 JDK 8 环境

文章目录 步骤 1&#xff1a;检查系统版本步骤 2&#xff1a;下载支持 ARM 的 JDK 8步骤 3&#xff1a;安装 JDK步骤 4&#xff1a;配置环境变量步骤 5&#xff1a;验证安装步骤 6&#xff1a;注意事项步骤7&#xff1a;查看Java的安装路径 在 Mac&#xff08;ARM 架构&#xf…

【Redis】—0.1、Ubuntu20.04源码编译部署redis6.2.7

1、Redis下载 创建redis的目录&#xff1a;mkdir -p /data/db/redis 下载redis&#xff1a;https://redis.io/download/ 2、上传文件到目录后解压 tar xvf redis-6.2.7.tar.gz 3、安装redis的依赖软件更新gcc&#xff0c;装一系列软件包&#xff0c;gcc&#xff0c;g和make。 s…

redis都有哪些用法

1. 缓存&#xff08;Caching&#xff09;&#xff1a; • Redis常被用作缓存层&#xff0c;存储那些频繁访问但更新不频繁的数据&#xff0c;以减少数据库的访问压力&#xff0c;提高数据读取速度。 • LRU&#xff08;Least Recently Used&#xff09;淘汰策略&#xff1a;Red…

npm install -g@vue/cli报错解决:npm error code ENOENT npm error syscall open

这里写目录标题 报错信息1解决方案 报错信息2解决方案 报错信息1 使用npm install -gvue/cli时&#xff0c;发生报错&#xff0c;报错图片如下&#xff1a; 根据报错信息可以知道&#xff0c;缺少package.json文件。 解决方案 缺什么补什么&#xff0c;这里我们使用命令npm…

爬取boss直聘上海市人工智能招聘信息+LDA主题建模

爬取boss直聘上海市人工智能招聘信息 import time import tqdm import random import requests import json import pandas as pd import os from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriv…

【STM32+HAL】FreeRTOS学习小札

一、RTOS程序任务结构 如上图所示&#xff0c;在实时操作系统中&#xff0c;开发人员可以把要实现的功能划分为多个任务&#xff0c;每个任务负责实现其中的一部分&#xff0c;每个任务都是一个很简单的程序&#xff0c;通常是一个死循环。 二、多任务系统基本概念 1、FreeRTO…

路径规划之启发式算法之二:遗传算法(Genetic Algorithm)

遗传算法&#xff08;Genetic Algorithm, GA&#xff09;是一种基于自然选择和遗传学原理的优化搜索算法&#xff0c;它通过模拟自然界的进化过程来寻找最优解。 一、基本原理 遗传算法的基本原理是模拟自然选择、遗传和突变等生物进化过程。它通过将问题的求解过程转换成类似…

【ubuntu24.04】GTX4700 配置安装cuda

筛选显卡驱动显卡驱动 NVIDIA-Linux-x86_64-550.135.run 而后重启:最新的是12.6 用于ubuntu24.04 ,但是我的4700的显卡驱动要求12.4 cuda

Springboot的文件上传与下载

Springboot的文件上传与下载 文章说明配置路径映射实体类、服务与控制器前端页面前端服务器搭建 文章说明 文件上传实现了&#xff0c;文件下载是非常简单的&#xff0c;只需要通过浏览器即可下载成功&#xff1b;于是就没有实现专门的接口 配置路径映射 通过 public void addR…

多方法做配对样本t检验(三)

Wilcoxon符号秩检验 Wilcoxon符号秩检验&#xff08;Wilcoxon Signed-Rank Test&#xff09; 是一种非参数统计方法&#xff0c;用于检验两组相关样本&#xff08;配对样本&#xff09;之间的差异是否显著。它通常用来代替配对样本t检验&#xff0c;特别是在数据不符合正态分布…

如何选择最适合企业的ETL解决方案?

在今天的大数据时代&#xff0c;企业的数据管理和处理变得愈发重要。企业也越来越依赖于数据仓库和数据湖来提取、转换和加载&#xff08;ETL&#xff09;关键业务信息。一个高效、灵活的ETL解决方案不仅能提升数据处理能力&#xff0c;还能为企业决策提供有力支持。然而&#…

前端node.js

一.什么是node.js 官网解释:Node.js 是一个开源的、跨平台的 JavaScript 运行时环境。 二.初步使用node.js 需要区分开的是node.js和javascript互通的只有console和定时器两个API. 三.Buffer Buffer 是一个类似于数组的 对象&#xff0c;用于表示固定长度的字节序列。Buffer…

Facebook的开源项目解析:推动开发者社区的技术进步

Facebook&#xff0c;作为全球领先的社交平台之一&#xff0c;其在技术领域的创新不仅体现在产品功能的实现上&#xff0c;也积极推动开源社区的发展。开源项目已经成为Facebook技术战略的重要组成部分&#xff0c;通过开源&#xff0c;Facebook不仅加速了技术进步&#xff0c;…

本地学习axios源码-如何在本地打印axios里面的信息

1. 下载axios到本地 git clone https://github.com/axios/axios.git 2. 下载react项目, 用vite按照提示命令配置一下vite react ts项目 npm create vite my-vue-app --template react 3. 下载koa, 搭建一个axios请求地址的服务端 a.初始化package.json mkdir koa-server…

电阻可靠性的内容

一、影响电阻可靠性的因素&#xff1a; 影响电阻可靠性的因素有温度系数、额定功率&#xff0c;最大工作电压、固有噪声和电压系数 &#xff08;一&#xff09;温度系数 电阻的温度系数表示当温度改变1摄氏度时&#xff0c;电阻阻值的相对变化&#xff0c;单位为ppm/℃.电阻温…