作者 齐木
用关系型数据库一定多多少少会用到 Join 操作。常见的 Join 有 Nested-Loop Join Hash Join Sort Merge Join 等等。实际在 OLTP 场景中 最常用的就是基于索引点查的 Index Nested-Loop Join 这样的 Join 往往能在极短的时间内返回 相信这也是大多数开发同学对 Join 的感受。
PolarDB-X 不仅语法兼容 MySQL 作为分布式数据库 也力求保持与单机数据库一致的使用体验。在分布式场景下 Join 的两张表可能都是分布式表 因此需要通过多次网络请求获取相应的数据。如何高效地实现这一点呢
我们先看看单机数据库上的 Join 是怎么做的。MySQL 支持的 Join 算法很有限
如果 Join 两侧的任何一张表上 join key 列存在索引 那么 MySQL 通常会使用基于索引的 BKA Join 或 NL Join 我们实际使用中的绝大多数情形都对应这种方式。如果 Join 两侧都没有索引可以用 那么 MySQL 只能退而求其次选择 Block Nested-Loop Join 或 Hash Join 取决于 MySQL 版本 。我们今天主要关注 NL 和 BKA Join。
Nested-Loop Join 是最简单的 Join 形式 可以看作一个两层 For 循环。对于外表 也称为驱动表 中的每一行 循环检查内表 也称为被驱动表 的每一行 如果满足 Join 条件则作为 Join 结果输出。如果 Join Key 在内侧表有索引可用 那么内表的循环可以大大简化——只要查索引即可拿到可以 Join 的行 无需遍历整个表。我们也将这种带索引的 NL Join 称为 Index Nested-Loop Join。
# Nested-Loop Join for outer_row in outer_table: for inner_row in inner_table: if join_condition is True: output (outer_row, inner_row) # Index Nested-Loop Join for outer_row in outer_table: for inner_row in inner_index.lookup(outer_join_key): if join_condition is True: output (outer_row, inner_row)
注 *左右滑动阅览
下面的例子中 orders 表通过 customer 表的主键 c_custkey 与之进行 Join MySQL 会使用 Index NL Join 算法完成 Join。
/* Query 1 */ SELECT o_orderkey, o_custkey, c_name FROM orders JOIN customer ON o_custkey c_custkey WHERE o_orderkey BETWEEN 1001 AND 1005
注 *左右滑动阅览
BKA Join 可以看作一个性能优化版的 Index Nested-Loop Join。之所以称为 Batched 是因为它的实现使用了存储引擎提供的 MRR Multi-Range Read 接口批量进行索引查询 并通过 PK 排序的方法 将随机索引回表转化为顺序回表 一定程度上加速了查索引的磁盘 IO。
下面的例子中 Join Key 命中的是二级索引 并且 SELECT 的列包含二级索引中所不包含的列 因此需要进行索引回表得到完整的 Join 结果。
/* Query 2 */ SELECT c_name, c_custkey, o_orderkey, o_totalprice FROM customer JOIN orders ON c_cutkey o_custkey WHERE c_custkey BETWEEN 13 AND 15
注 *左右滑动阅览
通常 OLTP 查询中 Join 驱动侧的数据量不大 并且 Join 往往都有能匹配的索引。这种情况下 NL Join、BKA Join 的代价与驱动侧的数据量呈线性相关 可以迅速计算出结果。
PolarDB-X 的架构与 MySQL 有很大的不同 它的架构可以分为 SQL 层和存储层 SQL 层的计算节点需要计算数据所在的分片 然后从多个 DN 节点 数据节点 拉取所需的数据。
对于 Join 查询 如果恰好 Join Key 和拆分键一致 那么可以将其下推到 DN 执行。否则 就需要在 CN 节点执行 Join。PolarDB-X 支持多种 Join 算法 包括 Lookup Join、Nested-Loop Join、Hash Join、Sort-Merge Join 等多种执行方式。在 OLTP 查询中最常用的就是类似 MySQL BKA Join 的 Lookup Join 本文主要介绍 Lookup Join 其他 Join 诸如 Hash Join、Nested-Loop Join 等将在以后的文章中介绍。
除了 Join 本身的功能需求 PolarDB-X 的 Lookup Join 的设计中还要考虑以下两个性能需求
Lookup Join 的执行过程如下 非索引回表情形
/* Query 1 */ SELECT o_orderkey, o_custkey, c_name FROM orders JOIN customer ON o_cutkey c_custkey WHERE o_orderkey BETWEEN 1001 AND 1005
注 *左右滑动阅览
这个过程中有一些有趣的细节 例如 当要 lookup 的列不止一列 例如 X A AND Y B 时如何处理 这时可以通过 row-expression 组成多列的 IN 条件。如果多列 IN 条件中出现 NULL 如何处理 对于 Anti-Join 如何处理 这些就不在这里展开了 有兴趣的同学可以在评论交流。
对于绝大多数 TP 查询 Lookup Join 都可以通过一次 lookup 完成 Join 将延迟降到了最低。
PolarDB-X 还支持全局索引 用户可以为分区表创建全局索引表 加快对索引键的查询。和本地索引一样 如果查询中包含索引未覆盖的列 全局索引也需要进行回表。回表的做法和上一小节的 Lookup Join 过程是完全一致的 不难理解——索引回表可以看作一种特殊的 1:1 的 Join。
依赖全局索引的 Join 则更为复杂一些 回忆下 MySQL 的 BKA Join 需要进行两次 lookup
/* Query 2 */ SELECT c_name, c_custkey, o_orderkey, o_totalprice FROM customer JOIN orders ON c_cutkey o_custkey WHERE c_custkey BETWEEN 13 AND 15
注 *左右滑动阅览
绝大多数 OLTP 查询数据量都能通过单个 batch 完成 完成查询的总延迟为 3 次 round-trip。不难证明在分布式情况下这是最优的。
此外 PolarDB-X 也允许用户手动将更多的列加入全局索引的覆盖列 牺牲部分写入性能换取更好的读取性能 如果所有列均被覆盖则无需进行回表 只需两轮 round-trip 即可。更进一步 PolarDB-X 鼓励用户通过合理设计拆分键尽可能将 Join 下推。
1. Enhancing Productivity with MySQL 5.6 New Features
3. ?Block Nested-Loop and Batched Key Access Joins
PolarDB-X 让“Online DDL”更Online
HTAP 数据库“必修课” PolarDB-X Online Schema Change
每次都需要解释大量指令 使用 PolarDB-X 向量化引擎
如宝马3系和5系 PolarDB-X 与 DRDS 并驾齐驱
PolarDB-X 存储架构之“基于Paxos的最佳生产实践”
创业与投资的本质,都是追寻一种能够穿越时空,抵达未来的高效方式。 德勤管理咨...
背景 有时候我会碰到快速搭建测试服务的需求,比如像这样: 搭建一个 HTTP Servi...
1.百度是个大骗子,我抄了十几年的满分作文却从未得过满分。 2.学神在刷难题,...
1.在报名的路上,我看见远处的学校,轰!的一声没了。希望如此。 2.男:我一直...
1.某女生寝室门口贴着一个告示男生与饭盒不得入内,问何解?答曰两者都会搞大女...
3月24日,腾讯发布2020年Q4及全年财报,其中金融科技及企业服务第四季收入385亿...
基于阿里巴巴的互联网架构、大数据技术,利用混合云架构打造全新的云化电子税 务...
前言 微服务成了互联网架构的标配模式,对微服务之间的调用的流量治理和管控就尤...
本文转载自微信公众号「后端Q」,作者conan。转载本文请联系后端Q公众号。 概述 ...
作者 | 楚奕 来源 | 阿里技术公众号 这篇文章主要从技术视角介绍下跨平台WebCanv...