搜尋索引

本頁說明如何新增及使用搜尋索引。系統會針對搜尋索引中的項目執行全文搜尋

如何使用搜尋索引

您可以針對要提供全文搜尋的任何資料欄建立搜尋索引。如要建立搜尋索引,請使用 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')

資料一致性

建立索引時,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 陳述式,透過 AlbumTitleRating 的權杖建立搜尋索引:

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 欄,用於建立從搜尋索引擷取的順序。詳情請參閱「搜尋索引排序順序」。
  • 交錯:與次要索引類似,您可以交錯搜尋索引。交錯搜尋索引使用的資源較少,可寫入並加入主資料表。詳情請參閱「交錯搜尋索引」。
  • 選項子句:鍵/值組合清單,可覆寫搜尋索引的預設設定。

搜尋索引的內部版面配置

搜尋索引內部表示法的重要元素是 docid,可做為任意長度基本資料表主鍵的儲存空間效率表示法。此外,系統也會根據使用者提供的 CREATE SEARCH INDEX 陳述式 ORDER BY 資料欄,建立內部資料版面配置的順序。以一或兩個 64 位元整數表示。

搜尋索引在內部會實作為雙層對應:

  1. 權杖對應至文件 ID
  2. 以資料表主鍵為準的 Docid

這個架構可大幅節省儲存空間,因為 Spanner 不需要為每個 <token, document> 配對儲存完整的基本資料表主鍵。

實作這兩個層級對應的實體索引有兩種:

  1. 次要索引,可將分割區鍵和 docid 對應至基本資料表主鍵。在上一節的範例中,這會將 {SingerId, ReleaseTimestamp, uid} 對應至 {AlbumId}。次要索引也會儲存 CREATE SEARCH INDEXSTORING 子句中指定的所有資料欄。
  2. 權杖索引:將權杖對應至 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 Beautiful days
a2 1 743 美麗雙眼

假設預先排序的資料欄是遞增順序,則依 SingerId 分區的符記索引內容會依下列方式分區:

SingerId _token ReleaseTimestamp uid
1 美麗 743 uid1
1 美麗 997 uid2
1 743 uid1
1 眼睛 997 uid2

搜尋索引分片

Spanner 分割資料表時,會分配搜尋索引資料,讓特定資料表列中的所有權杖都位於同一個分割區。換句話說,搜尋索引是文件分片。這項分片策略會對效能造成重大影響:

  1. 無論權杖數量或索引資料欄數量為何,每筆交易通訊的伺服器數量都維持不變。TOKENLIST
  2. 涉及多個條件運算式的搜尋查詢會在每個分割上獨立執行,避免與分散式聯結相關的效能負擔。

搜尋索引有兩種發布模式:

  • 統一資料分割 (預設)。在一致性分片中,每個基本資料表列的索引資料會隨機指派給分區的索引分割。
  • 排序順序分片。在排序順序分片中,系統會根據 ORDER BY 資料欄 (即預先排序的資料欄),將每個基本資料表資料列的資料指派給分區的索引分割。舉例來說,如果排序順序為遞減,則分區的第一個索引分割會顯示排序順序值最大的所有資料列,下一個分割則會顯示排序順序值次大的群組。

這些分片模式會在熱點風險和查詢成本之間做出取捨:

  • 如果搜尋索引的讀取或寫入模式可能導致熱點,建議使用統一分片搜尋索引。平均分片會將讀取和寫入負載平均分配到各個分割區,藉此減輕熱點問題,但這可能會導致查詢執行期間的資源用量增加。在統一分片搜尋索引中,由於資料是隨機分布,查詢必須讀取分區內的所有分割。存取一致分片索引時,Spanner 會平行讀取所有分割,以縮短整體查詢延遲時間。
  • 如果讀取或寫入模式不太可能導致資源使用率不均,建議使用排序順序分片搜尋索引。如果查詢的 ORDER BY 與索引的 ORDER BY 相符,且指定相對較低的 LIMIT,這個方法就能降低查詢成本。執行這類查詢時,Spanner 會從分區的第一個分割開始遞增讀取,如果 LIMIT 能夠提早滿足,查詢就不必讀取所有分割即可完成。
  • 搜尋索引的分片模式是使用 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 或未指定,系統會使用統一分片建立搜尋索引。

交錯式搜尋索引

與次要索引相同,您可以在基本資料表的父項資料表中交錯搜尋索引。使用交錯搜尋索引的主要原因,是為了將基本資料表資料與小型分區的索引資料放在相同位置。這種機會性共置具有下列優點:

  • 寫入作業不需要執行兩階段提交
  • 搜尋索引與主資料表的反向聯結不會分配。

交錯搜尋索引有下列限制:

  1. 只有排序順序分片索引可以交錯。
  2. 搜尋索引只能交錯於頂層資料表 (而非子項資料表)。
  3. 與交錯式資料表和次要索引相同,請將父項資料表的鍵設為交錯式搜尋索引中 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));

應用程式可能會定義次要索引,以便使用依 ReleaseTimestamp 排序的 AlbumName 查閱資訊:

CREATE INDEX AlbumsSecondaryIndex ON Albums(AlbumName, ReleaseTimestamp DESC);

對應的搜尋索引如下 (由於次要索引不支援全文搜尋,因此使用完全相符的權杖化):

CREATE SEARCH INDEX AlbumsSearchIndex
ON Albums(AlbumName_Token)
ORDER BY ReleaseTimestamp DESC;

搜尋索引排序順序必須符合下列規定:

  1. 只能使用 INT64 資料欄做為搜尋索引的排序依據。任意大小的資料欄會使用搜尋索引中過多的資源,因為 Spanner 需要在每個權杖旁儲存 docid。具體來說,排序順序欄無法使用 TIMESTAMP 類型,因為 TIMESTAMP 使用奈秒精確度,不適合 64 位元整數。
  2. 排序順序資料欄不得為 NULL。有兩種方法可滿足這項規定:

    1. 將排序順序欄宣告為 NOT NULL
    2. 設定索引以排除 NULL 值

時間戳記通常用於判斷排序順序。常見做法是使用自 Unix 紀元起算的微秒時間做為這類時間戳記。

應用程式通常會先使用遞減排序的搜尋索引,擷取最新資料。

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 查詢規定」。

在產生的資料欄上使用 NULL 篩選功能,即可根據任意條件排除資料列。詳情請參閱「使用產生的資料欄建立部分索引」。

後續步驟