简介
本页面详细介绍了 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
表中的 SongName
和 SongGenre
列创建结构体。
过滤器
过滤器运算符从其输入中读取所有行,并在每个行上应用标量谓词,然后仅返回满足谓词的行。
例如,使用以下查询:
SELECT s.LastName FROM (SELECT s.LastName
FROM Singers AS s LIMIT 3) s
WHERE s.LastName LIKE 'Rich%';
以下为查询结果:
LastName |
---|
Richards |
以下为执行计划:
姓氏以 Rich
开头的歌手的谓词是以过滤器的形式实现的。过滤器的输入是索引扫描的输出,而过滤器的输出是 LastName
以 Rich
开头的行。
为了提高性能,当过滤器直接位于扫描上方时,过滤器就会影响数据的读取方式。例如,假设一个表包含键 k
。直接位于表的扫描之上且含有谓词 k = 5
的过滤器将查找符合 k = 5
的行,而不读取整个输入。这样可以更有效地执行查询。在上面的示例中,过滤器运算符只读取满足 WHERE s.LastName LIKE 'Rich%'
谓词的行。
过滤器扫描
过滤器扫描运算符始终位于表或索引扫描之上。它与扫描配合使用以减少从数据库读取的行数,并且生成的扫描通常比使用过滤器更快。Spanner 在特定条件下应用过滤器扫描:
- 可搜索条件:如果 Spanner 可以运行,则应用可搜索条件。
确定要在表中访问的特定行。通常情况下,当过滤器位于主键的前缀上时会发生这种情况。例如,如果主键由
Col1
和Col2
组成,则包含Col1
或者包含Col1
和Col2
的显式值的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 分配运算符通过读取其输入行并向每行添加随机数字来生成输出。它与 Filter
或 Sort
运算符搭配使用,以实现采样方法。支持的采样方法有 Bernoulli 和 Reservoir。
例如,以下查询使用采样率为 10% 的 Bernoulli 采样。
SELECT s.SongName
FROM Songs AS s TABLESAMPLE BERNOULLI (10 PERCENT);
以下为查询结果:
SongName |
---|
Starting Again |
Nothing Is The Same |
请注意,因为结果是一个样本,所以即使查询相同,每次运行查询的结果也可能有所不同。
以下为执行计划:
在该执行计划中,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 |
请注意,因为结果是一个样本,所以即使查询相同,每次运行查询的结果也可能有所不同。
以下为执行计划:
在该执行计划中,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
请求 SongName
和 SongGenre
数组。
以下为查询结果:
Unspecified |
---|
[] |
[[Let's Get Back Together, COUNTRY], [Starting Again, ROCK]] |
[[Not About The Guitar, BLUES]] |
[] |
[] |
以下为执行计划:
序列化结果运算符会为 Singers
表的每一行创建一个结果,其中包含歌手歌曲的 SongName
和 SongGenre
对的数组。
排序
排序运算符读取输入行,按列对其排序,然后返回排序后的结果。
例如,使用以下查询:
SELECT s.SongGenre
FROM Songs AS s
ORDER By SongGenre;
以下为查询结果:
SongGenre |
---|
BLUES |
BLUES |
BLUES |
BLUES |
CLASSICAL |
国家/地区 |
ROCK |
ROCK |
ROCK |
以下为执行计划:
在该执行计划中,排序运算符从分布式联合运算符接收其输入行,对输入行进行排序,然后将排序后的行返回给序列化结果运算符。
为了限制返回的行数,排序运算符可以有选择地使用 LIMIT
和 OFFSET
参数。对于分布式场景,包含 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 |
以下为执行计划:
联合输入
联合输入运算符将结果返回给全部联合运算符。您可以参阅全部联合运算符,查看执行计划中联合输入运算符的示例。
二元运算符
二元运算符是具有两个关系子项的运算符。以下运算符为二元运算符:
交叉应用
交叉应用运算符对由另一个表的某个查询检索到的每一行运行表查询,并返回所有表查询运行的联合结果。不同于执行基于集合的处理的运算符(例如哈希联接),交叉应用和外部应用运算符执行面向行的处理。交叉应用运算符有两种输入,分别为输入和映射。交叉应用运算符将输入端的每一行应用到映射端。交叉应用的结果包含来自输入端和映射端的列。
例如,使用以下查询:
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
表的表扫描。
交叉应用操作的映射端包含以下内容(从上到下):
交叉应用运算符将输入端的每一行映射到映射端中具有相同 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 |
以下为执行计划:
在此执行计划中,合并联接是分布式的,以便联接在数据所在的位置执行。这也允许此示例中的合并联接在不引入额外排序运算符的情况下进行运算,因为两个表扫描都已经按 SingerId
、AlbumId
(即联接条件)排序。在此计划中,只要 Albums
表的 SingerId
、AlbumId
相对小于右侧 SongsBySingerAlbumSongNameDesc
索引扫描 SingerId_1
、AlbumId_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 |
以下为执行计划:
在上一个执行计划中,查询优化器引入了其他 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 |
以下为执行计划:
在此示例中,全部联合运算符会将其输入行组合起来,并将结果传递给序列化结果运算符。
即使子项的列名使用了不同的变量,但若每列使用相同的数据类型集,则查询也可以成功,示例如下:
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'
的分片执行表扫描。序列化结果运算符根据表扫描返回的行计算 SongName
和 SongGenre
值。然后,分布式联合运算符会返回来自远程服务器的组合结果作为 SQL 查询的结果。
分布式合并并集
分布式合并联合运算符将查询分布在多个 远程服务器。然后,它会组合查询结果以生成经过排序的结果, 称为分布式合并排序。
分布式合并联合会执行以下步骤:
根服务器向托管 分块。该子查询包含的指令 按特定顺序排序
每个远程服务器在其分块上执行子查询,然后发送结果 按请求的顺序返回
根服务器合并已排序的子查询,以生成完全排序的 结果。
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 |
以下为执行计划:
在该执行计划中,结构体构造函数出现在序列化结果运算符中。