查询执行计划

概览

本页面介绍了有关查询执行计划的概念以及 Spanner 在分布式环境中执行查询。了解 使用 Google Cloud 控制台,请参阅了解 Spanner 的执行方式 查询。您还可以查看示例历史查询计划, 特定查询随时间推移的效果变化情况如需了解详情,请参阅 采样查询计划

Spanner 使用声明式 SQL 语句来查询其数据库。 SQL 语句定义用户想要什么,而不指定如何获取 结果。查询执行计划是旨在获取结果的一系列步骤。对于指定的 SQL 语句,获取结果的方式可能有多种。Spanner 查询优化器 选择不同的执行计划,并选择其认为最高效的方案。 然后,Spanner 会使用执行计划检索结果。

从概念上讲,执行计划是关系运算符构成的树。每个运算符从其输入读取行并生成输出行。位于执行计划根部的运算符的结果会作为 SQL 查询结果返回。

以下为一个示例查询:

SELECT s.SongName FROM Songs AS s;

上述查询会生成一个查询执行计划,该计划的可视化图表如下:

示例查询执行计划

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

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 将数据划分为分片。各个分片可以彼此独立移动并被分配给不同的服务器,这些服务器可以位于不同的物理位置。为了评估分布式数据的执行计划, Spanner 基于以下各项使用执行方式:

  • 包含数据的服务器中子计划的本地执行
  • 多个远程执行的编排和聚合(通过积极的分布修剪)

Spanner 使用初始运算符 distributed union, 及其变体 distributed cross applydistributed outer apply,用于启用此模型。

采样查询计划

Spanner 抽样查询计划允许您查看 查询计划,并比较查询性能随时间变化的情况。并非所有查询 提供采样查询计划。只有占用更高 CPU 的查询才可能 数据。Spanner 查询计划样本的数据保留期限为 30 天。您可以在以下页面的查询数据分析页面中找到查询计划示例: Google Cloud 控制台有关说明,请参阅查看抽样查询计划

采样查询计划的详解与常规查询执行相同 。详细了解如何理解直观规划 调试查询,请参阅查询计划可视化工具导览

抽样查询计划的常见用例:

抽样查询计划的一些常见用例包括:

如果某个查询的效果随时间的推移存在显著差异,或者 想要提高查询性能,请参阅 SQL 最佳实践 构造优化查询语句,帮助 Spanner 找到 执行计划

查询生命周期

Spanner 中的 SQL 查询首先编译为执行计划, 则代码会发送到初始的服务器以执行。根服务器是 以尽量减少到达所查询数据的跃点数。通过 然后:

  • 启动子计划的远程执行(如有必要)
  • 等待远程执行的结果
  • 处理剩余的本地执行步骤,例如聚合结果
  • 返回查询的结果

接收子计划的远程服务器充当其子计划的“根”服务器,采用与最顶层根服务器相同的模型。其结果是一个远程执行树。从概念上讲,查询执行从上到下进行,而查询结果自下而上返回。下图展示了这种模式:

概念性查询计划

以下示例更详细地说明了这种模式。

聚合查询

聚合查询实现 GROUP BY 查询。

例如,使用以下查询:

SELECT s.SingerId, COUNT(*) AS SongCount
FROM Songs AS s
WHERE s.SingerId < 100
GROUP BY s.SingerId;

以下为查询结果:

+----------+-----------+
| SingerId | SongCount |
+----------+-----------+
|        3 |         1 |
|        2 |         8 |
+----------+-----------+

从概念上讲,以下为执行计划:

聚合查询执行计划

Spanner 将执行计划发送到根服务器, 协调查询执行并执行子计划的远程分布。

此执行计划从分布式联合开始,该运算符用于分布 子计划转发到分块满足 SingerId < 100 的远程服务器。扫描后 在各个拆分完成后,数据流聚合运算符将聚合行 来获取每个 SingerId 的计数。序列化结果运算符 对结果进行序列化处理。最后,分布式联合合并所有结果 组合在一起并返回查询结果。

如需详细了解聚合,请参阅聚合运算符

同位联接查询

交错表以物理方式与它们的相关表的行存储于同一位置。同位联接是指交错表之间的联接。与需要索引的联接或后向联接相比,同位联接具备更高的性能优势。

例如,使用以下查询:

SELECT al.AlbumTitle, so.SongName
FROM Albums AS al, Songs AS so
WHERE al.SingerId = so.SingerId AND al.AlbumId = so.AlbumId;

(此查询假定 SongsAlbums 中交错。)

以下为查询结果:

+-----------------------+--------------------------+
| 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 的分块的远程服务器。 由于 SongsAlbums 的交错表,因此每个远程服务器都能够 在每台远程服务器上执行整个子计划,而无需联接 不同的服务器

子计划包含交叉应用。每个交叉应用会执行一个表格 扫描Albums 以检索 SingerIdAlbumIdAlbumTitle。然后,交叉应用将表扫描的输出映射到输出 来自索引 SongsBySingerAlbumSongNameDesc索引扫描,受 索引中 SingerId过滤条件,与SingerId 表扫描输出。每个交叉应用会将其结果发送到序列化结果 运算符,它会对 AlbumTitleSongName 数据进行序列化并返回 并将结果提供给局部分布式联合。分布式联合聚合 返回这些结果,并将其作为查询结果返回。

索引和后向联接查询

上面的例子对两个表上使用了一个联接,两个表互相交错。当两个表(或一个表和一个索引)未交错时,执行计划会更复杂且效率更低。

请参考使用以下命令创建的索引:

CREATE INDEX SongsBySongName ON Songs(SongName)

在以下查询中使用该索引:

SELECT s.SongName, s.Duration
FROM Songs@{force_index=SongsBySongName} AS s
WHERE STARTS_WITH(s.SongName, "B");

以下为查询结果:

+----------+----------+
| SongName | Duration |
+----------+----------+
| Blue     |      238 |
+----------+----------+

以下为执行计划:

后向联接查询执行计划

生成的执行计划很复杂,因为索引 SongsBySongName 不包含 Duration 列。如需获取 Duration 值, Spanner 需要将索引结果后向联接 到表 Songs。这是一个联接,但并非位于同一位置,因为 Songs 表和 全局索引 SongsBySongName 不交错。生成的执行 比共用位置联接示例更复杂,因为 如果数据存在以下情况,Spanner 会执行优化以加快执行速度 并非位于同一地址。

顶层运算符是分布式交叉应用运算符。此输入端的 此运算符是索引 SongsBySongName 中满足 谓词 STARTS_WITH(s.SongName, "B")。分布式交叉应用 然后,将这些批次映射到分块包含 Duration 的远程服务器 数据。远程服务器使用表扫描来检索 Duration 列。 表扫描使用过滤条件 Condition:($Songs_key_TrackId' = $batched_Songs_key_TrackId),将 Songs 表中的 TrackId 联接到 从索引 SongsBySongName 进行批处理的行中的 TrackId 行。

结果会聚合为最终的查询结果。接下来,输入端包含分布式联合/局部分布式联合对的分布式交叉应用会评估索引中满足 STARTS_WITH 谓词的行。

以下是一个与上述查询略有不同(即不选择 s.Duration 列)的查询:

SELECT s.SongName
FROM Songs@{force_index=SongsBySongName} AS s
WHERE STARTS_WITH(s.SongName, "B");

此查询能够充分利用以下执行计划中所示的索引:

更简单的查询执行计划

该执行计划不需要后向联接,因为查询所需的所有列都存在于索引中。

后续步骤