本頁說明如何新增及使用搜尋索引。系統會針對搜尋索引中的項目執行全文搜尋。
如何使用搜尋索引
您可以針對要提供全文搜尋的任何資料欄建立搜尋索引。如要建立搜尋索引,請使用 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
陳述式,透過 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
欄,用於建立從搜尋索引擷取的順序。詳情請參閱「搜尋索引排序順序」。 - 交錯:與次要索引類似,您可以交錯搜尋索引。交錯搜尋索引使用的資源較少,可寫入並加入主資料表。詳情請參閱「交錯搜尋索引」。
- 選項子句:鍵/值組合清單,可覆寫搜尋索引的預設設定。
搜尋索引的內部版面配置
搜尋索引內部表示法的重要元素是 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 | Beautiful days |
a2 | 1 | 743 | 美麗雙眼 |
假設預先排序的資料欄是遞增順序,則依 SingerId
分區的符記索引內容會依下列方式分區:
SingerId | _token | ReleaseTimestamp | uid |
---|---|---|---|
1 | 美麗 | 743 | uid1 |
1 | 美麗 | 997 | uid2 |
1 | 天 | 743 | uid1 |
1 | 眼睛 | 997 | uid2 |
搜尋索引分片
Spanner 分割資料表時,會分配搜尋索引資料,讓特定資料表列中的所有權杖都位於同一個分割區。換句話說,搜尋索引是文件分片。這項分片策略會對效能造成重大影響:
- 無論權杖數量或索引資料欄數量為何,每筆交易通訊的伺服器數量都維持不變。
TOKENLIST
- 涉及多個條件運算式的搜尋查詢會在每個分割上獨立執行,避免與分散式聯結相關的效能負擔。
搜尋索引有兩種發布模式:
- 統一資料分割 (預設)。在一致性分片中,每個基本資料表列的索引資料會隨機指派給分區的索引分割。
- 排序順序分片。在排序順序分片中,系統會根據
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
或未指定,系統會使用統一分片建立搜尋索引。
交錯式搜尋索引
與次要索引相同,您可以在基本資料表的父項資料表中交錯搜尋索引。使用交錯搜尋索引的主要原因,是為了將基本資料表資料與小型分區的索引資料放在相同位置。這種機會性共置具有下列優點:
- 寫入作業不需要執行兩階段提交。
- 搜尋索引與主資料表的反向聯結不會分配。
交錯搜尋索引有下列限制:
- 只有排序順序分片索引可以交錯。
- 搜尋索引只能交錯於頂層資料表 (而非子項資料表)。
- 與交錯式資料表和次要索引相同,請將父項資料表的鍵設為交錯式搜尋索引中
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;
搜尋索引排序順序必須符合下列規定:
- 只能使用
INT64
資料欄做為搜尋索引的排序依據。任意大小的資料欄會使用搜尋索引中過多的資源,因為 Spanner 需要在每個權杖旁儲存 docid。具體來說,排序順序欄無法使用TIMESTAMP
類型,因為TIMESTAMP
使用奈秒精確度,不適合 64 位元整數。 排序順序資料欄不得為
NULL
。有兩種方法可滿足這項規定:- 將排序順序欄宣告為
NOT NULL
。 - 設定索引以排除 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 篩選功能,即可根據任意條件排除資料列。詳情請參閱「使用產生的資料欄建立部分索引」。
後續步驟
- 瞭解權杖化和 Spanner 權杖化工具。
- 瞭解數字索引。
- 瞭解 JSON 索引。
- 瞭解索引分割。