管理搜索索引

搜索索引是一种数据结构,旨在使用 SEARCH 函数实现非常高效的搜索。就像您在书籍后面找到的索引一样,字符串数据列的搜索索引就像一个辅助表,其中一列包含唯一字词,另一列表示出现这些字词的数据所在的位置。

创建搜索索引

如需创建搜索索引,请使用 CREATE SEARCH INDEX DDL 语句。您可以针对以下列类型创建搜索索引:

  • STRING
  • ARRAY<STRING>
  • STRUCT,至少包含一个类型为 STRINGARRAY<STRING> 的嵌套字段
  • JSON

创建搜索索引时,您可以指定要使用的文本分析器的类型。文本分析器可控制如何对数据进行词法单元化处理以供索引和搜索功能使用。默认值为 LOG_ANALYZER。此分析器非常适合机器生成的日志,并具有针对可观测性数据中常见令牌的特殊规则,例如 IP 地址或电子邮件地址。在预处理完您希望完全匹配的数据后,请使用 NO_OP_ANALYZERPATTERN_ANALYZER 使用正则表达式从文本中提取词元。

以下示例会在 simple_tableac 列上创建搜索索引。

CREATE TABLE dataset.simple_table(a STRING, b INT64, c JSON);

CREATE SEARCH INDEX my_index
ON dataset.simple_table(a, c);

如果在 ALL COLUMNS 上创建搜索索引,则表中的所有 STRINGJSON 数据都会编入索引。 如果表不包含此类数据(比方说,如果所有列包含整数),则索引创建操作将会失败。如果您指定要编入索引的 STRUCT 列,则所有嵌套的子字段都会编入索引。

在以下示例中,搜索索引是在 ac.ec.f.g 上创建的,并使用 NO_OP_ANALYZER 文本分析器。

CREATE TABLE dataset.my_table(
  a STRING,
  b INT64,
  c STRUCT <d INT64,
            e ARRAY<STRING>,
            f STRUCT<g STRING, h INT64>>) AS
SELECT 'hello' AS a, 10 AS b, (20, ['x', 'y'], ('z', 30)) AS c;

CREATE SEARCH INDEX my_index
ON dataset.my_table(ALL COLUMNS)
OPTIONS (analyzer = 'NO_OP_ANALYZER');

由于搜索索引是在 ALL COLUMNS 上创建的,因此如果添加到表中的任何列包含 STRING 数据,则会将这些列编入索引。

了解索引刷新

搜索索引由 BigQuery 完全管理,并在表发生变化时自动刷新。对表的以下架构更改可能会触发完全刷新:

  • 将新的可索引列添加到在 ALL COLUMNS 上具有搜索索引的表。
  • 由于表架构更改,索引列会更新。

如果您删除表中仅编入索引的列或重命名表本身,则搜索索引会自动予以删除。

搜索索引专为大型表而设计。如果您在小于 10GB 的表上创建搜索索引,则系统不会填充该索引。同样,如果从编入索引的表中删除数据导致表大小低于 10GB,则系统会暂时停用索引。在这种情况下,搜索查询不使用索引,并且 IndexUnusedReason 代码BASE_TABLE_TOO_SMALL。无论您是否将自己的预留用于索引管理作业,都会发生这种情况。当编入索引的表大小超过 10GB 时,系统会自动填充其索引。在填充并激活搜索索引之前,您不需要支付存储费用。使用 SEARCH 函数的查询始终会返回正确的结果,即使某些数据尚未编入索引也是如此。

获取有关搜索索引的信息

可以通过查询 INFORMATION_SCHEMA 来验证搜索索引是否存在以及是否就绪。有两个视图包含有关搜索索引的元数据。INFORMATION_SCHEMA.SEARCH_INDEXES 视图包含在数据集上创建的每个搜索索引的相关信息。INFORMATION_SCHEMA.SEARCH_INDEX_COLUMNS 视图包含数据集的每个表中已编入索引的列的相关信息。

以下示例展示了项目 my_project 的数据集 my_dataset 中表的所有活跃搜索索引。该示例包括索引名称、用于创建索引的 DDL 语句、索引覆盖率及其文本分析器。如果编入索引的基表小于 10GB,则系统不会填充其索引,在这种情况下,coverage_percentage 为 0。

SELECT table_name, index_name, ddl, coverage_percentage, analyzer
FROM my_project.my_dataset.INFORMATION_SCHEMA.SEARCH_INDEXES
WHERE index_status = 'ACTIVE';

结果应如下所示:

+-------------+-------------+--------------------------------------------------------------------------------------+---------------------+----------------+
| table_name  | index_name  | ddl                                                                                  | coverage_percentage | analyzer       |
+-------------+-------------+--------------------------------------------------------------------------------------+---------------------+----------------+
| small_table | names_index | CREATE SEARCH INDEX `names_index` ON `my_project.my_dataset.small_table`(names)      | 0                   | NO_OP_ANALYZER |
| large_table | logs_index  | CREATE SEARCH INDEX `logs_index` ON `my_project.my_dataset.large_table`(ALL COLUMNS) | 100                 | LOG_ANALYZER   |
+-------------+-------------+--------------------------------------------------------------------------------------+---------------------+----------------+

以下示例针对 my_table 的所有列创建搜索索引。

CREATE TABLE dataset.my_table(
  a STRING,
  b INT64,
  c STRUCT <d INT64,
            e ARRAY<STRING>,
            f STRUCT<g STRING, h INT64>>) AS
SELECT 'hello' AS a, 10 AS b, (20, ['x', 'y'], ('z', 30)) AS c;

CREATE SEARCH INDEX my_index
ON dataset.my_table(ALL COLUMNS);

以下查询会提取将哪些字段编入索引的信息。index_field_path 指示将列的哪个字段编入索引。这与 index_column_name 不同,后者仅在 STRUCT 的情况下提供编入索引的字段的完整路径。在此示例中,c 列包含 ARRAY<STRING> 字段 e 和另一个名为 fSTRUCT(包含 STRING 字段 g),每个网址都编入索引。

SELECT table_name, index_name, index_column_name, index_field_path
FROM my_project.dataset.INFORMATION_SCHEMA.SEARCH_INDEX_COLUMNS

结果类似于以下内容:

+------------+------------+-------------------+------------------+
| table_name | index_name | index_column_name | index_field_path |
+------------+------------+-------------------+------------------+
| my_table   | my_index   | a                 | a                |
| my_table   | my_index   | c                 | c.e              |
| my_table   | my_index   | c                 | c.f.g            |
+------------+------------+-------------------+------------------+

以下查询将 INFORMATION_SCHEMA.SEARCH_INDEX_COUMNS 视图与 INFORMATION_SCHEMA.SEARCH_INDEXESINFORMATION_SCHEMA.COLUMNS 视图联接,以包含每列的搜索索引状态和数据类型:

SELECT
  index_columns_view.index_catalog AS project_name,
  index_columns_view.index_SCHEMA AS dataset_name,
  indexes_view.TABLE_NAME AS table_name,
  indexes_view.INDEX_NAME AS index_name,
  indexes_view.INDEX_STATUS AS status,
  index_columns_view.INDEX_COLUMN_NAME AS column_name,
  index_columns_view.INDEX_FIELD_PATH AS field_path,
  columns_view.DATA_TYPE AS data_type
FROM
  mydataset.INFORMATION_SCHEMA.SEARCH_INDEXES indexes_view
INNER JOIN
  mydataset.INFORMATION_SCHEMA.SEARCH_INDEX_COLUMNS index_columns_view
  ON
    indexes_view.TABLE_NAME = index_columns_view.TABLE_NAME
    AND indexes_view.INDEX_NAME = index_columns_view.INDEX_NAME
LEFT OUTER JOIN
  mydataset.INFORMATION_SCHEMA.COLUMNS columns_view
  ON
    indexes_view.INDEX_CATALOG = columns_view.TABLE_CATALOG
    AND indexes_view.INDEX_SCHEMA = columns_view.TABLE_SCHEMA
    AND index_columns_view.TABLE_NAME = columns_view.TABLE_NAME
    AND index_columns_view.INDEX_COLUMN_NAME = columns_view.COLUMN_NAME
ORDER BY
  project_name,
  dataset_name,
  table_name,
  column_name;

结果类似于以下内容:

+------------+------------+----------+------------+--------+-------------+------------+---------------------------------------------------------------+
| project    | dataset    | table    | index_name | status | column_name | field_path | data_type                                                     |
+------------+------------+----------+------------+--------+-------------+------------+---------------------------------------------------------------+
| my_project | my_dataset | my_table | my_index   | ACTIVE | a           | a          | STRING                                                        |
| my_project | my_dataset | my_table | my_index   | ACTIVE | c           | c.e        | STRUCT<d INT64, e ARRAY<STRING>, f STRUCT<g STRING, h INT64>> |
| my_project | my_dataset | my_table | my_index   | ACTIVE | c           | c.f.g      | STRUCT<d INT64, e ARRAY<STRING>, f STRUCT<g STRING, h INT64>> |
+------------+------------+----------+------------+--------+-------------+------------+---------------------------------------------------------------+

索引管理选项

要创建索引并让 BigQuery 维护它们,您有两种选择:

  • 使用默认共享槽池:当计划编入索引的数据低于每个组织的限制时,您可以使用免费共享槽池来管理索引。
  • 使用您自己的预留:要在较大的生产工作负载上实现更可预测且一致的索引编制进度,您可以使用自己的预留来管理索引。

使用共享槽

如果您尚未将项目配置为使用专用预留来执行索引操作,就会在免费的共享槽池中处理索引管理,但存在以下限制。

如果向表添加数据,从而导致编入索引的表的总大小超过组织的限制,则 BigQuery 会暂停所有编入索引的表的索引管理。发生这种情况时,INFORMATION_SCHEMA.SEARCH_INDEXES 视图中的 index_status 字段会显示 PENDING DISABLEMENT,且该索引会排队等待删除。虽然索引正在等待停用,但它仍然在查询中使用,并且您需要为该索引支付存储费用。索引删除后,index_status 字段会将该索引显示为 TEMPORARILY DISABLED。在此状态下,查询不使用该索引,并且您不需要为该索引支付存储费用。在这种情况下,IndexUnusedReason 代码BASE_TABLE_TOO_LARGE

如果您从表中删除数据,并且编入索引的表的总大小低于每个组织的限制,则所有已编入索引的表的索引管理功能都将恢复。INFORMATION_SCHEMA.SEARCH_INDEXES 视图中的 index_status 字段为 ACTIVE,查询可以使用该索引,并且您需要为该索引支付存储费用。

BigQuery 不保证共享池的可用容量或您看到的索引吞吐量。对于生产应用,您可能需要使用专用槽来执行索引处理功能。

使用您自己的预留

您可以选择指定使用自己的预留来将表编入索引,而不是使用默认的共享槽池。使用您自己的预留可确保索引管理作业(例如创建、刷新和后台优化)具有可预测且一致的性能。

  • 在预留中运行索引作业时,没有表大小限制。
  • 使用您自己的预留可以在管理索引时更加灵活。 如果需要创建非常大的索引或对编入索引的表进行大量更新,您可以暂时为分配空间添加更多的槽。

如需将具有指定预留的项目中的表编入索引,请在表所在的区域中创建预留。然后,将项目分配到该预留,并将 job_type 设置为 BACKGROUND

SQL

使用 CREATE ASSIGNMENT DDL 语句.

  1. 在 Google Cloud 控制台中,转到 BigQuery 页面。

    转到 BigQuery

  2. 在查询编辑器中,输入以下语句:

    CREATE ASSIGNMENT
      `ADMIN_PROJECT_ID.region-LOCATION.RESERVATION_NAME.ASSIGNMENT_ID`
    OPTIONS (
      assignee = 'projects/PROJECT_ID',
      job_type = 'BACKGROUND');
    

    请替换以下内容:

    • ADMIN_PROJECT_ID:拥有预留资源的管理项目的 ID
    • LOCATION:预留的位置
    • RESERVATION_NAME:预留的名称
    • ASSIGNMENT_ID:分配的 ID

      此 ID 对项目和位置来说必须是唯一的,以小写字母或数字开头和结尾,并且只能包含小写字母、数字和短划线。

    • PROJECT_ID:包含要编入索引的表的项目 ID。此项目已分配到预留。

  3. 点击 运行

如需详细了解如何运行查询,请参阅运行交互式查询

bq

使用 bq mk 命令:

bq mk \
    --project_id=ADMIN_PROJECT_ID \
    --location=LOCATION \
    --reservation_assignment \
    --reservation_id=RESERVATION_NAME \
    --assignee_id=PROJECT_ID \
    --job_type=BACKGROUND \
    --assignee_type=PROJECT

请替换以下内容:

  • ADMIN_PROJECT_ID:拥有预留资源的管理项目的 ID
  • LOCATION:预留的位置
  • RESERVATION_NAME:预留的名称
  • PROJECT_ID:要分配到此预留的项目的 ID

查看索引作业

每次在单个表上创建或更新索引时,都会创建一个新的索引作业。如需查看作业的相关信息,请查询 INFORMATION_SCHEMA.JOBS* 视图。通过在查询的 WHERE 子句中设置 job_type IS NULL AND SEARCH(job_id, '`search_index`'),即可对索引作业进行过滤。以下示例列出了项目 my_project 中最新的五个索引作业:

SELECT *
FROM
 region-us.INFORMATION_SCHEMA.JOBS
WHERE
  project_id  = 'my_project'
  AND job_type IS NULL
  AND SEARCH(job_id, '`search_index`')
ORDER BY
 creation_time DESC
LIMIT 5;

选择预留大小

要为预留选择适当的槽数,您应该考虑何时运行索引管理作业、它们使用的槽数以及您的使用量随时间变化的情况。在以下情况下,BigQuery 会触发索引管理作业:

  • 您在表上创建索引。
  • 编入索引的表中的数据被修改。
  • 表的架构发生了变化,并且此变化影响将哪些列编入索引。
  • 索引数据和元数据会定期优化或更新。

表的索引管理作业所需的槽数取决于以下因素:

  • 表的大小
  • 将数据注入到表中的速率
  • 应用于表的 DML 语句的速率
  • 构建和维护索引的可接受延迟时间
  • 索引的复杂程度,通常由数据属性(例如重复字词数量)决定
初始估算

以下估算可帮助您估计预留所需的槽数。由于索引工作负载的高度可变性,因此在开始将数据编入索引后,应该重新评估您的要求。

  • 现有数据:对于 1000 个槽的预留,BigQuery 中的现有表可以每秒最多 4 GiB(平均约为每天 336 TiB)的速度编入索引。
  • 新注入的数据:对于新注入的数据,索引通常占用更多资源,因为表及其索引需要经过多轮转换优化。平均而言,与将初次回填的相同数据编入索引相比,将新注入的数据编入索引编入索引将需要三倍的资源数量。
  • 不经常修改的数据:对于连续执行的索引维护,进行少量数据修改或根本没有数据修改的已编入索引的表所需的资源大幅减少。对于将初次回填的相同数据编入索引,建议在开始时保持所需槽数的 1/5,且不得少于 250 个槽。
  • 在编制索引的过程中,保留的槽数大致随着预留大小而线性扩缩。 但是对于编制索引,我们不建议使用少于 250 个槽的预留,因为这可能会导致索引进度变慢。
  • 这些估算值可能会随着特征、优化和实际用量的变化而变化。
  • 如果贵组织的总表大小超出了地区的索引限制,则应为索引操作持续分配非零预留。否则,索引操作可能会回退到默认层级,从而导致意外删除所有索引。
监控用量和进度

评估高效地运行索引管理作业所需的槽数的最佳方法是监控槽利用率并相应地调整预留大小。以下查询生成索引管理作业的每日槽使用量。us-west1 地区仅包含过去 30 天的数据:

SELECT
  TIMESTAMP_TRUNC(job.creation_time, DAY) AS usage_date,
  -- Aggregate total_slots_ms used for index-management jobs in a day and divide
  -- by the number of milliseconds in a day. This value is most accurate for
  -- days with consistent slot usage.
  SAFE_DIVIDE(SUM(job.total_slot_ms), (1000 * 60 * 60 * 24)) AS average_daily_slot_usage
FROM
  `region-us-west1`.INFORMATION_SCHEMA.JOBS job
WHERE
  project_id = 'my_project'
  AND job_type IS NULL
  AND SEARCH(job_id, '`search_index`')
GROUP BY
  usage_date
ORDER BY
  usage_date DESC
limit 30;

如果没有足够的槽来运行索引管理作业,则索引可能会与其表不同步,并且索引作业可能会失败。在此情况下,BigQuery 从头开始重新构建索引。为避免产生不同步索引,请确保您有足够的槽来支持数据注入和优化中的索引更新。如需详细了解如何监控槽使用量,请参阅管理员资源图表

最佳做法

  • 搜索索引专为大型表而设计。搜索索引的性能随表的大小而提升。
  • 不要将仅包含极少量唯一值的列编入索引。
  • 请勿将您从未打算调用 SEARCH 函数的列编入索引。
  • ALL COLUMNS 上创建搜索索引时要小心。每次添加包含 STRINGJSON 数据的列时,该列都会编入索引。
  • 您应该在生产应用中使用您自己的预留进行索引管理。如果您选择为索引管理作业使用默认共享槽池,则需要遵守每个组织的容量限制

删除搜索索引

如果不再需要搜索索引,或者想要更改某个表中要编入索引的列,可以删除该表的当前索引。为此,请使用 DROP SEARCH INDEX DDL 语句

如果删除编入索引的表,其索引也会自动删除。

示例:

DROP SEARCH INDEX my_index ON dataset.simple_table;

后续步骤