• CMU15-445 数据库系统播客:查询规划与优化

CMU15-445 数据库系统播客:查询规划与优化

2025-08-16 12:31:58 栏目:宝塔面板 44 阅读

查询优化概述

查询优化在数据库管理系统(DBMS)中至关重要 。SQL是一种声明性语言,这意味着用户告诉DBMS他们想要什么结果,而不是如何获取结果。然而,执行同一个查询可以有多种不同的方式(例如,不同的连接算法),而这些执行计划的性能差异可能非常大。例如,一个表操作可能在1.3小时和0.45秒之间产生巨大差异。因此,DBMS需要一种方法来选择给定查询的“最佳”执行计划,这就是DBMS优化器的职责。

查询优化非常困难,这被认为是构建DBMS最困难的部分。成功的查询优化能力可以显著区分高端数据库系统(如Oracle、DB2、Teradata、SQL Server)与开源或免费系统(如Postgres,尽管Postgres也很好,但其查询优化器不如SQL Server复杂)。IBM在20世纪70年代首次实现了查询优化器,即System R项目。当时,人们认为DBMS无法选择比人类手写更好的查询计划。然而,System R证明了数据库系统可以通过优化器生成与人类编写的计划一样好甚至更好的计划。System R的许多概念和设计决策至今仍在使用。

查询优化的两种主要方法

查询优化主要有两种方法:

  1. 静态规则/启发式规则 (Static Rules / Heuristics)
  • 通过重写查询来消除低效或不必要的元素。
  • 这些技术 不需要检查实际数据 ,但可能需要查阅系统目录(即元数据)。
  1. 基于成本的搜索 (Cost-based Search)
  • 使用成本模型估算执行计划的成本。
  • 评估查询的多个等效计划,并选择成本最低的那个。

静态规则与查询重写

静态规则的核心思想是 关系代数等价性 。如果两个关系代数表达式或查询计划产生相同的元组集合,则它们是等效的。重要的是,这里强调的是“集合”,意味着 不要求结果的顺序相同 。这种等价性允许DBMS在不改变最终结果的前提下,通过转换或重排操作符来找到更高效的执行计划。这种高级技术通常被称为 查询重写 (Query Rewriting) 。

在查询优化架构中,SQL查询首先经过一个可选的 SQL重写器 ,然后由 解析器 转换为抽象语法树。 绑定器 将语法树中的命名对象(如表、列名)转换为内部标识符,并查阅系统目录,生成 逻辑计划 。逻辑计划以高层方式描述查询要做什么(如扫描表、连接表),但不指定具体如何执行。随后,逻辑计划可以进入 树重写器 ,在这里应用静态规则进行重写。

以下是一些常见的静态规则优化:

谓词下推 (Predicate Pushdown)

什么是谓词?如何理解? 谓词是指SQL查询中用于过滤数据的条件,通常出现在WHERE子句中。例如,e.grade = 'A'就是一个谓词。

如何理解下推? 假设SQL查询被分析并表示为一棵操作树,谓词下推就是将过滤操作从树的较高层(例如,在连接之后)移动到较低层(例如,在连接之前)。

举例子:把 WHERE 推到 JOIN 之前。 例如,对于一个连接了studentenrolled表的查询,并且有一个WHERE e.grade = 'A'的条件。最初可能先执行连接,再应用过滤。通过谓词下推,优化器会先在enrolled表上应用grade = 'A'的过滤,从而在执行连接之前就大大减少参与连接的元组数量。这样做的目的是 尽早减少数据量 ,从而减少后续操作的工作量。

此外,还可以 重排谓词的顺序 ,优先应用选择性更高的谓词(即能过滤掉更多数据的谓词),以更快地减少处理的数据量。

需要注意的是,并非所有谓词都适合下推,例如,如果谓词涉及计算成本较高的用户定义函数(UDF),数据库可能会选择不将其下推。

投影下推 (Projection Pushdown)

在查询早期阶段执行投影操作,只保留查询所需或连接所需的属性。

这样可以 最小化从一个操作符传递到下一个操作符的数据量 ,这在行式存储系统(避免复制宽行中不必要的列)和分布式数据库(减少网络传输的数据量)中尤为重要。

当DBMS分析SQL查询并将其转换为操作树时,投影下推意味着在查询执行的早期阶段, 只保留查询所需或后续操作(例如连接操作)所需的属性(列) 。其他不需要的列会在数据量较小的阶段就被“投影掉”,从而避免将它们复制和传递到后续的昂贵操作中。

假设有一个SQL查询,需要从student表和enrolled表中获取学生姓名(s.name)和课程ID(e.cid),并且两个表通过s.sid = e.sid进行连接。如果student表有上千个列,但这个查询只需要其中的sidname列,enrolled表也只需要sidcid列,那么:

  1. 原始(未优化)计划 :可能会先将student表的全部列和enrolled表的全部列进行连接,然后再对连接后的巨大结果集进行投影,只保留namecid
  2. 应用投影下推后 :优化器会在这两个表被连接 之前 ,就对student表执行一个投影操作,只保留sidname列;对enrolled表执行投影操作,只保留sidcid列。这样,在执行连接时,参与连接的元组会更“窄”,大大减少了需要处理的数据量。

这种优化在以下场景中尤为重要:

  • 行式存储系统(Row-Store Systems) :在行式存储系统中,如果一个元组非常宽(即有很多列),并且查询只需要其中的少数几列,那么过早地将整个宽元组从一个操作符传递到下一个操作符会消耗大量内存和I/O。通过投影下推,可以尽早地剔除不必要的列,从而 减少在内存中复制和处理的数据量 。
  • 分布式数据库(Distributed Databases) :在分布式环境中,数据可能存储在不同的节点上,并且在执行连接等操作时需要在网络上传输。 网络I/O是慢且低效的 。如果能在数据传输到其他节点之前就进行投影,只发送必要的列,可以显著 减少网络传输的数据量 和开销。
  • 减少后续操作的开销 :当数据量减少后,后续的连接、排序、聚合等操作的处理效率会更高,因为它们需要处理的数据总量更小。

需要注意的是,投影下推对 列式存储系统(Column-Store Systems) 来说可能不那么重要,因为列式存储本身就是按列存储数据,通常在读取时就只读取查询所需的列。

表达式简化与重写 (Expression Simplification and Rewriting)

简化复杂的谓词表达式。例如,WHERE X = Y AND Y = 3 可以被简化为 WHERE X = 3 AND Y = 3

合并谓词 :将多个范围谓词合并为更紧凑的形式。例如,WHERE val BETWEEN 1 AND 100 OR val BETWEEN 50 AND 150 可以简化为 WHERE val BETWEEN 1 AND 150

这些处理属于 明显的逻辑优化 ,它们在不查看实际数据内容的情况下,仅凭查询本身的结构和系统目录中的元数据(例如,主键不能为NULL)即可识别并进行重写。例如,优化器可以识别并移除不可能的谓词(如WHERE 1 = 0,总是假)或不必要的谓词(如WHERE 1 = 1,总是真),或者识别出冗余的自连接。

复杂查询与基于成本的搜索

对于复杂的查询,仅仅依靠静态规则是不够的。例如, 不同的连接顺序 (例如,对于N个表的连接,可能存在4^N种连接顺序,这是一个巨大的数字,即卡特兰数)以及 使用什么连接算法 (如哈希连接与嵌套循环连接的性能差异巨大)等决策, 无法仅通过静态规则来确定 。此时,就需要 基于成本的搜索 (Cost-based Search) 。

成本模型 (Cost Model)

数据库系统内部维护一个 成本模型 ,用于 估算每个潜在执行计划可能产生的成本 。

这个成本是一个 内部的、合成的数字 ,它 只用于在同一DBMS内部比较不同查询计划的相对性能 。它与实际的执行时间没有直接的外部映射关系。

成本估算通常基于多种因素,包括 磁盘I/O次数 、 DRAM(内存)占用量 ,以及在分布式数据库中, 网络消息的数量 (因为网络I/O慢且低效)。

成本模型的核心目的是在 不实际运行查询计划 的情况下,近似估算其成本。实际运行查询是获得真实成本的唯一方式,但由于可能的计划数量巨大,这不切实际。

少数系统(如MongoDB)曾采用过简单的方法,即并行触发多个查询计划,选择第一个返回结果的计划,并将其作为后续相同查询的默认计划。

统计信息 (Statistics Information)

为了能够准确估算查询计划的成本,DBMS需要 维护关于表结构的内部统计信息 。

这些统计信息通常存储在 系统目录 中, 包括表和索引的外观、元组中的值分布 (如特定列的最小值/最大值、唯一值的数量、直方图等)。

统计信息的维护 可以通过以下方式进行:

  • 自动更新 :当表数据发生一定比例(如10%)的变化时自动收集。
  • 查询时收集 :在执行查询时,DBMS可以查看数据并更新相关统计信息。
  • 手动执行 ANALYZE 命令 :用户或管理员可以显式运行ANALYZE函数(在不同系统中有不同的语法,但概念类似),这通常会启动一次全表扫描,检查数据并更新内部统计信息。

准确的统计信息对于优化器做出明智的成本估算至关重要 。它们帮助优化器更好地理解数据的分布和选择性,从而更准确地预测不同操作的开销。

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

搜索文章

Tags

数据库 API FastAPI Calcite 电商系统 MySQL Web 应用 异步数据库 数据同步 ACK 双主架构 循环复制 TIME_WAIT 运维 负载均衡 JumpServer SSL 堡垒机 跳板机 HTTPS 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 长轮询 配置 自定义序列化 Netstat Linux 服务器 端口 Linux 安全 ​Redis 推荐模型 Postgres OTel Iceberg 工具 云原生 scp Linux的scp怎么用 scp上传 scp下载 scp命令 AI 助手 SQLark 向量数据库 大模型 共享锁 PG DBA openHalo 存储 SQLite-Web SQLite 数据库管理工具 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 数据结构 ZODB 核心架构 订阅机制 引擎 性能 数据脱敏 加密算法 B+Tree ID 字段 分布式 集中式 Go 数据库迁移 读写 容器 数据类型 虚拟服务器 虚拟机 内存 Redis 8.0 容器化 网络故障 DBMS 管理系统 频繁 Codis 模型 OAuth2 Token JOIN 微软 SQL Server AI功能 QPS 高并发 工具链 国产数据库 发件箱模式 自动重启 Pottery 聚簇索引 非聚簇索引 原子性 部署 Entity Testcloud 云端自动化 速度 服务器中毒 事务隔离 SpringAI 排行榜 排序 SSH Caffeine CP Web 接口 分页方案 排版 数据集成工具 MCP 开放协议 行业 趋势 数据页 悲观锁 乐观锁 StarRocks 数据仓库 Redka 大表 业务场景 sqlmock LRU 分页 AIOPS 分布式架构 分布式锁​ 1 优化器 池化技术 连接池 单点故障 仪表盘 网络 dbt 数据转换工具 Order EasyExcel MySQL8 意向锁 记录锁 事务同步 InfluxDB 日志 IT 对象 字典 RAG HelixDB 双引擎 播客 订单 单线程 主从复制 代理 Crash 代码 编程 UUIDv7 主键 UUID ID Ansible LLM 语句 Pump 恢复数据 Valkey Valkey8.0 ReadView 产业链 兼容性 数据字典 线程安全 List 类型 Weaviate MGR 分布式集群 失效 解锁 调优 Next-Key 表空间 分布式锁 Zookeeper 关系数据库 慢SQL优化 RR 互联网 GitHub Git 矢量存储 数据库类型 AI代理 查询规划 国产 用户 算法 快照读 当前读 视图 千万级 神经系统 count(*) count(主键) 行数 CAS 技巧 并发控制 恢复机制 拦截器 动态代理 多线程 闪回