二元运算符具有两个关系子项。以下运算符为二元运算符:
数据库架构
此页面上的查询和执行计划基于以下数据库架构:
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");
应用联接
应用联接是 Spanner 使用的主要联接运算符。应用联接运算符执行面向行的处理,不同于执行基于集合的处理的运算符(例如哈希联接)。应用运算符有两种输入,分别为输入(左侧子项)和映射(右侧子项)。应用运算符使用应用方法(交叉、外部、半或反半)将输入端的每一行应用到映射端。此外,应用联接的变体也会出现在分布式应用的地图端。
在以下情况下,Apply 联接运算符的效率最高:
- 输入的基数较低。
- 联接键是 Map 端主键的前缀。
- 该查询联接了两个交织表。
属性和执行统计信息
运算符的属性描述了在执行运算符时使用的特征。执行统计信息是在查询执行期间收集的值,可帮助您评估运算符的性能。
属性
| 名称 | 说明 |
|---|---|
| 执行方法 | 在行执行中,运算符一次处理一行。 在批处理执行中,运算符一次处理一批行。 |
执行统计信息
| 名称 | 说明 |
|---|---|
| 延迟时间 | 运算符中所有执行操作的耗时。 |
| 累计延迟时间 | 当前运算符及其后代的总时间。 |
| CPU 时间 | 执行运算符所花费的 CPU 时间总和。 |
| 累计 CPU 时间 | 执行相应运算符及其后代所花费的总 CPU 时间。 |
| 执行时间 | 运行查询和处理结果所花费的总时间。 |
| 返回的行数 | 相应运算符输出的行数 |
| 执行任务数量 | 相应运算符的执行次数。某些执行任务可以并行运行。 |
交叉应用
交叉应用执行内联接,仅返回匹配的行。
以下查询演示了此运算符:
该查询请求每个歌手的名字,以及歌手的一首歌的歌名。
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。)然后,执行计划顶部的分布式联合运算符将来自远程服务器的所有输出行合并,并将其作为查询结果返回。
外部应用
外部应用提供左外联接语义。它会根据需要添加 NULL 填充,以确保映射端的每次执行至少返回一行。
半应用
仅当映射端出现匹配时,半应用运算符才会返回输入列。
以下查询使用半联接来查找哪些歌手有专辑:
SELECT
FirstName,
LastName
FROM
Singers
WHERE
SingerId IN (
SELECT
SingerId
FROM
Albums);
/*-----------+----------+
| FirstName | LastName |
+-----------+----------+
| Marc | Richards |
| Catalina | Smith |
| Alice | Trentor |
| Lea | Martin |
+-----------+----------*/
方案细分如下所示:

反半应用
反半连接应用运算符与半连接应用运算符类似,不同之处在于,仅当映射端未出现匹配项时,该运算符才会返回输入表列。
以下查询使用反半联接来查找没有专辑的歌手:
SELECT
FirstName,
LastName
FROM
Singers
WHERE
SingerId NOT IN (
SELECT
SingerId
FROM
Albums);
/*-----------+----------+
| FirstName | LastName |
+-----------+----------+
| David | Lomond |
+-----------+----------*/
方案细分如下所示:

哈希联接
哈希联接运算符是 SQL 连接基于哈希的实现。哈希联接执行基于集合的处理。哈希联接运算符从标记为“build”(构建)(左子级)的输入中读取行,并根据联接条件将它们插入哈希表中。然后,哈希联接运算符从标记为“probe”(探测)(右子级)的输入中读取行。对于从探测输入读取的每一行,哈希联接运算符会在哈希表中查找与之匹配的行。哈希联接运算符将匹配的行作为结果返回。
哈希联接具有以下优势:
- 不需要对输入进行排序
- 它会在构建哈希表时计算 Bloom 过滤器。该运算符使用过滤条件从探测侧排除没有匹配项的行。请注意,这是一个剩余过滤条件,而不是搜索过滤条件。
以下查询演示了此运算符:
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 的列放置在哈希表中。然后,哈希联接运算符从探测端读取所有行。对于每个探测行,哈希联接运算符会在哈希表中查找与之匹配的行。匹配结果由哈希联接运算符返回。
在返回之前,哈希表中生成的匹配项也可能会根据残余条件进行过滤。(例如,非等值联接中会出现残余条件。)由于内存管理和联接变体,哈希联接执行计划可能会很复杂。主哈希联接算法适用于处理内联接、半联接、反联接和外联接变体。
属性和执行统计信息
运算符的属性描述了在执行运算符时使用的特征。执行统计信息是在查询执行期间收集的值,可帮助您评估运算符的性能。
属性
| 名称 | 说明 |
|---|---|
| 执行方法 | 在行执行中,运算符一次处理一行。 在批处理执行中,运算符一次处理一批行。 |
执行统计信息
| 名称 | 说明 |
|---|---|
| 延迟时间 | 运算符中所有执行操作的耗时。 |
| 累计延迟时间 | 当前运算符及其后代的总时间。 |
| CPU 时间 | 执行运算符所花费的 CPU 时间总和。 |
| 累计 CPU 时间 | 执行相应运算符及其后代所花费的总 CPU 时间。 |
| 执行时间 | 运行查询和处理结果所花费的总时间。 |
| 返回的行数 | 相应运算符输出的行数 |
| 执行任务数量 | 相应运算符的执行次数。某些执行任务可以并行运行。 |
合并联接
“合并联接”运算符是基于 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 |
+-----------------------+--------------------------+
| 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 |
| Nothing To Do With Me | Not About The Guitar |
+-----------------------+--------------------------*/
执行计划如下所示:

在此执行计划中,合并联接是分布式的,以便联接在数据所在的位置执行。这也允许此示例中的合并联接在不引入额外排序运算符的情况下进行运算,因为两个表扫描都已经按 SingerId、AlbumId(即联接条件)排序。在此计划中,只要 Albums 表的 SingerId、AlbumId 小于右侧扫描的 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 | Blue |
| Total Junk | 42 |
| Total Junk | 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 |
| Green | 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 | Blue |
| Nothing To Do With Me | 42 |
| Nothing To Do With Me | Not About The Guitar |
| Play | The Second Time |
| Play | Starting Again |
| Play | Nothing Is The Same |
| Play | Let's Get Back Together |
| Play | I Knew You Were Magic |
| Play | Blue |
| Play | 42 |
| Play | Not About The Guitar |
| Terrified | Fight Story |
+-----------------------+--------------------------*/
执行计划如下所示:

在上一个执行计划中,查询优化器引入了其他排序运算符来执行合并联接。此示例查询中的 JOIN 条件仅针对 AlbumId,这不是数据的存储方式,因此必须添加排序。查询引擎支持分布式合并算法,从而允许在本地(而非全局)执行排序,这样可以分摊和并行分配 CPU 费用。
生成的匹配项也可能会根据残余条件进行过滤。例如,非等值联接中会出现残差条件。由于存在其他排序要求,因此合并联接执行计划可能会很复杂。主合并联接算法适用于处理内联接、半联接、反联接和外联接变体。
属性和执行统计信息
运算符的属性描述了在执行运算符时使用的特征。执行统计信息是在查询执行期间收集的值,可帮助您评估运算符的性能。
属性
| 名称 | 说明 |
|---|---|
| 执行方法 | 在行执行中,运算符一次处理一行。 在批处理执行中,运算符一次处理一批行。 |
执行统计信息
| 名称 | 说明 |
|---|---|
| 延迟时间 | 运算符中所有执行操作的耗时。 |
| 累计延迟时间 | 当前运算符及其后代的总时间。 |
| CPU 时间 | 执行运算符所花费的 CPU 时间总和。 |
| 累计 CPU 时间 | 执行相应运算符及其后代所花费的总 CPU 时间。 |
| 执行时间 | 运行查询和处理结果所花费的总时间。 |
| 返回的行数 | 相应运算符输出的行数 |
| 执行任务数量 | 相应运算符的执行次数。某些执行任务可以并行运行。 |
递归联合
递归联合运算符对两个输入执行联合,一个输入表示 base 情况,另一个输入表示 recursive 情况。它用于具有量化路径遍历的图表查询。基本输入会先进行处理,并且只处理一次。递归输入会一直进行处理,直到递归终止。当达到上限(如果指定)时,或者当递归未生成任何新结果时,递归会终止。在以下示例中,Collaborations 表会添加到架构中,并创建了一个名为 MusicGraph 的属性图表。
CREATE TABLE Collaborations (
SingerId INT64 NOT NULL,
FeaturingSingerId INT64 NOT NULL,
AlbumTitle STRING(MAX) NOT NULL,
) PRIMARY KEY(SingerId, FeaturingSingerId, AlbumTitle);
CREATE OR REPLACE PROPERTY GRAPH MusicGraph
NODE TABLES(
Singers
KEY(SingerId)
LABEL Singers PROPERTIES(
BirthDate,
FirstName,
LastName,
SingerId,
SingerInfo)
)
EDGE TABLES(
Collaborations AS CollabWith
KEY(SingerId, FeaturingSingerId, AlbumTitle)
SOURCE KEY(SingerId) REFERENCES Singers(SingerId)
DESTINATION KEY(FeaturingSingerId) REFERENCES Singers(SingerId)
LABEL CollabWith PROPERTIES(
AlbumTitle,
FeaturingSingerId,
SingerId),
);
以下图表查询会查找与给定歌手合作过的歌手,或是与这些合作者合作过的歌手。
GRAPH MusicGraph
MATCH (singer:Singers {singerId:42})-[c:CollabWith]->{1,2}(featured:Singers)
RETURN singer.SingerId AS singer, featured.SingerId AS featured

递归联合运算符会过滤 Singers 表,以查找具有给定 SingerId 的歌手。这是递归联合的基本输入。递归联合的递归输入包含一个用于其他查询的分布式交叉应用或其他联接运算符,该运算符会反复将 Collaborations 表与上一次联接迭代的结果进行联接。来自基本输入的行会组成第 0 次迭代。在每次迭代中,迭代的输出都会由递归假脱机扫描存储起来。递归假脱机扫描中的行会在 spoolscan.featuredSingerId = Collaborations.SingerId 时与 Collaborations 表进行联接。递归会在完成两次迭代时终止,因为这是查询中的指定上限。
属性和执行统计信息
运算符的属性描述了在执行运算符时使用的特征。执行统计信息是在查询执行期间收集的值,可帮助您评估运算符的性能。
属性
| 名称 | 说明 |
|---|---|
| 执行方法 | 在行执行中,运算符一次处理一行。 在批处理执行中,运算符一次处理一批行。 |
执行统计信息
| 名称 | 说明 |
|---|---|
| 延迟时间 | 运算符中所有执行操作的耗时。 |
| 累计延迟时间 | 当前运算符及其后代的总时间。 |
| CPU 时间 | 执行运算符所花费的 CPU 时间总和。 |
| 累计 CPU 时间 | 执行相应运算符及其后代所花费的总 CPU 时间。 |
| 执行时间 | 运行查询和处理结果所花费的总时间。 |
| 返回的行数 | 相应运算符输出的行数 |
| 执行任务数量 | 相应运算符的执行次数。某些执行任务可以并行运行。 |