本頁面說明如何新增及使用搜尋索引。全文搜尋會針對搜尋索引中的項目執行。
如何使用搜尋索引
您可以為要進行全文搜尋的任何資料欄建立搜尋索引。如要建立搜尋索引,請使用 CREATE SEARCH INDEX DDL 陳述式。如要更新索引,請使用 ALTER SEARCH INDEX DDL 陳述式。Spanner 會自動建構及維護搜尋索引,包括在資料庫中的資料變更時,立即在搜尋索引中新增及更新資料。
搜尋索引分區
搜尋索引可以分區或不分區,具體取決於您要加速的查詢類型。
舉例來說,如果應用程式查詢電子郵件信箱,分區索引就是最佳選擇。每項查詢僅限於特定信箱。
舉例來說,如果查詢涵蓋產品目錄中的所有產品類別,則最適合使用未分區查詢。
搜尋索引用途
除了全文搜尋,Spanner 搜尋索引也支援下列功能:
- JSON 搜尋:可有效率地為 JSON 和 JSONB 文件建立索引及執行查詢。
- 子字串搜尋:這類查詢會在較大的文字主體中尋找較短的字串 (子字串)。
- 將任何已建立索引資料子集 (包括完全比對和數值) 的條件合併為單一索引掃描。
如要進一步瞭解用途,請參閱「搜尋與次要索引」。
搜尋索引範例
為了展示搜尋索引的功能,假設有一個資料表儲存音樂專輯的相關資訊:
GoogleSQL
CREATE TABLE Albums (
AlbumId STRING(MAX) NOT NULL,
AlbumTitle STRING(MAX)
) PRIMARY KEY(AlbumId);
PostgreSQL
CREATE TABLE albums (
albumid character varying NOT NULL,
albumtitle character varying,
PRIMARY KEY(albumid));
Spanner 有多個可建立權杖的權杖化函式。如要修改上一個資料表,讓使用者執行全文搜尋來尋找專輯名稱,請使用 TOKENIZE_FULLTEXT 函式,從專輯名稱建立權杖。接著,建立使用 TOKENLIST 資料類型的資料欄,用於保存 TOKENIZE_FULLTEXT 的權杖化輸出內容。在本範例中,我們會建立 AlbumTitle_Tokens 欄。
GoogleSQL
ALTER TABLE Albums
ADD COLUMN AlbumTitle_Tokens TOKENLIST
AS (TOKENIZE_FULLTEXT(AlbumTitle)) HIDDEN;
PostgreSQL
ALTER TABLE albums
ADD COLUMN albumtitle_tokens spanner.tokenlist
GENERATED ALWAYS AS (spanner.tokenize_fulltext(albumtitle)) VIRTUAL HIDDEN;
下列範例使用 CREATE SEARCH INDEX DDL,在 AlbumTitle 權杖 (AlbumTitle_Tokens) 上建立搜尋索引 (AlbumsIndex):
GoogleSQL
CREATE SEARCH INDEX AlbumsIndex
ON Albums(AlbumTitle_Tokens);
PostgreSQL
本範例使用 CREATE SEARCH INDEX。
CREATE SEARCH INDEX albumsindex ON albums(albumtitle_tokens);
新增搜尋索引後,請使用 SQL 查詢找出符合搜尋條件的專輯。例如:
GoogleSQL
SELECT AlbumId
FROM Albums
WHERE SEARCH(AlbumTitle_Tokens, "fifth symphony")
PostgreSQL
SELECT albumid
FROM albums
WHERE spanner.search(albumtitle_tokens, 'fifth symphony')
為具名結構定義建立及查詢搜尋索引
您可以在具名結構定義上建立搜尋索引,如以下範例所示。
GoogleSQL
CREATE SCHEMA AlbumSchema;
CREATE TABLE AlbumSchema.Albums (
AlbumId STRING(MAX) NOT NULL,
AlbumTitle STRING(MAX),
AlbumTitle_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(AlbumTitle)) STORED HIDDEN,
) PRIMARY KEY(AlbumId);
CREATE SEARCH INDEX AlbumSchema.AlbumsIndex ON AlbumSchema.Albums(AlbumTitle_Tokens);
PostgreSQL
在 PostgreSQL 中,您無法使用完整名稱在具名結構定義上建立索引。
CREATE SCHEMA AlbumSchema;
CREATE TABLE AlbumSchema.albums (
albumid character varying NOT NULL,
albumtitle character varying,
albumtitle_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.tokenize_fulltext(albumtitle)) STORED HIDDEN,
PRIMARY KEY(albumid)
);
CREATE SEARCH INDEX albumsindex ON AlbumSchema.albums(albumtitle_tokens);
以下範例說明如何查詢具名結構定義的搜尋索引:
GoogleSQL
SELECT AlbumId
FROM AlbumSchema.Albums
WHERE SEARCH(AlbumTitle_Tokens, "fifth symphony")
以下範例使用查詢提示,指示查詢最佳化工具在執行查詢時,使用名為 AlbumsIndex 的索引。
SELECT AlbumId
FROM AlbumSchema.Albums@{FORCE_INDEX="AlbumSchema.AlbumsIndex"}
WHERE SEARCH(AlbumTitle_Tokens, "fifth symphony")
PostgreSQL
在 PostgreSQL 中,您無法使用完整名稱在具名結構定義上建立索引。
SELECT albumid
FROM AlbumSchema.albums
WHERE spanner.search(albumtitle_tokens, 'fifth symphony')
以下範例使用查詢提示,指示查詢最佳化工具在執行查詢時,使用名為 albumsindex 的索引。
SELECT albumid
FROM AlbumSchema.albums /*@ FORCE_INDEX = albumsindex */
WHERE spanner.search(albumtitle_tokens, 'fifth symphony')
資料一致性
建立索引時,Spanner 會使用自動化程序回填資料,確保一致性。寫入作業提交後,索引會在同一筆交易中更新。Spanner 會自動執行資料一致性檢查。
搜尋索引結構定義
搜尋索引是在資料表的一或多個 TOKENLIST 資料欄上定義。搜尋索引包含下列元件:
- 基礎資料表:需要建立索引的 Spanner 資料表。
TOKENLIST欄:定義需要建立索引的權杖的資料欄集合。這些資料欄的順序並不重要。
舉例來說,在下列陳述式中,基礎資料表為 Albums。TOKENLIST 欄建立於 AlbumTitle (AlbumTitle_Tokens) 和 Rating (Rating_Tokens)。
GoogleSQL
CREATE TABLE Albums (
AlbumId STRING(MAX) NOT NULL,
SingerId INT64 NOT NULL,
ReleaseTimestamp INT64 NOT NULL,
AlbumTitle STRING(MAX),
Rating FLOAT64,
AlbumTitle_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(AlbumTitle)) HIDDEN,
Rating_Tokens TOKENLIST AS (TOKENIZE_NUMBER(Rating)) HIDDEN
) PRIMARY KEY(AlbumId);
PostgreSQL
CREATE TABLE albums (
albumid character varying NOT NULL,
singerid bigint NOT NULL,
releasetimestamp bigint NOT NULL,
albumtitle character varying,
rating double precision,
albumtitle_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.tokenize_fulltext(albumtitle)) VIRTUAL HIDDEN,
rating_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.tokenize_fulltext(rating)) VIRTUAL HIDDEN,
PRIMARY KEY(AlbumId));
使用下列 CREATE SEARCH INDEX 陳述式,透過 AlbumTitle 和 Rating 的權杖建立搜尋索引:
GoogleSQL
CREATE SEARCH INDEX AlbumsIndex
ON Albums(AlbumTitle_Tokens, Rating_Tokens)
PARTITION BY SingerId
ORDER BY ReleaseTimestamp DESC
PostgreSQL
CREATE SEARCH INDEX albumsindex
ON albums(albumtitle_tokens, rating_tokens)
PARTITION BY singerid
ORDER BY releasetimestamp DESC
搜尋索引有下列選項:
- 分區:選用的一組資料欄,用於劃分搜尋索引。查詢分區索引通常比查詢未分區索引更有效率。詳情請參閱「分區搜尋索引」。
- 排列順序欄:選用的
INT64欄,用於建立從搜尋索引擷取的順序。詳情請參閱「搜尋索引排列順序」。 - 交錯:與次要索引一樣,您可以交錯搜尋索引。交錯搜尋索引會使用較少資源,寫入並彙整基礎資料表。詳情請參閱「交錯搜尋索引」。
- Options 子句:鍵/值組合清單,會覆寫搜尋索引的預設設定。
搜尋索引的內部版面配置
搜尋索引內部表示法的重要元素是 docid,可做為基礎資料表主鍵的儲存效率表示法,長度不限。此外,它也會根據使用者提供的 CREATE SEARCH INDEX 陳述式 ORDER BY 欄,建立內部資料版面配置的順序。以一或兩個 64 位元整數表示。
搜尋索引在內部實作為兩層對應:
- 詞元對應至文件 ID
- Docid 對應至基礎資料表主鍵
由於 Spanner 不需要為每個 <token, document> 配對儲存完整的基礎資料表主鍵,因此這個配置可大幅節省儲存空間。
實體索引分為兩種類型,分別實作兩個層級的對應:
- 次要索引:將分區鍵和 docid 對應至基礎資料表主鍵。在上一節的範例中,這會將
{SingerId, ReleaseTimestamp, uid}對應至{AlbumId}。次要索引也會儲存CREATE SEARCH INDEX的STORING子句中指定的所有資料欄。 - 權杖索引:將權杖對應至 docid,類似資訊檢索文獻中的反向索引。Spanner 會為搜尋索引的每個
TOKENLIST維護獨立的權杖索引。邏輯上,詞元索引會維護每個分區中每個詞元的 docid 清單 (在資訊檢索中稱為張貼清單)。清單會依權杖排序,以便快速擷取,而清單內則會依 docid 排序。個別權杖索引是實作細節,不會透過 Spanner API 公開。
Spanner 支援下列 docid 選項。
| 搜尋索引 | Docid | 行為 |
|---|---|---|
搜尋索引省略 ORDER BY 子句 |
{uid} |
Spanner 會新增隱藏的專屬值 (UID) 來識別每個資料列。 |
ORDER BY column |
{column, uid} |
Spanner 會新增 UID 資料欄,用於區分分區中具有相同 column 值的資料列。 |
使用須知:
- 內部 UID 資料欄不會透過 Spanner API 公開。
- 在未新增 UID 的索引中,如果交易新增的資料列已存在 (分區、排列順序),就會失敗。
舉例來說,假設有下列資料:
| AlbumId | SingerId | ReleaseTimestamp | SongTitle |
|---|---|---|---|
| a1 | 1 | 997 | 美好時光 |
| a2 | 1 | 743 | 美麗的眼睛 |
假設預先排序欄為遞增順序,則依 SingerId 分割的權杖索引內容會以下列方式分割權杖索引內容:
| SingerId | _token | ReleaseTimestamp | uid |
|---|---|---|---|
| 1 | 美麗 | 743 | uid1 |
| 1 | 美麗 | 997 | uid2 |
| 1 | 天 | 743 | uid1 |
| 1 | 眼睛 | 997 | uid2 |
搜尋索引資料分割
Spanner 分割資料表時,會分配搜尋索引資料,讓特定基礎資料表資料列中的所有權杖都位於同一個分割區。換句話說,搜尋索引是以文件為單位進行 shard。這種資料分割策略對效能有重大影響:
- 無論權杖數量或已建立索引的
TOKENLIST欄數量為何,每筆交易通訊的伺服器數量都維持不變。 - 涉及多個條件運算式的搜尋查詢會在每個分割區上獨立執行,避免與分散式彙整相關的效能負擔。
搜尋索引有兩種分布模式:
- 統一資料分割 (預設)。在統一 shard 模式中,每個基礎資料表資料列的索引資料,都會隨機指派給分區的索引分割。
- 排序順序資料分割。在排序順序 sharding 中,每個基礎資料表資料列的資料,都會根據
ORDER BY欄 (即預先排序欄) 指派給分區的索引分割。舉例來說,如果排列順序為遞減,則所有具有最大排列順序值的資料列,都會顯示在分區的第一個索引分割區,而次大的排列順序值群組則會顯示在下一個分割區。
這些 sharding 模式會在熱點風險和查詢成本之間做出取捨:
- 如果搜尋索引的讀取或寫入模式可能導致熱點,建議使用統一 shard 搜尋索引。統一資料分割會將讀取和寫入負載平均分配到各個分割,藉此減少熱點,但這可能會增加查詢執行期間的資源用量。在統一 shard 搜尋索引中,由於資料隨機分布,查詢必須讀取分區內的所有分割。存取平均分 shard 的索引時,Spanner 會平行讀取所有分割,縮短整體查詢延遲時間。
- 如果讀取或寫入模式不太可能造成熱點,建議使用排列順序 shard 搜尋索引。如果查詢的
ORDER BY與索引的ORDER BY相符,且指定的LIMIT較低,這種做法可以降低查詢成本。執行這類查詢時,Spanner 會從分區的第一個分割區開始遞增讀取,如果能提早滿足LIMIT,查詢就能在不讀取所有分割區的情況下完成。 搜尋索引的 sharding 模式是使用
OPTIONS子句設定。
GoogleSQL
CREATE SEARCH INDEX AlbumsIndex
ON Albums(AlbumTitle_Tokens, Rating_Tokens)
PARTITION BY SingerId
ORDER BY ReleaseTimestamp DESC
OPTIONS (sort_order_sharding = true);
PostgreSQL
搜尋索引的分區模式是使用 WITH 子句設定。
CREATE SEARCH INDEX albumsindex
ON albums(albumtitle_tokens, rating_tokens)
PARTITION BY singerid
ORDER BY releasetimestamp DESC
WITH (sort_order_sharding = true);
如果設定 sort_order_sharding=false 或未指定,系統會使用統一資料分割建立搜尋索引。
交錯搜尋索引
與次要索引一樣,您可以在基礎資料表的父項資料表中交錯搜尋索引。使用交錯搜尋索引的主要原因,是將基礎資料表資料與索引資料放在同一個位置,以便處理小型分區。這種機會主義式的共置方式有以下優點:
- 寫入作業不需要執行兩階段修訂。
- 搜尋索引與基礎資料表的反向 join 不會分散。
交錯搜尋索引有下列限制:
- 只有依排序順序分區的索引可以交錯。
- 搜尋索引只能交錯在頂層資料表 (而非子資料表)。
- 與交錯資料表和次要索引一樣,請將父項資料表的索引鍵設為交錯搜尋索引中
PARTITION BY資料欄的前置字串。
定義交錯搜尋索引
以下範例說明如何定義交錯搜尋索引:
GoogleSQL
CREATE TABLE Singers (
SingerId INT64 NOT NULL
) PRIMARY KEY(SingerId);
CREATE TABLE Albums (
SingerId INT64 NOT NULL,
AlbumId STRING(MAX) NOT NULL,
AlbumTitle STRING(MAX),
AlbumTitle_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(AlbumTitle)) HIDDEN
) PRIMARY KEY(SingerId, AlbumId),
INTERLEAVE IN PARENT Singers ON DELETE CASCADE;
CREATE SEARCH INDEX AlbumsIndex
ON Albums(AlbumTitle_Tokens)
PARTITION BY SingerId,
INTERLEAVE IN Singers
OPTIONS (sort_order_sharding = true);
PostgreSQL
CREATE TABLE singers(
singerid bigint NOT NULL
PRIMARY KEY(singerid));
CREATE TABLE albums(
singerid bigint NOT NULL,
albumid character varying NOT NULL,
albumtitle character varying,
albumtitle_tokens spanner.tokenlist
GENERATED ALWAYS
AS (
spanner.tokenize_fulltext(albumtitle)
) VIRTUAL HIDDEN,
PRIMARY KEY(singerid, albumid)),
INTERLEAVE IN PARENT singers ON DELETE CASCADE;
CREATE
SEARCH INDEX albumsindex
ON
albums(albumtitle_tokens)
PARTITION BY singerid INTERLEAVE IN singers WITH(sort_order_sharding = true);
搜尋索引排列順序
搜尋索引排列順序定義的需求條件與次要索引不同。
舉例來說,請參考下表:
GoogleSQL
CREATE TABLE Albums (
AlbumId STRING(MAX) NOT NULL,
ReleaseTimestamp INT64 NOT NULL,
AlbumName STRING(MAX),
AlbumName_Token TOKENLIST AS (TOKEN(AlbumName)) HIDDEN
) PRIMARY KEY(AlbumId);
PostgreSQL
CREATE TABLE albums (
albumid character varying NOT NULL,
releasetimestamp bigint NOT NULL,
albumname character varying,
albumname_token spanner.tokenlist
GENERATED ALWAYS AS(spanner.token(albumname)) VIRTUAL HIDDEN,
PRIMARY KEY(albumid));
應用程式可能會定義次要索引,使用 AlbumName 查詢資訊,並依 ReleaseTimestamp 排序:
CREATE INDEX AlbumsSecondaryIndex ON Albums(AlbumName, ReleaseTimestamp DESC);
對應的搜尋索引如下所示 (使用完全比對權杖化,因為次要索引不支援全文搜尋):
CREATE SEARCH INDEX AlbumsSearchIndex
ON Albums(AlbumName_Token)
ORDER BY ReleaseTimestamp DESC;
搜尋索引排列順序必須符合下列規定:
- 搜尋索引的排列順序只能使用
INT64資料欄。Spanner 必須在每個權杖旁儲存 docid,因此任意大小的資料欄會耗用過多搜尋索引資源。具體來說,排序順序欄無法使用TIMESTAMP類型,因為TIMESTAMP使用奈秒精確度,不適用於 64 位元整數。 排列順序欄不得為
NULL。有兩種方法可以滿足這項要求:- 將排列順序欄宣告為
NOT NULL。 - 將索引設為排除空值。
- 將排列順序欄宣告為
時間戳記通常用於決定排列順序。常見做法是使用自 Unix 紀元起算的微秒數做為這類時間戳記。
應用程式通常會使用遞減排序的搜尋索引,先擷取最新資料。
由於排列順序欄是基礎次要索引和權杖索引中 docid 鍵的一部分,因此只要基礎資料表中的值發生變更,Spanner 就必須重寫對應的次要索引資料列和所有相關聯的權杖索引資料列,這會產生寫入負擔。如果排列順序值經常變更,請考慮從 ORDER BY 中省略該資料欄,或使用精細程度較低的值。舉例來說,如果 UpdateTime 資料欄每隔幾秒就會更新,您可以使用產生資料欄,將時間戳記分組到小時,例如 UpdateTimeHours INT64 AS (UNIX_SECONDS(TIMESTAMP_TRUNC(UpdateTime, HOUR))) STORED (或 PostgreSQL 中的 updatetimehours bigint GENERATED ALWAYS AS (EXTRACT(EPOCH FROM updatetime - '1970-01-01Z'::timestamptz)::bigint / 3600) STORED)。
NULL 篩選搜尋索引
搜尋索引可以使用 WHERE column_name IS NOT NULL 語法排除基礎資料表資料列。NULL 篩選功能可套用至分區鍵、排列順序欄和儲存的資料欄。無法對儲存的陣列資料欄進行 NULL 篩選。
範例
GoogleSQL
CREATE SEARCH INDEX AlbumsIndex
ON Albums(AlbumTitle_Tokens)
STORING (Genre)
WHERE Genre IS NOT NULL
PostgreSQL
CREATE SEARCH INDEX albumsindex
ON albums(albumtitle_tokens)
INCLUDE (genre)
WHERE genre IS NOT NULL
查詢必須在 WHERE 子句中指定 NULL 篩選條件 (本例為 Genre IS NOT NULL)。否則查詢最佳化工具無法使用搜尋索引。詳情請參閱「SQL 查詢規定」。
在索引定義中使用 WHERE <column> IS NOT NULL 篩選器,可確保只為非空值建立索引。這樣一來,即使查詢的 WHERE 子句中未明確包含 IS NOT NULL 條件,查詢最佳化工具也能有效使用索引來處理相關查詢。
在產生的資料欄上使用 NULL 篩選,即可根據任何條件排除資料列。詳情請參閱「使用產生的資料欄建立部分索引」。
後續步驟
- 瞭解權杖化和 Spanner 權杖化工具。
- 瞭解數值索引。
- 瞭解 JSON 索引。
- 瞭解索引分區。