• CMU15-445 数据库系统播客:查询执行模型与数据访问

CMU15-445 数据库系统播客:查询执行模型与数据访问

2025-08-16 12:32:08 栏目:宝塔面板 45 阅读

本节课深入探讨了数据库系统如何执行查询计划,特别是涉及到的查询处理模型、数据访问方法以及表达式评估机制。理解这些概念对于优化数据库性能和理解查询执行的底层原理至关重要。

查询计划处理模型

数据库管理系统(DBMS)的查询计划处理模型定义了它如何执行查询计划。这些模型各有优缺点,适用于不同的工作负载和运行环境。

迭代模型(Iterator Model)

也叫火山模型(Volcano Model)/ 流水线模型(Pipeline Model)。

  • 这是最常见的处理模型,几乎所有数据库系统都采用此模型,特别是基于行的系统。
  • 其核心思想是,查询计划中的每个操作符(如连接、排序等)都实现了一个 Next() 函数。当一个父节点调用其子节点的 Next() 函数时,子节点会返回 一个元组 (tuple),或者在没有更多元组时返回一个空标记。这种机制使得数据能够像 流水线一样 在操作符之间流动,单个元组可以尽可能远地在查询计划中向上处理,从而最大化内存中元组的工作量,这在基于磁盘的系统中尤为重要,因为磁盘I/O非常昂贵。
  • 尽管该模型支持高效的流水线处理,但某些操作符无法在接收到所有子元组之前继续处理,它们被称为 管道阻塞点(Pipeline Breakers) 。常见的管道阻塞点包括: 连接(Joins) 、 子查询(Subqueries) 和  ORDER BY 操作 。例如,一个哈希连接在探测阶段之前必须先构建完整的哈希表。
  • 迭代模型易于理解和推理,并且与 LIMIT 子句等输出控制机制配合得很好。

物化模型(Materialization Model)

  • 该模型是迭代模型的一个 专用版本 ,主要用于内存数据库系统。
  • 与迭代模型每次返回一个元组不同,物化模型中的每个操作符会 一次性处理所有输入数据,然后一次性地发出所有输出数据 。这意味着操作符会“物化”其全部结果作为一个单一的输出。一旦操作符完成执行并返回其结果缓冲区,DBMS就无需再返回它来获取更多数据。
  • 这种方法对于 OLTP(在线事务处理)工作负载非常适用 ,因为OLTP查询通常只访问少量元组。这样可以减少函数调用的开销。
  • 然而,它 不适合处理具有大量中间结果的OLAP(在线分析处理)查询 ,因为这可能导致DBMS不得不将这些中间结果溢出到磁盘,从而降低性能。

向量化模型(Vectorized / Batch Model)

  • 向量化模型 基于迭代模型 ,但进行了优化。
  • 在每次调用 Next() 函数时,操作符不是返回一个元组,而是返回 一个批次(batch)或向量(vector)的元组 。
  • 系统被设计成操作符的内部循环可以直接处理一整批元组,而不是逐个元组处理。批次的大小可以根据硬件和查询特性进行调整。
  • 这种方法 非常适合OLAP查询 ,因为它们通常需要扫描大量元组,减少了 Next() 函数的调用次数。此外,它还允许操作符利用 向量化(SIMD)指令 来高效处理元组批次,极大地提升了性能。现代大型数据仓库系统广泛采用此模型.

除了上述模型,查询计划的执行方向通常是 从顶向下(Top-to-Bottom) ,即从根节点开始,并从其子节点“拉取”数据。但也存在 从底向上(Bottom-to-Top) 的方法,它允许更精细地控制CPU缓存和寄存器中的数据流,尽管这种方法对人类程序员来说更难理解和实现。

数据访问方法(Access Methods)

数据访问方法定义了DBMS如何从表中检索数据。它们是查询计划中的叶子操作符,负责将数据“喂给”上层的操作符。

顺序扫描(Sequential Scan)

  • 这是最基本的访问方法,DBMS会遍历表中的 每一个数据页 ,将其从缓冲区池中取出。然后,它会遍历该页中的每一个元组,并评估谓词(WHERE子句)以决定是否包含该元组。DBMS会维护一个内部游标来跟踪已检查的最后一个页面和槽位。
  • 优化措施 :尽管顺序扫描在没有索引时是唯一的选择,但它在基于磁盘的系统中通常性能很慢。因此,有多种优化方法来提升其效率:

预取(Prefetching) :提前获取后续的几个数据页,避免在需要时阻塞等待I/O。

缓冲区池旁路(Buffer Pool Bypass) :扫描操作符将从磁盘获取的页面存储在其本地内存中,而不是污染共享的缓冲区池缓存,从而避免“顺序泛洪”问题。

并行化(Parallelization) :使用多个线程或进程并行执行扫描操作。

区域图(Zone Maps) :对每个数据页中的属性值预先计算聚合信息(如最小值、最大值、平均值等)。在访问页面之前,DBMS首先检查区域图,如果区域图指示该页面不可能包含满足条件的元组,就可以 跳过整个页面 的读取,从而大大减少磁盘I/O。然而,区域图的 维护成本较高 ,不适用于高更新频率的OLTP系统,但对于“一次写入,多次读取”的OLAP工作负载非常有用。

延迟物化(Late Materialization) :在列式存储系统(DSM)中,可以延迟拼接完整元组的时机。操作符只传递最少必要的信息(例如记录ID)给下一个操作符,只有当上层查询计划需要实际数据时,才回表获取完整元组数据。这避免了不必要的数据移动,尤其是在仅需要部分列的情况下。

堆聚簇(Heap Clustering) :如果元组在堆页中按照聚簇索引的顺序存储,DBMS可以直接跳转到需要的页面,从而实现顺序访问。

索引扫描(Index Scan)

  • DBMS选择一个或多个索引来快速定位查询所需的元组,从而 减少不必要的工作量 。
  • 选择最佳索引是复杂的,取决于索引中包含的属性、查询中引用的属性、属性值的分布、谓词的类型(如小于、大于、等于)以及索引是否唯一等因素。
  • 多索引/位图扫描(Multi-Index / Bitmap Scan) :当查询条件可以利用多个索引时,DBMS会分别在每个匹配的索引上执行查找,生成匹配的记录ID集合。然后,根据查询的谓词(AND子句使用 交集 ,OR子句使用 并集 )将这些集合进行组合。最后,DBMS根据组合后的记录ID去检索实际的元组并应用任何剩余的谓词。Postgres称之为“位图扫描”,它通常使用位图、哈希表或Bloom过滤器来实现集合操作。
  • 二级索引回表随机I/O优化(Index Scan Page Sorting) :对于 非聚簇索引(unclustered index) ,通过索引扫描获取记录ID后,由于元组在磁盘上的存储顺序可能与索引的逻辑顺序不同,直接回表可能会导致大量 随机I/O ,效率低下。为了解决这个问题,DBMS可以 先通过索引扫描,收集所有满足条件的记录ID 。然后, 不立即回表,而是根据这些记录ID所在的页面ID进行排序 。这样,当实际去读取数据页时,相关的元组将集中在少数几个页面中,从而将大量的随机I/O转换为更高效的 顺序I/O ,每个页面只需读取一次。

表达式评估(Expression Evaluation)

DBMS将SQL查询中的 WHERE 子句等谓词表示为 表达式树(Expression Tree) 。树中的节点代表不同类型的表达式,如比较运算符、逻辑运算符(AND/OR)、算术运算符、常量值和元组属性引用等。

  • 评估过程 :在运行时评估表达式树时,DBMS会维护一个上下文句柄,其中包含当前正在处理的元组、查询参数和表模式等元数据。然后,DBMS会 遍历表达式树 (通常是深度优先),从叶子节点开始,计算出中间值并向上层节点传递,直到根节点产生最终的布尔结果(真或假),以判断元组是否匹配谓词。
  • 性能瓶颈与JIT编译 :虽然表达式树在概念上易于理解和实现,但对于 大量数据(例如数十亿行) ,每次评估一个元组时都重复遍历表达式树会变得 非常慢 。因为每次遍历都需要进行函数调用、分支跳转、类型检查和运算符调度等开销。
  • 为了解决这个问题,高端数据库系统采用了 即时编译(Just-In-Time Compilation, JIT) 技术。JIT编译可以将表达式树 直接编译成机器码指令 ,这些指令可以非常高效地在CPU上执行,从而避免了重复的树遍历开销。一些先进的系统甚至可以将整个查询计划编译成一条指令流水线,进一步减少间接跳转,使得查询执行如同手动编写并编译的代码一样高效。

总结 :本节课强调,数据库查询计划的执行方式有多种,具体取决于数据库系统所处的环境和所处理的工作负载。在大多数情况下,DBMS会尽可能优先使用索引扫描。而表达式树虽然灵活易懂,但在处理大量数据时会因其解释执行的特性而变得缓慢,因此通过 JIT编译 将其优化为原生机器码是提升性能的关键。

本文地址:https://www.yitenyun.com/312.html

搜索文章

Tags

数据库 API FastAPI Calcite 电商系统 MySQL Web 应用 异步数据库 数据同步 ACK 双主架构 循环复制 JumpServer SSL 堡垒机 跳板机 HTTPS TIME_WAIT 运维 负载均衡 JumpServer安装 堡垒机安装 Linux安装JumpServer Deepseek 宝塔面板 Linux宝塔 Docker 生命周期 esxi esxi6 root密码不对 无法登录 web无法登录 HexHub 序列 核心机制 Windows Windows server net3.5 .NET 安装出错 服务器 管理口 HTTPS加密 宝塔面板打不开 宝塔面板无法访问 查看硬件 Linux查看硬件 Linux查看CPU Linux查看内存 Windows宝塔 Mysql重置密码 Oracle 处理机制 InnoDB 数据库锁 无法访问宝塔面板 监控 开源 PostgreSQL 存储引擎 连接控制 机制 Serverless 无服务器 语言 ES 技术 协同 服务器性能 Spring Redis 异步化 分页查询 索引 缓存方案 缓存架构 缓存穿透 高可用 group by Undo Log GreatSQL 连接数 SQL 动态查询 机器学习 日志文件 MIXED 3 响应模型 R2DBC SVM Embedding 优化 万能公式 R edis 线程 数据 主库 RocketMQ 长轮询 配置 自定义序列化 Linux 安全 Postgres OTel Iceberg 工具 云原生 Netstat Linux 服务器 端口 ​Redis 推荐模型 scp Linux的scp怎么用 scp上传 scp下载 scp命令 AI 助手 SQLark 向量数据库 大模型 共享锁 PG DBA 存储 SQLite-Web SQLite 数据库管理工具 openHalo Hash 字段 Recursive OB 单机版 查询 电商 系统 Ftp 架构 流量 Rsync • 索引 • 数据库 锁机制 修改DNS Centos7如何修改DNS redo log 重做日志 数据分类 加密 磁盘架构 人工智能 推荐系统 场景 聚簇 非聚簇 sftp 服务器 参数 线上 库存 预扣 向量库 Milvus 业务 同城 双活 信息化 智能运维 MySQL 9.3 防火墙 黑客 Doris SeaTunnel MVCC Python 高效统计 今天这篇文章就跟大家 不宕机 数据备份 传统数据库 向量化 mini-redis INCR指令 网络架构 网络配置 INSERT COMPACT 缓存 分库 分表 Redisson 锁芯 RDB AOF prometheus Alert 窗口 函数 PostGIS 启动故障 事务 Java 开发 Canal Web IT运维 崖山 新版本 filelock MongoDB 数据结构 核心架构 订阅机制 引擎 性能 数据脱敏 加密算法 B+Tree ID 字段 分布式 集中式 ZODB Go 数据库迁移 读写 容器 数据类型 虚拟服务器 虚拟机 内存 网络故障 容器化 DBMS 管理系统 频繁 Codis 模型 Redis 8.0 OAuth2 Token JOIN 微软 SQL Server AI功能 QPS 高并发 发件箱模式 自动重启 Pottery 聚簇索引 非聚簇索引 原子性 国产数据库 部署 Entity 工具链 Testcloud 云端自动化 速度 服务器中毒 事务隔离 SpringAI 排行榜 排序 SSH Caffeine CP Web 接口 分页方案 排版 数据集成工具 MCP 开放协议 数据页 Redka 行业 趋势 悲观锁 乐观锁 StarRocks 数据仓库 sqlmock LRU 大表 业务场景 分页 AIOPS 分布式架构 分布式锁​ 1 优化器 池化技术 连接池 单点故障 仪表盘 网络 dbt 数据转换工具 Order 意向锁 记录锁 EasyExcel MySQL8 事务同步 InfluxDB IT 日志 对象 字典 RAG HelixDB 双引擎 播客 单线程 订单 主从复制 代理 Crash 代码 编程 UUIDv7 主键 LLM UUID ID Ansible 语句 Pump Valkey Valkey8.0 恢复数据 ReadView 数据字典 兼容性 线程安全 产业链 List 类型 Weaviate MGR 分布式集群 失效 解锁 调优 Next-Key 表空间 分布式锁 Zookeeper 慢SQL优化 关系数据库 GitHub Git RR 互联网 矢量存储 数据库类型 AI代理 查询规划 国产 用户 算法 快照读 当前读 视图 千万级 神经系统 count(*) count(主键) 行数 CAS 技巧 拦截器 动态代理 多线程 并发控制 恢复机制 闪回