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

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

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

查询优化概述

查询优化在数据库管理系统(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 双主架构 循环复制 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 Entity JOIN 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 语句 神经系统 慢SQL优化 千万级 拦截器 动态代理 Valkey Valkey8.0 矢量存储 数据库类型 AI代理 查询规划 关系数据库 编程 count(*) count(主键) 行数 UUID ID 表空间 恢复数据