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

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

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

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

查询计划处理模型

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