Carry の Blog Carry の Blog
首页
  • Nginx
  • Prometheus
  • Iptables
  • Systemd
  • Firewalld
  • Docker
  • Sshd
  • DBA工作笔记
  • MySQL
  • Redis
  • TiDB
  • Elasticsearch
  • Python
  • Shell
  • MySQL8-SOP手册
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Carry の Blog

好记性不如烂键盘
首页
  • Nginx
  • Prometheus
  • Iptables
  • Systemd
  • Firewalld
  • Docker
  • Sshd
  • DBA工作笔记
  • MySQL
  • Redis
  • TiDB
  • Elasticsearch
  • Python
  • Shell
  • MySQL8-SOP手册
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • MySQL

  • Redis

  • Keydb

  • TiDB

    • TiCDC同步数据到Kafka
    • 对TiDB中算子的深入理解
      • 一、算子基础概念
        • 1.1 什么是算子?
        • 1.2 算子的作用
        • 1.3 算子与执行计划
      • 二、TiDB中的核心算子类型
        • 2.1 数据源算子
        • 2.2 过滤和投影算子
        • 2.3 连接算子
        • 2.4 聚合算子
        • 2.5 排序算子
      • 三、TiDB特有的分布式算子
        • 3.1 分布式执行算子
        • 3.2 MPP架构相关算子
      • 四、算子执行原理与优化
        • 4.1 算子执行模型
        • 4.2 常见算子优化技术
      • 五、实际案例分析
        • 5.1 简单查询的算子链
        • 5.2 复杂查询的算子树
        • 5.3 使用EXPLAIN分析执行计划
      • 六、算子调优实践
        • 6.1 识别性能瓶颈算子
        • 6.2 常见算子优化方法
        • 6.3 TiDB特有的调优参数
      • 总结
    • TiDB使用 TTL (Time to Live) 定期删除过期数据
    • 如何移除TiDB中的表分区
    • TiDB配置文件调优
    • 深入解析TiFlash:原理、适用场景与调优实践
    • tidb fast ddl
  • MongoDB

  • Elasticsearch

  • Kafka

  • victoriametrics

  • BigData

  • Sqlserver

  • 数据库
  • TiDB
Carry の Blog
2022-03-10
目录

对TiDB中算子的深入理解原创

# 对TiDB中算子的深入理解

# 一、算子基础概念

# 1.1 什么是算子?

算子(Operator)是数据库查询执行过程中的基本处理单元,每个算子负责完成特定的数据处理任务。在TiDB这样的分布式数据库中,算子不仅是查询执行的基本构建块,还是分布式计算的核心组件。

当我们执行一条SQL查询时,如SELECT name FROM users WHERE age > 18 ORDER BY create_time;,TiDB会将这条SQL语句解析并转换为由多个算子组成的执行计划,每个算子负责一个特定的处理步骤。

# 1.2 算子的作用

算子在查询执行中扮演着关键角色:

  • 数据处理分解:将复杂查询拆分为简单、可管理的步骤
  • 执行计划构建:形成查询的逻辑和物理执行路径
  • 分布式计算协调:在TiDB集群的不同节点间协调数据处理
  • 性能优化基础:优化器可以针对不同算子进行优化决策

# 1.3 算子与执行计划

执行计划是由多个算子组成的树形结构,数据从叶子节点(通常是表扫描算子)流向根节点(通常返回最终结果)。TiDB的优化器会根据统计信息、表结构和查询特点选择最优的算子组合和执行顺序。

# 二、TiDB中的核心算子类型

# 2.1 数据源算子

数据源算子负责从存储层读取原始数据:

  • TableScan:全表扫描,读取表中的所有数据

    • 适用场景:小表、无合适索引的查询
    • 性能特点:I/O开销大,适合读取高比例的表数据
  • IndexScan:索引扫描,通过索引定位和读取数据

    • 适用场景:有匹配索引的查询条件
    • 性能特点:可以显著减少I/O,适合高选择性查询
  • IndexLookup:索引回表,先通过索引定位行,再回表获取完整数据

    • 适用场景:索引不覆盖所有查询列
    • 性能特点:比TableScan快,但比IndexScan慢(需要两次访问)

# 2.2 过滤和投影算子

这类算子负责数据的筛选和列选择:

  • Selection:根据条件过滤数据行

    • 实现方式:应用WHERE子句中的条件
    • 优化技巧:尽可能下推到存储层执行
  • Projection:选择和计算需要的列

    • 实现方式:提取SELECT子句中指定的列
    • 优化技巧:减少不必要的列传输,提前计算表达式

# 2.3 连接算子

连接算子用于合并多个表的数据:

  • HashJoin:哈希连接,适合大数据量连接

    • 工作原理:将小表构建哈希表,大表进行探测
    • 适用场景:两表大小差异明显的等值连接
    • 内存消耗:需要将构建表全部加载到内存
  • MergeJoin:归并连接,适合已排序数据

    • 工作原理:利用两表的有序性进行归并
    • 适用场景:连接列上有序(如索引列)
    • 性能特点:避免全表排序的开销
  • IndexJoin:索引连接,利用被连接表的索引

    • 工作原理:对外表的每一行,通过内表索引查找匹配行
    • 适用场景:内表在连接列上有索引
    • 性能特点:适合外表较小,内表较大的情况

# 2.4 聚合算子

聚合算子用于分组和计算汇总数据:

  • HashAgg:哈希聚合,使用哈希表进行分组

    • 工作原理:将分组列构建哈希表,聚合计算
    • 内存消耗:较高,需要存储分组信息
    • 适用场景:分组较多,无序数据
  • StreamAgg:流式聚合,对有序数据进行聚合

    • 工作原理:利用数据的有序性顺序聚合
    • 内存消耗:较低,不需要全部存储
    • 适用场景:数据已按分组列排序

# 2.5 排序算子

  • Sort:对数据进行排序

    • 实现方式:实现ORDER BY语句的功能
    • 内存消耗:大数据量时可能使用外部排序
    • 优化技巧:利用索引顺序避免排序
  • TopN:获取排序后的前N条记录

    • 实现方式:维护大小为N的堆
    • 性能优势:比完全排序更节省资源
    • 适用场景:LIMIT子句限制结果集大小

# 三、TiDB特有的分布式算子

# 3.1 分布式执行算子

  • Exchange:数据交换算子,负责节点间数据传输

    • 作用:协调不同节点间的数据流动
    • 类型:HashExchange(哈希分区)、BroadcastExchange(广播)
  • Coprocessor:下推到TiKV的算子

    • 作用:将过滤、聚合等操作推送到存储节点执行
    • 优势:减少网络传输,利用分布式并行计算

# 3.2 MPP架构相关算子

TiDB 5.0引入的MPP(大规模并行处理)架构带来了新的算子:

  • ExchangeSender/ExchangeReceiver:MPP节点间数据交换

    • 作用:在TiFlash节点间传输数据
    • 优势:支持更复杂的并行计算模型
  • MPPHashAgg/MPPHashJoin:MPP模式下的聚合和连接

    • 特点:利用多节点并行处理大规模数据
    • 适用场景:复杂分析查询,大表连接

# 四、算子执行原理与优化

# 4.1 算子执行模型

TiDB支持两种执行模型:

  • 火山模型(Volcano Model):

    • 工作方式:通过迭代器接口(Next())逐行处理数据
    • 优点:实现简单,内存占用小
    • 缺点:函数调用开销大,不利于现代CPU优化
  • 向量化执行模型:

    • 工作方式:批量处理数据(如1024行一批)
    • 优点:减少函数调用开销,提高CPU缓存命中率
    • 应用:TiDB在分析型查询中采用向量化执行

# 4.2 常见算子优化技术

TiDB优化器会应用多种技术优化算子执行:

  • 算子下推:将过滤、聚合等操作下推到存储层

    • 示例:将WHERE条件下推到TableScan
    • 优势:减少数据传输量,利用存储层索引
  • 算子合并:合并功能相近的算子减少开销

    • 示例:将Filter和TableScan合并
    • 优势:减少中间结果和处理步骤
  • 算子重排序:调整算子执行顺序提高效率

    • 示例:将高选择性Filter前置
    • 优势:尽早减少数据量
  • 并行执行:多线程并行执行算子

    • 适用算子:Join、Aggregation等计算密集型算子
    • 优势:充分利用多核CPU资源

# 五、实际案例分析

# 5.1 简单查询的算子链

对于查询:SELECT name, age FROM users WHERE age > 20;

可能的执行计划:

Projection(name, age)
└── Selection(age > 20)
    └── TableScan(users)
1
2
3

执行流程:

  1. TableScan从users表读取原始数据
  2. Selection过滤出age > 20的记录
  3. Projection提取name和age列返回

# 5.2 复杂查询的算子树

对于查询:SELECT u.name, COUNT(o.id) FROM users u JOIN orders o ON u.id = o.user_id WHERE u.age > 20 GROUP BY u.name;

可能的执行计划:

Projection(name, count)
└── HashAgg(group by: name, aggr: count(id))
    └── HashJoin(u.id = o.user_id)
        ├── Selection(age > 20)
        │   └── TableScan(users)
        └── TableScan(orders)
1
2
3
4
5
6

执行流程:

  1. 两个TableScan并行从users和orders表读取数据
  2. Selection过滤users表中age > 20的记录
  3. HashJoin根据user_id连接两表数据
  4. HashAgg按name分组并计算订单数量
  5. Projection提取最终结果列

# 5.3 使用EXPLAIN分析执行计划

在TiDB中,可以使用EXPLAIN语句查看SQL的执行计划:

EXPLAIN SELECT name, age FROM users WHERE age > 20;
1

输出示例:

+-------------------------+----------+-----------+---------------+--------------------------------+
| id                      | estRows  | task      | access object | operator info                  |
+-------------------------+----------+-----------+---------------+--------------------------------+
| Projection_4            | 8000.00  | root      |               | test.users.name, test.users.age|
| └─Selection_5           | 8000.00  | root      |               | gt(test.users.age, 20)         |
|   └─TableReader_7       | 10000.00 | root      |               | data:TableFullScan_6           |
|     └─TableFullScan_6   | 10000.00 | cop[tikv] | table:users   | keep order:false               |
+-------------------------+----------+-----------+---------------+--------------------------------+
1
2
3
4
5
6
7
8

通过EXPLAIN可以看到:

  • 估计的行数(estRows)
  • 算子执行位置(root表示TiDB,cop表示TiKV/TiFlash)
  • 访问的表和索引
  • 算子的详细信息

# 六、算子调优实践

# 6.1 识别性能瓶颈算子

通过EXPLAIN ANALYZE可以获取算子实际执行时间和处理行数:

EXPLAIN ANALYZE SELECT name, age FROM users WHERE age > 20;
1

关注以下指标:

  • 执行时间明显偏长的算子
  • 预估行数与实际行数差异大的算子
  • 处理数据量大的算子

# 6.2 常见算子优化方法

  • TableScan优化:

    • 创建合适的索引转换为IndexScan
    • 使用分区表减少扫描范围
  • Join优化:

    • 调整表顺序,小表作为构建表
    • 在连接列上创建索引
    • 适当增加join_buffer_size
  • Aggregation优化:

    • 在分组列上创建索引
    • 减少不必要的分组列
    • 考虑预计算或物化视图
  • Sort优化:

    • 在排序列上创建索引
    • 使用LIMIT减少排序数据量
    • 考虑是否真正需要排序

# 6.3 TiDB特有的调优参数

  • tidb_distsql_scan_concurrency:控制并发扫描的线程数
  • tidb_index_lookup_join_concurrency:控制索引查找连接的并发度
  • tidb_hash_join_concurrency:控制哈希连接的并发度
  • tidb_projection_concurrency:控制投影算子的并发度

# 总结

TiDB的算子系统是其分布式SQL引擎的核心组件,理解各类算子的工作原理和优化技巧对于编写高效SQL和调优TiDB性能至关重要。通过合理利用索引、调整执行计划和配置参数,可以显著提升查询性能。

在实际应用中,应结合EXPLAIN分析工具,识别查询中的瓶颈算子,有针对性地进行优化。同时,随着TiDB版本的更新,算子的实现和优化策略也在不断演进,建议关注官方文档获取最新的优化建议。

#执行计划#SQL优化#分布式计算
上次更新: 4/24/2025

← TiCDC同步数据到Kafka TiDB使用 TTL (Time to Live) 定期删除过期数据→

最近更新
01
tidb fast ddl
04-04
02
TiDB配置文件调优 原创
04-03
03
如何移除TiDB中的表分区 原创
04-03
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式