第十讲 Query Execution Part 1

1 处理模型【Processing Model】

DBMS 的处理模型【Processing Model】定义了系统如何执行【execute】查询计划【Query Plan】。

  • 针对不同的工作负载进行不同的权衡。

方法1:迭代器模型【Iterator Model】
方法2:物化模型【Materialization Model】
方法3:矢量化/批处理模型【Vectorized / Batch Model】

1.1 迭代器模型

迭代器是最简单的模型。他是一种自顶向下的方法。

每个查询计划【Query Plan】的(物理)操作符【Operator】都实现一个 Next() 函数。

  • 在每次调用时,操作符返回单个元组(需要注意的是,这个元组可以是关于列的一个子集,也可以是 Record ID),如果没有更多元组,则返回 eof 标记。
  • 操作符实现一个循环,即查询计划树中的父操作符在在其子节点的操作符上调用 Next() 来检索其输出元组,然后处理它们。

每个操作符的实现还具有 Open() 和 Close() 函数,它们类似于构造函数和析构函数,但这是用于操作符的罢了。
迭代器模型也称为火山模型【Volcano Model】或管道模型【Pipeline Model】。

如今大多数 DBMS 都使用这种方法,,它使得元组流水线【pipeling】成为可能。
许多运算符必须阻塞,直到他们的子操作符发出所有元组,赭红操作符也被称为 Pipeline Breaker:

  • 连接【Joins】,
  • 子查询【Subqueries】
  • 排序【Order By】

使用这种方法可以轻松进行输出控制。比如我们在查询【Query】中添加 LIMIT 10 子句,那么点那个我们的根操作符在迭代了 10 次后就可以停止了,我们不需要再继续调用 Next 了。

迭代器的约束是:我们一次只能处理一个元组,我们通过 Next 获取一个元组,然后可能会去做一些成本很高的操作,如果我们可以批量处理,那么就可以摊销很多的成本。

1.2 物化模型

物化模式是一种自底向上的方法。当父操作符调用子操作符时,他总是生成他可以生成的所有结果,然后上推给它的父擦作符。

每个操作符一次处理所有输入,然后一次发出所有输出。

  • 操作符将其输出“物化”为单个结果。
  • DBMS 可以下推提示【push down hints】(例如 LIMIT)以避免扫描太多元组。
  • 可以发送物化后的行或者单个行。

输出可以是整个元组 (NSM) 或列的子集 (DSM)。

在该模型下,我们经常会用到内链【inline】,比如图中的 S 表假设有亿万数据,我们将所有元组加载到内存,然后一次返回给父操作符,这种方法太愚蠢了,因此我们会将某些操作符进行内联。

物化模型更适合 OLTP 工作负载,因为查询一次仅访问少量元组。

  • 更低的执行/协调开销。
  • 更少的函数调用/操作符调用

但是不适合具有大量中间结果的 OLAP 查询。 

1.3 矢量模型

矢量模型就像迭代器模型一样,每个运算符都实现一个 Next() 函数,但是每个操作符发出一批元组而不是单个元组。

  • 每个运算符在内部作循环中,一次处理多个元组来加快速度(由于我们一次可以看到所有的数据,我们可以做一些矢量化操作,比如SIMD)
  • 批次的大小可能因硬件或查询属性而异

矢量化模型非常适合 OLAP 查询,因为它大大减少了每个操作符的调用次数,并使得操最符更轻松地使用矢量化 (SIMD) 指令来处理批量元组。 

1.4 PLAN PROCESSING DIRECTION

方法1:自上而下:

  • 从根开始,从其子节点“提取”数据。
  • 元组总是通过函数调用传递。

方法2:自下而上

  • 从叶节点开始,将数据推送到其父节点。
  • 允许对管道中的缓存/寄存器进行更严格的控制。
  • 更适合动态查询重新优化。

但是,大多数系统都是自顶而下的。

2 访问方法【Access Method】

访问方法是 DBMS 访问存储在表中的数据的方式,它在关系代数中未定义。

三种基本的访问方法:

  • 顺序扫描【Sequential Scan】
  • 索引扫描【 Index Scan】(许多变体)。
  • 多索引扫描【Multi-Index Scan】

2.1 顺序扫描【Sequential Scan】

对于表中的每一页【Page】:

  • 从缓冲池中检索它
  • 迭代每个元组并检查是否需要包含它

DBMS 维护一个内部游标,用于跟踪它检查过的最后一个页【Page】/槽【SLot】。、

这几乎总是 DBMS 在执行查询时可以做的最糟糕的事情,但它有时候可能是唯一可用的选择(比如当我们没有任何索引时)。
顺序扫描的优化:

  • 预取【Prefetching】
  • 缓冲池绕过【Buffer Pool Bypass】
  • 并行化【Parallelization】
  • 堆聚簇【Heap Clustering】
  • 延迟物化【 Late Materialization】
  • 数据跳过【Data Skipping】
数据跳过

方法1:近似查询【Approximate Queries】(有损)

  • 对整个表的采样子集执行查询以产生近似结果。
  • 示例:BlinkDB、Redshift、ComputeDB、XDB、Oracle、Snowflake、Google BigQuery、DataBricks

方法2:区域地图【Zone Map】(无损)

  • 预先计算每页的列式聚合【Aggregation】,DBMS 首先检查区域地图【Zone Map】来决定是否要访问该页【Page】。
  • 需要在页面大小与过滤器效能之间进行权衡,因为页面越大,数据也就越多,如果在一个有百万数据的大页上,我们根据区域地图算出需要访问该页,但是该页中可能只有一条数据满足查询条件,但是我们不得不扫描完整的页。
  • 示例:Oracle、Vertica、SingleStore、Netezza、Snowflake、Google BigQuery

2.2 索引扫描【 Index Scan】

DBMS 选择一个索引来查找查询【Query】所需的元组。
使用哪个索引取决于(所有这些都是后面章节里会讲,大概是第12讲):

  • 索引包含哪些属性【attribute】
  • 查询【Query】引用哪些属性【attribute】
  • 属性【attribute】的值域
  • 谓词组合【Predicate composition】
  • 索引是否有唯一键或非唯一键

假设我们有一个包含 100 个元组和两个索引的表:
→ 索引1:age
→ 索引2:dept

SELECT * FROM students

WHERE age < 30

AND dept = 'CS'

AND country = 'CHINA'

场景1:30岁以下的有99人,但是CS部门只有2人。 

在该场景下,我们希望可以选择 dept 索引,因为他的选择性更好,可以顾虑更多的数据

场景1:30岁以下的有 2人,但是CS部门有99人。 

在该场景下,我们希望可以选择 age 索引,因为他的选择性更好,可以顾虑更多的数据

2.3 多索引扫描【Multi-Index Scan】

如果 DBMS 可以在一个查询【Query】中使用多个索引:

  • 使用每个匹配的索引计算 Record ID 的集合。
  • 根据查询【Query】的谓词,来组合这些集合(并集与交集)
  • 检索记录并应用剩余的谓词

实现的数据库:

  • DB2 多索引扫描
  • PostgreSQL 位图扫描
  • MySQL 索引合并

SELECT * FROM students

WHERE age < 30

AND dept = 'CS'

AND country = 'CHINA'

继续复用前面的 age 和 dep 的例子:

  • 我们可以使用第一个检索 idx_age 来匹配满足 age < 30 的 Record ID
  • 然后使用第二个检索 idx_dep 来匹配满足 dept = 'CS' 的 Record ID,
  • 取它们 的交集
  • 检索记录,并检查 country = 'CHINA'

可以使用位图【Bitmap】或哈希表【Hash Table】有效地完成集合交集。

3 修改查询【Modification Query】

修改数据库的操作符(INSERT、UPDATE、DELETE)需要负责修改目标表【table】及其索引【index】,约束检查可以立即在操作符内部发生,也可以推迟到稍后的查询【Query】/事务【Transction】中。


这些运算符的输出可以是 Record ID 或元组数据(即 RETURNING)。

UPDATE/DELETE:

  • 子操作符传递目标元组的 Record ID
  • 必须追踪每一个先前看到过的元组(下面例子里会讲,这也是万圣节问题的解决方案)。

INSERT:

  • 选择1 :在操作符内部物化元组
  • 选择2:操作符插入从子操作符传入【Pass In】的任何元组。

例子:

1️⃣ 假设我们有一张perople表,表中记录了薪水

2️⃣ 现在我想对所有薪水小于 1100 的人作一次 100 元的薪水普调,那么现在我的更新操作符会调用它的子操作符的 Next 方法,该子操作符执行一个索引扫描,以超出所有薪水小于 1100 的工人

 

3️⃣ 在索引扫描中,我们跟随游标遍历数据,并将满足条件的元组返回给父操作符

4️⃣ 然后父操作符将它从索引内删除,然后插入一条更新后的索引


5️⃣ 然后索引游标继续滑动,但是我们会查到刚才插入的那条数据

这就是著名的万圣节问题【HALLOWEEN PROBLEM】,即:更新操作更改元组的物理位置,导致扫描操作符多次访问该元组的异常。

  • 可能发生在聚簇表或索引扫描上。

IBM 研究人员于 1976 年万圣节在 System R 上工作时首次发现。
解决方案:跟踪每个查询【QUery】中修改的 Record ID。 

4 表达式评估【Expression Evaluation】

DBMS 将 WHERE 子句表示为表达式树【Expression Tree】。我们前面看到查询计划【QUery Plan】是一颗关于操作符的数,在每个操作符内部依然可以是树结构。

表达式树中的节点代表不同的表达式类型:

  • 比较(=、<、>、!=)
  • 合取 (AND)、析取 (OR)
  • 算术运算符(+、-、*、/、%)
  • 常数值
  • 元组属性引用【Tuple Attribute Reference】

例子:

1️⃣ 下面是我们要执行的 Prepared 语句,我们调用execute执行它

 2️⃣ 下面是该SQL中的表达式树:

以这种方式评估谓词的速度其实很慢,DBMS 遍历树,对于它访问的每个节点,它必须弄清楚操操作符需要做什么。

我们来考虑这个谓词:WHERE S.val=1

更好的方法,或者说是优化吧,就是直接计算表达式,回想一下 JIT 编译,Postgre 中就有这个特性,它会将表达式部分内联编译(set jit = 'on')。

5. Schedule

到目前为止,我们基本上已经从数据流【data flow】的角度了解了查询处理模型【query processing model】。
控制流【conrol flow】隐含在处理模型【processing model】中。 我们可以使用调度程序【scheduler】使控制流更加明确。
数据库论文中通常不会讨论查询调度程序【scheduler】。 我们将看看 Quickstep(学术)项目中做了什么。 基于允许数据流和控制流之间的频繁切换。

相关推荐

  1. C++:二分查找

    2024-04-07 14:44:06       41 阅读
  2. _css2d转换

    2024-04-07 14:44:06       43 阅读
  3. _css媒体查询

    2024-04-07 14:44:06       40 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-04-07 14:44:06       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-07 14:44:06       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-07 14:44:06       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-07 14:44:06       20 阅读

热门阅读

  1. redis的Set详细介绍

    2024-04-07 14:44:06       12 阅读
  2. redis修改协议改了,有哪些替代品?

    2024-04-07 14:44:06       15 阅读
  3. 如何让你的回答有逻辑?-开放式问题应答技巧

    2024-04-07 14:44:06       20 阅读
  4. C++类和对象(上)

    2024-04-07 14:44:06       16 阅读
  5. Vscode已经安装imutils但是依旧报错

    2024-04-07 14:44:06       23 阅读
  6. 设计模式:生活中的观察者模式

    2024-04-07 14:44:06       18 阅读
  7. Spring相关实战

    2024-04-07 14:44:06       13 阅读
  8. Flutter Engine引擎概念

    2024-04-07 14:44:06       21 阅读
  9. HOW - 前端国际化之多语言通用方案

    2024-04-07 14:44:06       16 阅读
  10. 「 网络安全常用术语解读 」同源策略SOP详解

    2024-04-07 14:44:06       16 阅读
  11. Openstack(T)部署ceph集群 ceph-14.2.22-nautilus

    2024-04-07 14:44:06       10 阅读