本頁面詳細說明 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");
Leaf 運算子
「leaf」運算子是沒有子項的運算子。leaf 運算子的類型如下:
Array unnest
「array unnest」運算子會將輸入陣列整併到元素資料列中。每個結果資料列最多包含兩個資料欄:來自陣列的實際值和/或陣列中以零為基礎的位置。
例如使用這個查詢:
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 |
以下為執行計畫:
Generate relation
「generate relation」運算子會傳回零或多個資料列。
Unit relation
「unit relation」會傳回一個資料列。這是「generate relation」運算子的特殊案例。
例如使用這個查詢:
SELECT 1 + 2 AS Result;
結果會是:
| Result |
|---|
| 3 |
以下為執行計畫:
Empty relation
「empty relation」不會傳回資料列。這是「generate relation」運算子的特殊案例。
例如使用這個查詢:
SELECT *
FROM albums
LIMIT 0
結果會是:
沒有相符的結果
以下為執行計畫:
掃描
「scan」運算子會藉由掃描資料列來源的方式傳回資料列。scan 運算子的類型如下:
- Table scan:掃描會在資料表上進行。
- Index scan:掃描會在索引上進行。
- 批次掃描:掃描會在其他關聯運算子建立的中繼資料表 (例如由分散式 cross apply 所建立的資料表) 上進行。
Spanner 會盡可能在索引鍵上套用述詞,做為掃描的一部分。套用了述詞的掃描能更有地效率執行,因為掃描不需要讀取整個資料表或索引。述詞在執行計畫中的形式為 KeyPredicate: column=value。
最糟的情況是,查詢可能需要查閱資料表中的所有資料列。這種情況會導致完整掃描,並在執行計畫中顯示為 full scan:
true。
例如使用這個查詢:
SELECT s.lastname
FROM singers@{FORCE_INDEX=SingersByFirstLastName} as s
WHERE s.firstname = 'Catalina';
結果會是:
| LastName |
|---|
| Smith |
以下為執行計畫:
在執行計畫中,頂層的 distributed union 運算子會傳送子計畫到遠端伺服器。每個子計畫都有一個 serialize result 運算子和一個 index scan 運算子。述詞 Key Predicate: FirstName = 'Catalina' 會將掃描限制在索引 SingersByFirstLastname 中,FirstName 等於 Catalina 的資料列。index scan 的輸出會傳回 serialize result 運算子。
Unary 運算子
「unary」運算子是包含單一相關子項的運算子。
以下運算子屬於 unary 運算子:
- 匯總
- 套用變異
- 建立批次
- 運算
- 運算結構體
- DataBlockToRowAdapter
- 篩選器
- 篩選掃描結果
- 限制
- 本機分割聯集
- 隨機指派 ID
- RowToDataBlockAdapter
- Serialize result
- 排序
- TVF
- 聯集輸入
匯總
「aggregate」運算子會實作 GROUP BY SQL 陳述式並匯總功能 (例如 COUNT)。aggregate 運算子的輸入會以索引鍵資料欄有邏輯地分區為多個群組 (若沒有 GROUP BY,則為單一群組)。針對每個群組,系統會運算零或多個匯總。
例如使用這個查詢:
SELECT s.singerid,
Avg(s.duration) AS average,
Count(*) AS count
FROM songs AS s
GROUP BY singerid;
查詢會以 SingerId 分組,並且執行 AVG 匯總與 COUNT 匯總。
結果會是:
| SingerId | average | count |
|---|---|---|
| 3 | 278 | 1 |
| 2 | 225.875 | 8 |
以下為執行計畫:
Aggregate 運算子分成「串流型」或「雜湊型」兩類。先前的執行計畫顯示的是串流型匯總。串流型匯總會讀取預先排序的輸入 (若有 GROUP BY),並在未封鎖的情況下運算群組。雜湊型匯總會建立雜湊表,以同時維護多個輸入列的遞增匯總。雖然與雜湊型匯總相比,串流型匯總的執行速度更快,也消耗較少的記憶體,但是串流型匯總需要使用經過排序的輸入 (依索引鍵欄或次要索引排序)。
對於分散的情境而言,aggregate 運算子會分成本機/全域組。每個遠端伺服器會在其輸入列執行本機匯總,然後將結果傳回根伺服器,讓根伺服器執行全域匯總。
Apply mutations
「apply mutations」運算子將來自於資料操縱陳述式 (DML) 的變異套用到資料表。這是 DML 陳述式查詢計畫的頂層運算子。
例如使用這個查詢:
DELETE FROM singers
WHERE firstname = 'Alice';
結果會是:
4 rows deleted This statement deleted 4 rows and did not return any rows.
以下為執行計畫:
建立批次
「create batch」運算子會將輸入列分批,做成序列。建立批次作業通常是分散式 cross apply 作業的一部分。輸入列可以在批次作業期間重新排序。每次執行批次運算子,進行批次的輸入列數量會有所不同。
請參閱分散式 cross apply 運算子,取得執行計畫中 create batch 運算子的範例。
運算
「compute」運算子會讀取輸入列,並新增一或多個透過純量陳述式運算的額外資料欄來產生輸出。請參閱 union all 運算子,瞭解執行計畫中 compute 運算子的範例。
Compute struct
「compute struct」運算子會為結構建立變數,包含每個輸入資料欄的欄位。
例如使用這個查詢:
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"]] |
以下為執行計畫:
在執行計畫中,array subquery 運算子會從 distributed union 運算子接收輸入,而該運算子會從 compute struct 運算子接收輸入。compute struct 運算子會從 Songs 資料表中的 SongName 和 SongGenre 資料欄建立結構。
DataBlockToRowAdapter
Spanner 查詢最佳化工具會在以不同執行方法運作的一對運算子之間,自動插入 DataBlockToRowAdapter 運算子。其輸入內容是使用批次導向執行方法的運算子,輸出內容則會饋送至以列導向執行方法執行的運算子。詳情請參閱「最佳化查詢執行作業」。
篩選器
「filter」運算子會讀取輸入的所有資料列,在每一列上套用純量述詞,然後只傳回滿足述詞的資料列。
例如使用這個查詢:
SELECT s.lastname
FROM (SELECT s.lastname
FROM singers AS s
LIMIT 3) s
WHERE s.lastname LIKE 'Rich%';
結果會是:
| LastName |
|---|
| Richards |
以下為執行計畫:
我們將以 Rich 開頭的歌手姓氏的述詞做為篩選器。篩選器的輸入是 index scan 的輸出,而篩選的輸出是 LastName 的開頭為 Rich 的資料列。
為了獲致效能,每當 filter 直接位於 scan 上方時,篩選器會影響資料讀取的方式。舉例來說,假設有一個包含索引鍵 k 的資料表。當包含述詞 k = 5 的 filter 直接位於 table scan 的上方,filter 會尋找符合 k = 5 的資料列,而不會讀取整個輸入。這讓執行查詢更有效率。在上一個範例中,filter 運算子只會讀取滿足 WHERE s.LastName LIKE 'Rich%' 述詞的資料列。
Filter scan
「filter scan」運算子總是位於「table scan」或「index scan」的上方。它會與掃描作業搭配使用,減少從資料庫讀取的資料列數,而這樣的掃描通常會比使用 filter 更快。在某些條件下,Spanner 會套用篩選掃描:
- 可搜尋的條件:若 Spanner 可決定要存取資料表中的哪個資料列,便適用可搜尋的條件。一般而言,這會在篩選條件位於主鍵的前置字串時發生。舉例來說,如果主鍵包含
Col1和Col2,那麼包含明確的Col1值的WHERE子句或Col1和Col2便是可搜尋的。在該情況下,Spanner 只會讀取索引鍵範圍內的值。 - 其餘條件:其他 Spanner 可評估掃描,以限制讀取資料量的條件。
例如使用這個查詢:
SELECT lastname
FROM singers
WHERE singerid = 1
結果會是:
| LastName |
|---|
| Richards |
以下為執行計畫:
限制
「limit」運算子會限制傳回的資料列數。選擇性的 OFFSET 參數會指定要傳回的起始列。在分散的情境中,系統可將 limit 運算子分隔成本機/全域組。每個遠端伺服器會套用輸出資料列的本機限制,然後將結果傳回根伺服器。根伺服器會匯總遠端伺服器傳送的資料列,然後套用全域限制。
例如使用這個查詢:
SELECT s.songname
FROM songs AS s
LIMIT 3;
結果會是:
| SongName |
|---|
| Not About The Guitar |
| The Second Time |
| Starting Again |
以下為執行計畫:
每個遠端伺服器的限制都會採用本機限制。根伺服器會從遠端伺服器匯總資料列,然後套用全域限制。
指派隨機 ID
「random ID assign」運算子會讀取輸入列,並在每個資料列中新增隨機數字,藉此產生輸出。可搭配 Filter 或 Sort 運算子,支援的取樣方法為 Bernoulli 和 Reservoir。
舉例來說,下列查詢使用 Bernoulli 取樣,取樣率為 10%。
SELECT s.songname
FROM songs AS s TABLESAMPLE bernoulli (10 PERCENT);
結果會是:
| SongName |
|---|
| Starting Again |
| Nothing Is The Same |
請注意,由於結果是樣本,即使查詢相同,每次執行查詢的結果可能都會不同。
以下為執行計畫:
在此執行計畫中,Random Id Assign 運算子會從 distributed union 運算子接收輸入,而該運算子會從 index scan 接收輸入。運算子會傳回含有隨機 ID 的資料列,然後 Filter 運算子會對隨機 ID 套用純量述詞,並傳回約 10% 的資料列。
以下範例使用 Reservoir
取樣率為 2 列的取樣。
SELECT s.songname
FROM songs AS s TABLESAMPLE reservoir (2 rows);
結果會是:
| SongName |
|---|
| I Knew You Were Magic |
| The Second Time |
請注意,由於結果是樣本,即使查詢相同,每次執行查詢的結果可能都會不同。
以下為執行計畫:
在此執行計畫中,Random Id Assign 運算子會從 distributed union 運算子接收輸入,而該運算子會從 index scan 接收輸入。運算子會傳回含有隨機 ID 的資料列,然後 Sort 運算子會對隨機 ID 套用排序順序,並套用 LIMIT (2 個資料列)。
本機分割聯集
「local split union」運算子會找出儲存在本機伺服器的資料表分割,對每個分割執行子查詢,然後建立聯集來合併所有結果。
執行計畫會掃描位置資料表,並顯示本機分割聯集。放置位置可以增加資料表中的分割數,讓您根據實體儲存位置,更有效率地批次掃描分割。
舉例來說,假設 Singers 資料表使用放置位置鍵來分割藝人資料:
CREATE TABLE Singers (
SingerId INT64 NOT NULL,
SingerName STRING(MAX) NOT NULL,
...
Location STRING(MAX) NOT NULL PLACEMENT KEY
) PRIMARY KEY (SingerId);
現在,請思考這項查詢:
SELECT BirthDate FROM Singers;
以下為執行計畫:
分散式聯集會將子查詢傳送至每個批次的分割,這些分割會實際儲存在同一部伺服器中。在每個伺服器上,本機分割聯集會找出儲存 Singers 資料的分割,在每個分割上執行子查詢,然後傳回合併結果。這樣一來,分散式聯集和本機分割聯集就能共同運作,有效率地掃描 Singers 資料表。如果沒有本機分割聯集,distributed union 會針對每個分割傳送一個 RPC,而不是每個分割批次傳送一個 RPC,因此當每個批次有多個分割時,會導致多餘的 RPC 往返。
RowToDataBlockAdapter
Spanner 查詢最佳化工具會在以不同執行方法運作的一對運算子之間,自動插入 RowToDataBlockAdapter 運算子。輸入內容是使用以列為導向的執行方法,輸出內容則會饋送至以批次為導向的執行方法中。詳情請參閱「最佳化查詢執行作業」。
Serialize result
「serialize result」運算子是 compute struct 運算子的特殊案例,它會序列化查詢最終結果的每個資料列,以傳回用戶端。
例如使用這個查詢:
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]] |
| [] |
| [] |
以下為執行計畫:
serialize result 運算子會為 Singers 資料表的每個資料列建立結果,其中包含歌手歌曲的 SongName 和 SongGenre 配對陣列。
排序
「sort」運算子會讀取輸入列、依資料欄排序資料列,然後傳回排序的結果。
例如使用這個查詢:
SELECT s.songgenre
FROM songs AS s
ORDER BY songgenre;
結果會是:
| SongGenre |
|---|
| BLUES |
| BLUES |
| BLUES |
| BLUES |
| 古典 |
| 國家/地區 |
| ROCK |
| ROCK |
| ROCK |
以下為執行計畫:
在此執行計畫中,sort 運算子會從 distributed union 運算子接收輸入列、將輸入列排序,然後將已排序的資料列傳回 serialize result 運算子。
若要限制傳回的資料列數,可選擇在 sort 運算子包含 LIMIT 和 OFFSET 參數。針對分散式的情境,系統會將具備 LIMIT 或 OFFSET 的 sort 運算子分成本機/全域組。每個遠端伺服器都會套用輸入列的排序順序和本機限制/偏移,然後將結果傳回根伺服器。根伺服器會匯總遠端伺服器傳送的資料列,在排序後套用全域限制/偏移。
例如使用這個查詢:
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 |
以下為執行計畫:
Union input
「union input」運算子會將結果傳回「union all」運算子。如需在執行計畫中使用 union input 運算子的範例,請參閱 union all 運算子。
Binary 運算子
「binary」運算子是包含兩個關聯子項的運算子。以下運算子屬於二進位運算子:
Cross apply
「cross apply」運算子會對在查詢其他資料表後所擷取的每個資料列執行資料表查詢,並傳回所有資料表查詢的聯集。Cross apply 和 outer apply 運算子會執行資料列導向的處理,不像其他執行以組合為基礎的處理程序的運算子 (如 hash join)。cross apply 運算子包含兩種輸入:input 和 map。cross apply 運算子會將輸入端中的每個資料列套用到對應端。cross apply 的結果包含輸入端和對應端的資料欄。
例如使用這個查詢:
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 | 空值 |
第一欄是從 Singers 資料表填入,第二欄則是從 Songs 資料表填入。當 SingerId 存在於 Singers 資料表,但 Songs 資料表中沒有對應的 SingerId 時,第二欄會包含 NULL。
以下為執行計畫:
頂層節點的運算子是 distributed union 運算子。distributed union 運算子會將子計畫分配到遠端伺服器。子計畫包含 serialize result 運算子,會運算歌手的名字和該歌手其中一首歌曲的名稱,並且針對輸出的每一個資料列進行序列化。
serialize result 運算子會從 cross apply 運算子接受輸入。cross apply 運算子的輸入端是在 Singers 資料表上的 table scan。
cross apply 運算的對應端包含以下內容 (從上至下):
- aggregate 運算子,傳回
Songs.SongName。 - limit 運算子,將傳回的歌曲數量限制為每位歌手一首。
- 在
SongsBySingerAlbumSongNameDesc索引上進行 index scan。
cross apply 運算子會將輸入端的每個資料列,對應到對應端中具有相同 SingerId 的資料列。cross apply 運算子輸出是輸入列的 FirstName 值,以及來自對應列的 SongName 值。
(如果沒有對應到 SingerId 的對應列,SongName 值將是 NULL。) 接著,位於執行計畫頂層的 distributed union 運算子會結合所有來自遠端伺服器的輸出列,做為查詢結果傳回。
Hash join
「hash join」運算子是 SQL join 的雜湊實作。Hash join 執行以組合為基礎的處理程序。hash join 運算子會從標示為 build 的輸入讀取資料列,並根據 join 條件插入雜湊表。hash join 運算子接著會從標示為 probe 的輸入讀取資料列。針對每個從 probe 輸入讀取的資料列,hash join 運算子會在雜湊表中查詢相符的資料列。hash join 運算子會傳回相符的資料列做為結果。
例如使用這個查詢:
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 |
| 綠色 | Let's Get Back Together |
| 綠色 | I Knew You Were Magic |
| Green | Blue |
| Green | 42 |
| Terrified | Fight Story |
以下為執行計畫:
在執行計畫中,「build」是分散式聯集,會將掃描分配到 Albums 資料表。「Probe」是分散式聯集運算子,可將掃描分配到 SongsBySingerAlbumSongNameDesc 索引上。hash join 運算子會讀取所有來自建置端的資料列。系統會根據條件 a.SingerId =
s.SingerId AND a.AlbumId = s.AlbumId 中的資料欄,將每個建構資料列放入雜湊表。接著 hash join 運算子會從探測端讀取所有資料列。針對每個探測資料列,hash join 運算子會查詢雜湊表中相符的內容。hash join 運算子會傳回符合的結果。
雜湊表中相符的結果在傳回前,可能會由剩餘條件篩選。(剩餘條件出現的位置可能會在非相等的聯結)。由於記憶體管理和聯結變數,Hash join 執行計畫可能會很複雜。主要的 hash join 演算法會經過調整,以處理內部變數、半變數、反變數和外部聯結變數。
合併彙整
「merge join」運算子是 SQL join 的合併實作。聯結的兩側都會產生資料列,並依聯結條件中使用的資料欄排序。合併聯結會同時耗用兩個輸入串流,並在滿足聯結條件時輸出資料列。如果輸入內容原本並未依規定排序,最佳化工具就會在方案中加入明確的 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 |
| 綠色 | Let's Get Back Together |
| 綠色 | I Knew You Were Magic |
| Green | Blue |
| Green | 42 |
| Terrified | Fight Story |
| Nothing To Do With Me | Not About The Guitar |
以下為執行計畫:
在這個執行計畫中,合併聯結會分散,以便在資料所在位置執行聯結。此外,由於兩個資料表掃描作業都已依 SingerId 和 AlbumId 排序 (這是聯結條件),因此這個範例中的合併聯結作業不需要導入額外的排序運算子。在此計畫中,每當左側 Albums 表格的掃描比較小於右側 SongsBySingerAlbumSongNameDesc 索引掃描 SingerId_1、AlbumId_1 配對時,就會前進。SingerIdAlbumId同樣地,如果右側小於左側,右側就會前進。這項合併作業會繼續搜尋同義字,以便傳回相符結果。
請參考以下查詢,瞭解另一個合併聯結範例:
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 成本。
相符的結果在傳回前,可能會由剩餘條件篩選。(剩餘條件出現的位置可能會在非相等的聯結)。由於額外的排序需求,Merge join 執行計畫可能會很複雜。主要的合併聯結演算法會經過調整,以處理內部、半、反和外部聯結變數。
推送廣播雜湊聯結
「push broadcast hash join」運算子是 SQL join 的分散式雜湊聯結實作。push broadcast hash join 運算子會從輸入端讀取資料列,以建構資料批次。然後,該批次會廣播至所有包含地圖端資料的伺服器。在接收批次資料的目的地伺服器上,系統會使用批次資料做為建構端資料,並掃描本機資料做為雜湊聯結的探查端,藉此建構雜湊聯結。
最佳化工具不會自動選取 Push broadcast hash join。如要使用這個運算子,請在查詢提示中將聯結方法設為 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 |
以下為執行計畫:
Push 廣播雜湊聯結的輸入內容為 AlbumsByAlbumTitle 索引。該輸入內容會序列化為一批資料。然後,該批次會傳送至索引的所有本機分割 SongsBySingerAlbumSongNameDesc,接著批次會還原序列化並建構為雜湊表。雜湊表隨後會使用本機索引資料做為探測器,傳回相符的結果。
相符的結果在傳回前,可能會由剩餘條件篩選。(剩餘條件出現的位置可能會在非相等的聯結)。
Outer apply
「outer apply」運算子與 cross apply 運算子類似,但 outer apply 運算子會在必要時製造以 NULL 填補的資料列,藉此確保對應端每次執行都會傳回至少一個資料列。(換言之,它會提供 left outer join 語意)。
遞迴聯集
「recursive union」運算子會對兩個輸入執行聯集,一個代表 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 資料表與先前聯結疊代的結果。基礎輸入中的資料列會形成第零次疊代。在每次疊代時,疊代的輸出內容會由遞迴緩衝區掃描儲存。遞迴假脫機掃描中的資料列會與 Collaborations 上的資料表在 spoolscan.featuredSingerId = Collaborations.SingerId 上彙整。查詢中指定的上限為 2,因此完成兩次疊代後,遞迴就會終止。
N-ary 運算子
「N-ary」運算子是包含兩個以上兩個關聯子項的運算子。 以下運算子屬於 N-ary 運算子:
Union all
「union all」運算子會結合所有子項的資料列組合,且不會移除重複項目。Union all 運算子從分散在多個伺服器上的 union input 運算子接收輸入。union all 運算子要求其輸入使用相同結構定義,亦即每個資料欄都有相同的資料類型組。
例如使用這個查詢:
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 運算子會整合其輸入列,在此例中它會傳送結果到 serialize result 運算子。
由於每個資料欄中皆使用相同的資料類型組合,即使子項使用不同的變數做為資料欄名稱,下列查詢仍會成功:
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;
Scalar subquery
「scalar subquery」屬於 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 |
以下為執行計畫:
執行計畫包含一個純量子查詢,以 Scalar Subquery 顯示在 aggregate 運算子上方。
Spanner 有時會將純量子查詢轉換成另一個運算子,如 join 或 cross apply,以改善效能。
例如使用這個查詢:
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 已將純量子查詢轉換為 cross apply。
Array subquery
「array subquery」與 scalar subquery 類似,但 array subquery 可耗用一個以上的輸入列。耗用的資料列會轉換成一個純量輸出陣列,其中每個耗用的輸入列都包含一個元素。
例如使用這個查詢:
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 資料列陣列。執行計畫包含陣列子查詢,顯示為 Array Subquery,位於 distributed union 運算子上方:
分散式運算子
本頁稍早描述的運算子僅會在單一機器上執行。「分散式運算子」則會在多個伺服器中執行。
以下運算子屬於分散式運算子:
distributed union 運算子是從 distributed cross apply 和 distributed outer apply 擷取而來的原始運算子
執行計畫中的分散式運算子在一或多個本機 distributed union 變數的上方,會有一個 distributed union 變數。distributed union 變數會執行子計畫的遠端分配作業。本機 distributed union 變數會位於查詢所執行的每個掃描的上方,如以下執行計畫所示:
當動態變更的分割界線重新啟動時,本機 distributed union 變數可確保查詢執行的穩定性。
distributed union 變數會儘可能包含可產生分割修剪的分割述詞,也就是遠端伺服器只會在滿足述詞的分割上執行子計畫。這會改善延遲時間和整體查詢效能。
Distributed union
「distributed union」運算子在概念上將一或多個資料表分到多個分割,並在每個分割上個別遠端評估子查詢,然後聯集所有結果。
例如使用這個查詢:
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 |
以下為執行計畫:
distributed union 運算子會傳送子計畫到遠端伺服器,跨分割執行資料表掃描以滿足查詢的述詞 WHERE
s.SingerId = 2 AND s.SongGenre = 'ROCK'。「serialize result」運算子會從資料表掃描傳回的資料列運算 SongName 和 SongGenre 值。接著,distributed union 運算子便會傳回遠端伺服器的綜合結果,做為 SQL 查詢結果。
Distributed merge union
「distributed merge union」運算子會將查詢分配到多個遠端伺服器。然後合併查詢結果,產生排序結果,稱為「分散式合併排序」。
分散式合併聯集會執行下列步驟:
根伺服器會將子查詢傳送至每個遠端伺服器,這些伺服器會代管所查詢資料的分割。子查詢包含的指令會依特定順序排序結果。
每個遠端伺服器都會對分割資料執行子查詢,然後以要求的順序傳回結果。
根伺服器會合併排序後的子查詢,產生完全排序的結果。
Spanner 第 3 版以上版本預設會啟用分散式合併聯集。
Distributed cross apply
「distributed cross apply」 (DCA) 運算子會跨多個伺服器執行,延伸 cross apply 運算子。DCA 輸入端會將資料列「批次」分組 (與一次只能處理一個輸入列的一般 cross apply 不同)。DCA 對應端是執行遠端伺服器的 cross apply 運算子組。
例如使用這個查詢:
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 的資料列分批。此 cross apply 運算子的對應端是對 AlbumsByAlbumTitle 索引的索引掃描,受制於輸入列中符合 AlbumsByAlbumTitle 索引 AlbumId 索引鍵的 AlbumId 述詞。對應會在分批的輸入列中傳回 SingerId 值的 SongName。
總結此範例的 DCA 程序,DCA 的輸入是 Albums 資料表中分批的列,而 DCA 的輸出就是這些資料列應用到索引掃描的對應。
Distributed outer apply
「distributed outer apply」運算子會藉由跨多個伺服器執行的方式延伸 outer apply 運算子,類似 distributed cross apply 運算子延伸 cross apply 運算子的方式。
例如使用這個查詢:
SELECT lastname,
concertdate
FROM singers LEFT OUTER join@{JOIN_TYPE=APPLY_JOIN} concerts
ON singers.singerid=concerts.singerid;
結果的格式如下:
| LastName | ConcertDate |
|---|---|
| Trentor | 2014-02-18 |
| Smith | 2011-09-03 |
| Smith | 2010-06-06 |
| Lomond | 2005-04-30 |
| Martin | 2015-11-04 |
| Richards |
以下為執行計畫:
Apply mutations
「apply mutations」運算子將來自於資料操縱陳述式 (DML) 的變異套用到資料表。這是 DML 陳述式查詢計畫的頂層運算子。
例如使用這個查詢:
DELETE FROM singers
WHERE firstname = 'Alice';
結果會是:
4 rows deleted This statement deleted 4 rows and did not return any rows.
以下為執行計畫:
其他資訊
本節說明不是獨立運算子的項目,但可執行工作以支援上述一項或多個運算子。此處描述的項目雖然技術上算是運算子,卻不是查詢計畫中的獨立運算子。
結構建構函式
「結構建構函式」會建立「結構」,也就是一組欄位。一般而言,此函式會為源自於運算作業的資料列建立結構。結構建構函式不是獨立的運算子,而會出現在 compute struct 運算子或 serialize result 運算子中。
針對運算結構作業,結構建構函式會建立結構,如此運算資料列的資料欄才可使用單一變數參照到建構。
針對序列化的結果作業,結構建構函式會建立結構以序列化結果。
例如使用這個查詢:
SELECT IF(TRUE, struct(1 AS A, 1 AS B), struct(2 AS A , 2 AS B)).A;
結果會是:
| A |
|---|
| 1 |
以下為執行計畫:
在執行計畫中,結構建構函式會在 serialize result 運算子中出現。