查询执行运算符

简介

本页面详细介绍了 Spanner 中使用的运算符 查询执行计划。要了解如何检索 特定查询使用 Google Cloud 控制台,请参阅了解如何 Spanner 执行查询

此页面上的查询和执行计划基于以下数据库架构:

CREATE TABLE Singers (
  SingerId   INT64 NOT NULL,
  FirstName  STRING(1024),
  LastName   STRING(1024),
  SingerInfo BYTES(MAX),
  BirthDate  DATE
) PRIMARY KEY(SingerId);

CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName);

CREATE TABLE Albums (
  SingerId        INT64 NOT NULL,
  AlbumId         INT64 NOT NULL,
  AlbumTitle      STRING(MAX),
  MarketingBudget INT64
) PRIMARY KEY(SingerId, AlbumId),
  INTERLEAVE IN PARENT Singers ON DELETE CASCADE;

CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle);

CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget);

CREATE TABLE Songs (
  SingerId  INT64 NOT NULL,
  AlbumId   INT64 NOT NULL,
  TrackId   INT64 NOT NULL,
  SongName  STRING(MAX),
  Duration  INT64,
  SongGenre STRING(25)
) PRIMARY KEY(SingerId, AlbumId, TrackId),
  INTERLEAVE IN PARENT Albums ON DELETE CASCADE;

CREATE INDEX SongsBySingerAlbumSongNameDesc ON Songs(SingerId, AlbumId, SongName DESC), INTERLEAVE IN Albums;

CREATE INDEX SongsBySongName ON Songs(SongName);

CREATE TABLE Concerts (
  VenueId      INT64 NOT NULL,
  SingerId     INT64 NOT NULL,
  ConcertDate  DATE NOT NULL,
  BeginTime    TIMESTAMP,
  EndTime      TIMESTAMP,
  TicketPrices ARRAY<INT64>
) PRIMARY KEY(VenueId, SingerId, ConcertDate);

您可以使用以下数据操纵语言 (DML) 语句向这些表中添加数据:

INSERT INTO Singers (SingerId, FirstName, LastName, BirthDate)
VALUES (1, "Marc", "Richards", "1970-09-03"),
       (2, "Catalina", "Smith", "1990-08-17"),
       (3, "Alice", "Trentor", "1991-10-02"),
       (4, "Lea", "Martin", "1991-11-09"),
       (5, "David", "Lomond", "1977-01-29");

INSERT INTO Albums (SingerId, AlbumId, AlbumTitle)
VALUES (1, 1, "Total Junk"),
       (1, 2, "Go, Go, Go"),
       (2, 1, "Green"),
       (2, 2, "Forever Hold Your Peace"),
       (2, 3, "Terrified"),
       (3, 1, "Nothing To Do With Me"),
       (4, 1, "Play");

INSERT INTO Songs (SingerId, AlbumId, TrackId, SongName, Duration, SongGenre)
VALUES (2, 1, 1, "Let's Get Back Together", 182, "COUNTRY"),
       (2, 1, 2, "Starting Again", 156, "ROCK"),
       (2, 1, 3, "I Knew You Were Magic", 294, "BLUES"),
       (2, 1, 4, "42", 185, "CLASSICAL"),
       (2, 1, 5, "Blue", 238, "BLUES"),
       (2, 1, 6, "Nothing Is The Same", 303, "BLUES"),
       (2, 1, 7, "The Second Time", 255, "ROCK"),
       (2, 3, 1, "Fight Story", 194, "ROCK"),
       (3, 1, 1, "Not About The Guitar", 278, "BLUES");

叶运算符

叶运算符是指没有子运算符的运算符。叶运算符的类型有以下几种:

解除数组嵌套

“解除数组嵌套”运算符可将输入数组展平为多行元素。每个结果行最多包含两列:数组中的实际值,以及数组中从零开始计数的位置(可选)。

例如,使用以下查询:

SELECT a, b FROM UNNEST([1,2,3]) a WITH OFFSET b;

该查询会在列 a 中展平数组 [1,2,3],并在列 b 中显示数组位置。

以下为查询结果:

a b
1 0
2 1
3 2

以下为执行计划:

解除数组嵌套运算符

生成关系

生成关系运算符返回零个或更多的行。

单元关系

单元关系返回一行。它是生成关系运算符的一个特例。

例如,使用以下查询:

SELECT 1 + 2 AS Result;

生成的查询结果是:

结果
3

以下为执行计划:

单元关系运算符

空关系

空关系不返回任何行。它是生成关系运算符的一个特例。

例如,使用以下查询:

SELECT * FROM Albums LIMIT 0

生成的查询结果是:

No results

以下为执行计划:

空关系运算符

扫描

扫描运算符通过扫描行的源来返回行。以下是扫描运算符的类型:

  • 表扫描:扫描发生在表上。
  • 索引扫描:扫描发生在索引上。
  • 批量扫描:扫描发生在由其他关系运算符创建的中间表上(例如由分布式交叉应用创建的表)。

因此,Spanner 会尽可能在 扫描。应用了谓词后,扫描执行效率会更高,因为扫描不需要读取整个表或索引。谓词在执行计划中以 KeyPredicate: column=value 的形式显示。

在最坏的情况下,查询可能需要查询表中的所有行。这种情况会导致完整扫描,并在执行计划中显示为 full scan: true

例如,使用以下查询:

SELECT s.LastName
FROM singers@{FORCE_INDEX=SingersByFirstLastName} AS s
WHERE s.FirstName = 'Catalina';

以下为查询结果:

LastName
Smith

以下为执行计划:

扫描运算符

在该执行计划中,顶层分布式联合运算符将子计划发送给远程服务器。每个子计划都有一个序列化结果运算符和一个索引扫描运算符。谓词 Key Predicate: FirstName = 'Catalina' 将扫描范围限制为 FirstName 等于 Catalina 的索引 SingersByFirstLastname 中的行。索引扫描的输出将返回到序列化结果运算符。

一元运算符

一元运算符是具有单个关系子项的运算符。

以下运算符为一元运算符:

聚合

聚合运算符可实现 GROUP BY SQL 语句和聚合函数(如 COUNT)。聚合运算符的输入在逻辑上划分为按键列排列的组(如果没有 GROUP BY,则划分到一个组中)。对于每个组,计算零个或多个聚合。

例如,使用以下查询:

SELECT s.SingerId, AVG(s.duration) AS average, COUNT(*) AS count
FROM Songs AS s
GROUP BY SingerId;

该查询按 SingerId 进行分组,并执行 AVG 聚合和 COUNT 聚合。

以下为查询结果:

SingerId 平均值 计数
3 278 1
2 225.875 8

以下为执行计划:

聚合运算符

聚合运算符可以基于数据流或基于哈希。上方的执行计划展示了一个基于数据流的聚合。基于数据流的聚合从已预排序的输入(如果存在 GROUP BY)中读取数据并计算组,其过程没有阻塞。基于哈希的聚合会构建哈希表,以同时维护多个输入行的增量聚合。基于数据流的聚合比基于哈希的聚合速度更快,且使用的内存更少,但要求对输入进行排序(通过键列或二级索引)。

对于分布式场景,聚合运算符可以分为局部/全局对。每个远程服务器对其输入行执行局部聚合,然后将其结果返回到根服务器。根服务器再执行全局聚合。

应用变更

应用变更运算符将来自数据操纵语言 (DML) 语句的变更应用到表中。它是针对 DML 语句的查询计划中的顶级运算符。

例如,使用以下查询:

DELETE FROM Singers
WHERE FirstName = 'Alice';

以下为查询结果:

4 rows deleted
This statement deleted 4 rows and did not return any rows.

以下为执行计划:

应用变更运算符

创建批次

创建批次运算符将其输入行批量化为一个序列。创建批次操作通常作为分布式交叉应用操作的一部分。在创建批次期间,输入行可以重新排序。每次执行分批运算符时被分批的输入行数是可变的。

您可以参阅分布式交叉应用运算符,查看执行计划中创建批次运算符的示例。

计算

计算运算符通过读取其输入行并添加一个或多个通过标量表达式计算的附加列来生成输出。您可以参阅全部联合运算符,查看执行计划中计算运算符的示例。

计算结构体

计算结构体运算符为包含每个输入列的字段的结构体创建变量。

例如,使用以下查询:

SELECT FirstName,
       ARRAY(SELECT AS STRUCT song.SongName, song.SongGenre
             FROM Songs AS song
             WHERE song.SingerId = singer.SingerId)
FROM singers AS singer
WHERE singer.SingerId = 3;

以下为查询结果:

FirstName Unspecified
Alice [["Not About The Guitar","BLUES"]]

以下为执行计划:

计算结构体运算符

在该执行计划中,数组子查询运算符从分布式联合运算符接收输入,后者从计算结构体运算符接收输入。计算结构体运算符根据 Songs 表中的 SongNameSongGenre 列创建结构体。

过滤器

过滤器运算符从其输入中读取所有行,并在每个行上应用标量谓词,然后仅返回满足谓词的行。

例如,使用以下查询:

SELECT s.LastName FROM (SELECT s.LastName
FROM Singers AS s LIMIT 3) s
WHERE s.LastName LIKE 'Rich%';

以下为查询结果:

LastName
Richards

以下为执行计划:

过滤器运算符

姓氏以 Rich 开头的歌手的谓词是以过滤器的形式实现的。过滤器的输入是索引扫描的输出,而过滤器的输出是 LastNameRich 开头的行。

为了提高性能,当过滤器直接位于扫描上方时,过滤器就会影响数据的读取方式。例如,假设一个表包含键 k。直接位于表的扫描之上且含有谓词 k = 5 的过滤器将查找符合 k = 5 的行,而不读取整个输入。这样可以更有效地执行查询。在上面的示例中,过滤器运算符只读取满足 WHERE s.LastName LIKE 'Rich%' 谓词的行。

过滤器扫描

过滤器扫描运算符始终位于表或索引扫描之上。它与扫描配合使用以减少从数据库读取的行数,并且生成的扫描通常比使用过滤器更快。Spanner 在特定条件下应用过滤器扫描:

  • 可搜索条件:如果 Spanner 可以运行,则应用可搜索条件。 确定要在表中访问的特定行。通常情况下,当过滤器位于主键的前缀上时会发生这种情况。例如,如果主键由 Col1Col2 组成,则包含 Col1 或者包含 Col1Col2 的显式值的 WHERE 子句是可以搜索的。在这种情况下 Spanner 仅读取键范围内的数据。
  • 残差条件:Spanner 可以评估 以限制读取的数据量。

例如,使用以下查询:

SELECT LastName
FROM Singers
WHERE SingerId = 1

以下为查询结果:

LastName
Richards

以下为执行计划:

过滤器扫描运算符

限制

限制运算符限制返回的行数。可选的 OFFSET 参数指定要返回的起始行。对于分布式场景,限制运算符可以分为局部/全局对。每个远程服务器为其输出行应用局部限制,然后将其结果返回给根服务器。根服务器聚合远程服务器发送过来的行,然后应用全局限制。

例如,使用以下查询:

SELECT s.SongName
FROM Songs AS s
LIMIT 3;

以下为查询结果:

SongName
Not About The Guitar
The Second Time
Starting Again

以下为执行计划:

限制运算符

局部限制是针对每个远程服务器的限制。根服务器聚合来自远程服务器的行,然后应用全局限制。

随机 ID 分配

随机 ID 分配运算符通过读取其输入行并向每行添加随机数字来生成输出。它与 FilterSort 运算符搭配使用,以实现采样方法。支持的采样方法有 BernoulliReservoir

例如,以下查询使用采样率为 10% 的 Bernoulli 采样。

SELECT s.SongName
FROM Songs AS s TABLESAMPLE BERNOULLI (10 PERCENT);

以下为查询结果:

SongName
Starting Again
Nothing Is The Same

请注意,因为结果是一个样本,所以即使查询相同,每次运行查询的结果也可能有所不同。

以下为执行计划:

bernoulli 采样运算符

在该执行计划中,Random Id Assign 运算符从分布式联合运算符接收其输入,后者从索引扫描接收输入。运算符返回包含随机 ID 的行,Filter 运算符随后对随机 ID 应用标量谓词,并返回大约 10% 的行。

以下示例使用采样率为 2 行的 Reservoir 采样。

SELECT s.SongName
FROM Songs AS s TABLESAMPLE RESERVOIR (2 ROWS);

以下为查询结果:

SongName
I Knew You Were Magic
The Second Time

请注意,因为结果是一个样本,所以即使查询相同,每次运行查询的结果也可能有所不同。

以下为执行计划:

reservoir 采样运算符

在该执行计划中,Random Id Assign 运算符从分布式联合运算符接收其输入,后者从索引扫描接收输入。该运算符返回包含随机 ID 的行,Sort 运算符随后对随机 ID 应用排序顺序,并应用采样率为 2 行的 LIMIT

序列化结果

序列化结果运算符是计算结构体运算符的一种特殊情况,它将查询的最终结果的每一行序列化,然后返回给客户端。

例如,使用以下查询:

SELECT ARRAY(SELECT AS STRUCT so.SongName, so.SongGenre
             FROM Songs AS so
             WHERE so.SingerId = s.SingerId)
FROM Singers AS s;

该查询基于 SingerId 请求 SongNameSongGenre 数组。

以下为查询结果:

Unspecified
[]
[[Let's Get Back Together, COUNTRY], [Starting Again, ROCK]]
[[Not About The Guitar, BLUES]]
[]
[]

以下为执行计划:

序列化结果运算符

序列化结果运算符会为 Singers 表的每一行创建一个结果,其中包含歌手歌曲的 SongNameSongGenre 对的数组。

排序

排序运算符读取输入行,按列对其排序,然后返回排序后的结果。

例如,使用以下查询:

SELECT s.SongGenre
FROM Songs AS s
ORDER By SongGenre;

以下为查询结果:

SongGenre
BLUES
BLUES
BLUES
BLUES
CLASSICAL
国家/地区
ROCK
ROCK
ROCK

以下为执行计划:

排序运算符

在该执行计划中,排序运算符从分布式联合运算符接收其输入行,对输入行进行排序,然后将排序后的行返回给序列化结果运算符。

为了限制返回的行数,排序运算符可以有选择地使用 LIMITOFFSET 参数。对于分布式场景,包含 LIMIT 和/或 OFFSET 运算符的排序运算符会被分为局部/全局对。每个远程服务器都会为其输入行应用排序顺序和局部限制/偏移,然后将其结果返回给根服务器。根服务器聚合远程服务器发送过来的行,并进行排序,然后应用全局限制/偏移。

例如,使用以下查询:

SELECT s.SongGenre
FROM Songs AS s
ORDER By SongGenre
LIMIT 3;

以下为查询结果:

SongGenre
BLUES
BLUES
BLUES

以下为执行计划:

带有限制的排序运算符

该执行计划显示远程服务器的局部限制和根服务器的全局限制。

TVF

表值函数运算符通过读取输入行和 应用指定的函数。该函数可能会实现映射并返回 行数与输入相同。它还是一个生成器,可返回 行或返回较少行的过滤条件。

例如,使用以下查询:

SELECT Genre, SongName
FROM ML.PREDICT(MODEL GenreClassifier, Table Songs)

以下为查询结果:

类型 SongName
国家/地区 Not About The Guitar
摇滚乐 The Second Time
流行 Starting Again
流行 Nothing Is The Same
国家/地区 Let's Get Back Together
流行 I Knew You Were Magic
电子音乐 蓝色
摇滚乐 42
摇滚乐 Fight Story

以下为执行计划:

tvf 运营商

联合输入

联合输入运算符将结果返回给全部联合运算符。您可以参阅全部联合运算符,查看执行计划中联合输入运算符的示例。

二元运算符

二元运算符是具有两个关系子项的运算符。以下运算符为二元运算符:

交叉应用

交叉应用运算符对由另一个表的某个查询检索到的每一行运行表查询,并返回所有表查询运行的联合结果。不同于执行基于集合的处理的运算符(例如哈希联接),交叉应用和外部应用运算符执行面向行的处理。交叉应用运算符有两种输入,分别为输入和映射。交叉应用运算符将输入端的每一行应用到映射端。交叉应用的结果包含来自输入端和映射端的列。

例如,使用以下查询:

SELECT si.FirstName,
  (SELECT so.SongName
   FROM Songs AS so
   WHERE so.SingerId=si.SingerId
   LIMIT 1)
FROM Singers AS si;

该查询询问每个歌手的名字,以及歌手的一首歌的歌名。

以下为查询结果:

FirstName Unspecified
Alice Not About The Guitar
Catalina Let's Get Back Together
David NULL
Lea NULL
Marc NULL

第一列从 Singers 表填充,第二列从 Songs 表填充。如果 Singers 表中存在 SingerId,但 Songs 表中没有匹配的 SingerId,则第二列包含 NULL

以下为执行计划:

交叉应用运算符

顶层节点为分布式联合运算符。分布式联合运算符将子计划分布到远程服务器。该子计划包含一个序列化结果运算符,用于计算歌手的名字和歌手的一首歌的歌名,并序列化输出的每一行。

序列化结果运算符从交叉应用运算符接收输入。 交叉应用运算符的输入端是对 Singers 表的表扫描

交叉应用操作的映射端包含以下内容(从上到下):

  • 返回 Songs.SongName聚合运算符。
  • 将返回的歌曲数量限制为每个歌手一首歌曲的限制运算符。
  • SongsBySingerAlbumSongNameDesc 索引的索引扫描

交叉应用运算符将输入端的每一行映射到映射端中具有相同 SingerId 的行。交叉应用运算符的输出为输入行的 FirstName 值以及映射行的 SongName 值。(如果没有与 SingerId 匹配的映射行,则 SongName 值将为 NULL。)然后,执行计划顶部的分布式联合运算符将来自远程服务器的所有输出行组合起来,并将其作为查询结果返回。

哈希联接

哈希联接运算符是 SQL 连接基于哈希的实现。哈希联接执行基于集合的处理。哈希联接运算符从标记为“构建”的输入中读取行,并根据联接条件将它们插入哈希表中。然后,哈希接联运算符从标记为“探测”的输入中读取行。对于从探测输入读取的每一行,哈希联接运算符会在哈希表中查找与之匹配的行。哈希联接运算符将匹配的行作为结果返回。

例如,使用以下查询:

SELECT a.AlbumTitle, s.SongName
FROM Albums AS a JOIN@{join_method=hash_join} Songs AS s
ON a.SingerId = s.SingerId AND a.AlbumId = s.AlbumId;

以下为查询结果:

AlbumTitle SongName
Nothing To Do With Me Not About The Guitar
Green The Second Time
Green Starting Again
Green Nothing Is The Same
Green Let's Get Back Together
Green I Knew You Were Magic
Green Blue
Green 42
Terrified Fight Story

以下为执行计划:

哈希联接运算符

在该执行计划中,“构建”是一个在表 Albums 上分布扫描分布式联合运算符。“探测”是一个在 SongsBySingerAlbumSongNameDesc 索引上分布扫描的分布式联合运算符。哈希联接运算符从构建端读取所有行。每个构建行根据满足条件 a.SingerId = s.SingerId AND a.AlbumId = s.AlbumId 的列放置在哈希表中。然后,哈希联接运算符从探测端读取所有行。对于每个探测行,哈希联接运算符会在哈希表中查找与之匹配的行。匹配结果由哈希联接运算符返回。

在返回之前,哈希表中匹配的结果也可能会根据残差条件进行过滤。(例如,非等值联接中会出现残差条件。)由于内存管理和联接变体,哈希联接执行计划可能会很复杂。主哈希联接算法适用于处理内联接、半联接、反联接和外联接变体。

合并联接

“合并联接”运算符是基于 SQL 联接的合并实现。联接的两侧会生成按联接条件中使用的列进行排序的行。合并联接会以并发方式使用两个输入流,并在满足联接条件时输出行。如果输入最初未根据需要进行排序,则优化器会向方案中添加显式 Sort 运算符。

优化器不会自动选择“合并连接”。如需使用此运算符,请在查询提示上将联接方法设置为 MERGE_JOIN,如以下示例所示:

SELECT a.AlbumTitle, s.SongName
FROM Albums AS a JOIN@{join_method=merge_join} Songs AS s
ON a.SingerId = s.SingerId AND a.AlbumId = s.AlbumId;

以下为查询结果:

AlbumTitle SongName
绿色 The Second Time
Green Starting Again
Green Nothing Is The Same
Green Let's Get Back Together
Green I Knew You Were Magic
Green Blue
Green 42
Terrified Fight Story
Nothing To Do With Me Not About The Guitar

以下为执行计划:

合并联接 operator_1

在此执行计划中,合并联接是分布式的,以便联接在数据所在的位置执行。这也允许此示例中的合并联接在不引入额外排序运算符的情况下进行运算,因为两个表扫描都已经按 SingerIdAlbumId(即联接条件)排序。在此计划中,只要 Albums 表的 SingerIdAlbumId 相对小于右侧 SongsBySingerAlbumSongNameDesc 索引扫描 SingerId_1AlbumId_1 对,该表的左侧扫描就会执行。同样,只要右侧小于左侧,右侧就会执行。此合并执行会继续搜索等效项,以便返回找到的匹配项。

请考虑使用以下查询的另一个合并联接示例:

SELECT a.AlbumTitle, s.SongName
FROM Albums AS a JOIN@{join_method=merge_join} Songs AS s
ON a.AlbumId = s.AlbumId;

它会生成以下结果:

AlbumTitle SongName
Total Junk The Second Time
Total Junk Starting Again
Total Junk Nothing Is The Same
Total Junk Let\'s Get Back Together
Total Junk I Knew You Were Magic
Total Junk 蓝色
Total Junk 42
Total Junk Not About The Guitar
Green The Second Time
Green Starting Again
Green Nothing Is The Same
绿色 Let\'s Get Back Together
绿色 I Knew You Were Magic
Green Blue
Green 42
绿色 Not About The Guitar
Nothing To Do With Me The Second Time
Nothing To Do With Me Starting Again
Nothing To Do With Me Nothing Is The Same
Nothing To Do With Me Let\'s Get Back Together
Nothing To Do With Me I Knew You Were Magic
Nothing To Do With Me 蓝色
Nothing To Do With Me 42
Nothing To Do With Me Not About The Guitar
播放 The Second Time
播放 Starting Again
播放 Nothing Is The Same
播放 Let\'s Get Back Together
播放 I Knew You Were Magic
播放 蓝色
播放 42
播放 Not About The Guitar
Terrified Fight Story

以下为执行计划:

合并联接运算符 2

在上一个执行计划中,查询优化器引入了其他 Sort 运算符,以实现合并联接执行所需的属性。此示例查询中的 JOIN 条件仅针对 AlbumId,这不是数据的存储方式,因此必须添加排序。查询引擎支持分布式合并算法,从而允许在本地(而非全局)执行排序,这样可以分摊和并行分配 CPU 费用。

在返回之前,找到的匹配项也可能会根据残差条件进行过滤。(例如,非等值联接中会出现残差条件。)由于存在其他排序要求,因此合并联接执行计划可能会很复杂。主合并联接算法适用于处理内联接、半联接、反联接和外联接变体。

推送广播哈希联接

推送广播哈希联接运算符是基于分布式哈希联接的 SQL 联接实现。推送广播哈希联接运算符从输入端读取行,以构建一批数据。然后,该批数据被广播到包含映射端数据的所有服务器。在接收该批数据的目标服务器上,系统使用该批数据作为构建端数据来构建哈希联接,然后将本地数据作为哈希联接的探测端进行扫描。

优化器不会自动选择推送广播哈希联接。如需使用此运算符,请在查询提示上将联接方法设置为 PUSH_BROADCAST_HASH_JOIN,如以下示例所示:

SELECT a.AlbumTitle, s.SongName
FROM Albums AS a JOIN@{join_method=push_broadcast_hash_join} Songs AS s
ON a.SingerId = s.SingerId AND a.AlbumId = s.AlbumId;

以下为查询结果:

AlbumTitle SongName
绿色 The Second Time
Green Starting Again
Green Nothing Is The Same
绿色 Lets Get Back Together
绿色 I Knew You Were Magic
Green Blue
Green 42
Terrified Fight Story
Nothing To Do With Me Not About The Guitar

以下为执行计划:

推送广播哈希联接运算符

推送广播哈希联接的输入是 AlbumsByAlbumTitle 索引。该输入会序列化为一批数据。然后,该批次会被发送到索引 SongsBySingerAlbumSongNameDesc 的所有本地分片,随后该批次会被反序列化并构建到哈希表中。 然后,哈希表会将本地索引数据用作返回匹配结果的探测。

在返回之前,找到的匹配项也可能会根据残差条件进行过滤。(例如,非等值联接中会出现残差条件。)

外部应用

外部应用运算符与交叉应用运算符相似,不同之处在于:外部应用运算符会根据需要生成一个用 NULL 填充的行,以确保映射端的每次执行至少返回一行。(换句话说,它提供了左外联接语义。)

N 元运算符

N 元运算符是具有两个以上关系子项的运算符。 以下运算符为 N 元运算符:

全部联合

全部联合运算符将其子项的所有行集组合在一起,而不移除重复项。全部联合运算符从分布在多个服务器上的联合输入运算符接收其输入。全部联合运算符要求其输入具有相同的架构,即每一列具有相同的数据类型集。

例如,使用以下查询:

SELECT 1 a, 2 b
UNION ALL
SELECT 3 a, 4 b
UNION ALL
SELECT 5 a, 6 b;

子项的行类型由两个整数组成。

以下为查询结果:

a b
1 2
3 4
5 6

以下为执行计划:

union_all_operator

在此示例中,全部联合运算符会将其输入行组合起来,并将结果传递给序列化结果运算符。

即使子项的列名使用了不同的变量,但若每列使用相同的数据类型集,则查询也可以成功,示例如下:

SELECT 1 a, 2 b
UNION ALL
SELECT 3 c, 4 e;

若子项的列使用不同的数据类型,则查询会失败,示例如下:

SELECT 1 a, 2 b
UNION ALL
SELECT 3 a, 'This is a string' b;

标量子查询

标量子查询是一个 SQL 子表达式,它是标量表达式的一部分。Spanner 在任何情况下都会尝试移除标量子查询, 但在某些情况下,计划可以显式包含标量子查询。

例如,使用以下查询:

SELECT FirstName,
IF(FirstName='Alice',
   (SELECT COUNT(*)
    FROM Songs
    WHERE Duration > 300),
   0)
FROM Singers;

以下为 SQL 子表达式:

SELECT COUNT(*)
FROM Songs
WHERE Duration > 300;

以下为完整查询的结果:

FirstName
Alice 1
Catalina 0
David 0
Lea 0
Marc 0

以下为执行计划:

标量子查询运算符

该执行计划包含一个标量子查询(显示为标量子查询,位于聚合运算符上方)。

Spanner 有时会将标量子查询转换为另一个运算符, 作为联接或交叉应用,以便提升效果。

例如,使用以下查询:

SELECT *
FROM Songs
WHERE Duration = (SELECT MAX(Duration) FROM Songs);

以下为 SQL 子表达式:

SELECT MAX(Duration) FROM Songs;

以下为完整查询的结果:

SingerId AlbumId TrackId SongName Duration SongGenre
2 1 6 Nothing Is The Same 303 BLUES

以下为执行计划:

计划中不显示标量子查询运算符

该执行计划不包含标量子查询,因为 Spanner 将标量子查询转换为交叉应用。

数组子查询

数组子查询与标量子查询类似,区别在于数组子查询可以使用多个输入行。使用的行被转换为单个标量输出数组,其中每个所使用的输入行都对应一个元素。

例如,使用以下查询:

SELECT a.AlbumId,
ARRAY(SELECT ConcertDate
      FROM Concerts
      WHERE Concerts.SingerId = a.SingerId)
FROM Albums AS a;

以下为子查询:

SELECT ConcertDate
FROM Concerts
WHERE Concerts.SingerId = a.SingerId;

每个 AlbumId 的子查询结果将转换为与该 AlbumId 相对应的 ConcertDate 行的数组。该执行计划包含一个数组子查询(显示为数组子查询,位于分布式联合运算符上方):

数组子查询运算符

分布式运算符

本页面前述的运算符均在单台机器的边界内执行操作。分布式运算符可以跨多个服务器执行操作。

以下运算符为分布式运算符:

分布式联合运算符是派生出分布式交叉应用和分布式外部应用运算符的初始运算符。

在执行计划中,如果一个或多个局部分布式联合变体之上存在一个分布式联合变体,则该计划中会有分布式运算符。分布式联合变体执行子计划的远程分布。局部分布式联合变体位于为查询执行的每个扫描之上,如以下执行计划所示:

分布式运算符

局部分布式联合变体动态更改分片边界,以便在发生重启时也能稳定执行查询。

分布式联合变体会尽可能包含一个引起分片修剪的分片谓词,这意味着远程服务器仅对满足谓词的分片执行子计划。这样能够缩短延迟时间,并提高整体查询性能。

分布式联合

分布式联合运算符在概念上将一个或多个表分成多个分片,在每个分片上独立地远程计算子查询,然后联合所有结果。

例如,使用以下查询:

SELECT s.SongName, s.SongGenre
FROM Songs AS s
WHERE s.SingerId = 2 AND s.SongGenre = 'ROCK';

以下为查询结果:

SongName SongGenre
Starting Again ROCK
The Second Time ROCK
Fight Story ROCK

以下为执行计划:

分布式联合运算符

该分布式联合运算符向远程服务器发送子计划,远程服务器会对满足查询谓词 WHERE s.SingerId = 2 AND s.SongGenre = 'ROCK' 的分片执行表扫描序列化结果运算符根据表扫描返回的行计算 SongNameSongGenre 值。然后,分布式联合运算符会返回来自远程服务器的组合结果作为 SQL 查询的结果。

分布式合并并集

分布式合并联合运算符将查询分布在多个 远程服务器。然后,它会组合查询结果以生成经过排序的结果, 称为分布式合并排序

分布式合并联合会执行以下步骤:

  1. 根服务器向托管 分块。该子查询包含的指令 按特定顺序排序

  2. 每个远程服务器在其分块上执行子查询,然后发送结果 按请求的顺序返回

  3. 根服务器合并已排序的子查询,以生成完全排序的 结果。

Spanner 版本 3 默认启用分布式合并联合 之后的版本。

分布式交叉应用

分布式交叉应用 (DCA) 运算符通过在多个服务器上执行操作扩展了交叉应用运算符。DCA 输入端会对批处理行进行分组(该运算符与常规的交叉应用运算符不同,后者一次只对一个输入行起作用)。DCA 映射端是一组在远程服务器上执行操作的交叉应用运算符。

例如,使用以下查询:

SELECT AlbumTitle FROM Songs
JOIN Albums ON Albums.AlbumId=Songs.AlbumId;

结果的格式如下:

AlbumTitle
Green
Nothing To Do With Me
Play
Total Junk
Green

以下为执行计划:

分布式交叉应用运算符

DCA 输入包含对 SongsBySingerAlbumSongNameDesc 索引进行的索引扫描,该索引会批处理 AlbumId 的行。此交叉应用运算符的映射端是对索引 AlbumsByAlbumTitle 的索引扫描,受与 AlbumsByAlbumTitle 索引中的 AlbumId 键匹配的输入行中 AlbumId 谓词的限制。映射会返回与批处理输入行中的 SingerId 值相对应的 SongName

此示例的 DCA 过程总结如下:DCA 的输入是来自 Albums 表的批处理行,而 DCA 的输出是将这些行应用于索引扫描的映射。

分布式外部应用

分布式外部应用运算符通过在多个服务器上执行操作扩展了外部应用运算符,其方式与分布式交叉应用运算符扩展交叉应用运算符类似。

例如,使用以下查询:

SELECT LastName, ConcertDate FROM Singers
LEFT OUTER JOIN@{JOIN_TYPE=APPLY_JOIN} Concerts
ON Singers.SingerId=Concerts.SingerId;

结果的格式如下:

姓氏 ConcertDate
Trentor 2014-02-18
Smith 2011-09-03
Smith 2010-06-06
Lomond 2005-04-30
Martin 2015-11-04
Richards

以下为执行计划:

分布式外部应用运算符

应用变更

应用变更运算符将来自数据操纵语言 (DML) 语句的变更应用到表中。它是针对 DML 语句的查询计划中的顶级运算符。

例如,使用以下查询:

DELETE FROM Singers
WHERE FirstName = 'Alice';

以下为查询结果:

4 rows deleted
This statement deleted 4 rows and did not return any rows.

以下为执行计划:

应用变更运算符

其他信息

本节介绍的内容不是独立运算符,而是支持上面列出的一个或多个运算符所需执行的任务。此处描述的内容在技术上来说是运算符,但它们在您的查询计划中不是独立运算符。

结构体构造函数

结构体构造函数用于创建一个结构体或一个字段集合。它通常会为计算操作产生的行创建一个结构体。结构体构造函数不是独立运算符。相反,它出现在计算结构体运算符或序列化结果运算符中。

对于计算结构体操作,结构体构造函数会创建一个结构体,以便所计算行的列能够对该结构体进行单个变量引用。

对于序列化结果操作,结构体构造函数会创建一个结构体来序列化结果。

例如,使用以下查询:

SELECT IF(TRUE, struct(1 AS A, 1 AS B), struct(2 AS A , 2 AS B)).A;

以下为查询结果:

A
1

以下为执行计划:

结构体构造函数

在该执行计划中,结构体构造函数出现在序列化结果运算符中。