索引和地理分区

Spanner 提供了多种类型的索引,可用于提高查询性能。 为架构和查询模式选择正确的索引类型至关重要,对于使用地理位置分区功能的数据库尤其如此。本页介绍了不同索引类型的优势,以及在选择和使用具有地理分区的 Spanner 索引方面的最佳实践。

索引类型

Spanner 支持全局、本地和远程索引。每种类型都有不同的性能特征和使用情形。对于地理位置分区数据库,了解这些索引类型非常重要。选择合适的索引有助于优化数据库架构和查询,从而显著缩短地理分区数据库的延迟时间。对于不使用地理分区的数据库,了解这些索引类型的重要性较低,因为它们都存储在默认放置位置,并且具有相似的性能特征。

全局索引

全局索引是 Spanner 中的默认索引类型。索引数据存储在默认分区中,该分区可能与表的数据不在同一位置。在地理位置分区表上创建全局索引可能会导致涉及索引列的写入操作的写入延迟时间显著增加,尤其是当索引的默认分区写入仲裁与正在写入的表行的写入仲裁相差甚远时。您可以使用附近的只读副本以及读取租约区域或过时数据读取来缓解全局索引的读取延迟时间。

全局索引具有以下特征:

  • 它们可加快查询速度,否则查询将需要执行全表扫描,并且无论位置如何,它们都可在所有表行中强制执行唯一性。
  • 它们适用于需要在整个数据库中具有唯一值的列。
  • 它们可加快按索引列进行过滤或排序的查询的速度。

以下是全局唯一索引的示例:

CREATE UNIQUE INDEX idx_customer_email ON customer(email);

本地索引

本地索引在被编入索引的表的父层次结构中交错。主键列名称和类型必须与索引表匹配。

本地索引具有以下特征:

  • 它们将索引数据存储在与被索引数据相同的分区中。写入延迟时间取决于特定放置位置的写入仲裁,而不是默认放置位置的写入仲裁。

  • 它们可为以特定键前缀为目标的查询提供最低延迟时间,因为索引和表数据都位于同一位置。

如需创建本地索引,请在父表中交错索引。如果您使用 UNIQUE 本地索引,则唯一性仅在特定父行内强制执行,而不是在整个表中强制执行。

以下示例展示了如何在父表 locations 中创建一个与表 customer 交错的本地索引:

GoogleSQL

-- Create locations placement table
CREATE TABLE locations (
  location STRING(MAX) NOT NULL PLACEMENT KEY,
) PRIMARY KEY(location);

-- Create customer table interleaved in the locations table
CREATE TABLE customer (
  location STRING(MAX) NOT NULL,
  customerId  INT64 NOT NULL,
  email STRING(MAX),
  webcookie STRING(64),
) PRIMARY KEY(location, customerId), INTERLEAVE IN PARENT locations;

-- Create a local index on the interleaved customer table
CREATE INDEX idx_customer_email_local ON customer(location, email),
INTERLEAVE IN locations;

PostgreSQL

-- Create locations placement table
CREATE TABLE locations (
  location varchar NOT NULL PLACEMENT KEY PRIMARY KEY
);

-- Create customer table interleaved in the locations table
CREATE TABLE customer (
  location varchar NOT NULL,
  customerId  BIGINT NOT NULL,
  email varchar(1024),
  webcookie varchar(64),
  PRIMARY KEY(location, customerId)
) INTERLEAVE IN PARENT locations;

-- Create a local index on the interleaved customer table
CREATE INDEX idx_customer_email_local ON customer(location, email)
INTERLEAVE IN locations;

在读写事务中查询数据时,您必须在查询中指定索引表的主键前缀。否则,查询可能需要执行全表扫描。例如:

GoogleSQL

-- The location (the index key prefix) must be specified
SELECT *
FROM customer
WHERE location= @location AND email= @email;

PostgreSQL

-- The location (the index key prefix) must be specified
SELECT *
FROM customer
WHERE location= @location AND email= @email;

如需了解无需指定位置的优化,请参阅将全局唯一索引与本地或远程索引搭配使用

当展示位置表基于实体,并且您想要为特定实体或子实体中的数据编制索引时,本地索引也很有用。例如:

GoogleSQL

-- Create entity based customer placement table
CREATE TABLE customer (
  customerId INT64 NOT NULL,
  email STRING(MAX),
  webcookie STRING(64),
  location STRING(MAX) NOT NULL PLACEMENT KEY
) PRIMARY KEY(customerId);

-- Create customerOrders child table
CREATE TABLE customerOrders (
  customerId INT64 NOT NULL,
  orderId INT64 NOT NULL,
  orderName STRING(MAX) NOT NULL
) PRIMARY KEY(customerId, orderId), INTERLEAVE IN PARENT customer;

-- Create a local index on the interleaved child table
CREATE INDEX idx_order_local ON customerOrders(customerId, orderName),
INTERLEAVE IN customer;

PostgreSQL

-- Create entity based customer placement table
CREATE TABLE customer (
  customerId BIGINT NOT NULL PRIMARY KEY,
  email varchar(1024),
  webcookie varchar(64),
  location varchar NOT NULL PLACEMENT KEY
);

-- Create customerOrders child table
CREATE TABLE customerOrders (
  customerId BIGINT NOT NULL,
  orderId BIGINT NOT NULL,
  orderName varchar(1024) NOT NULL,
  PRIMARY KEY(customerId, orderId)
) INTERLEAVE IN PARENT customer;

-- Create a local index on the interleaved child table
CREATE INDEX idx_order_local ON customerOrders(customerId, orderName)
INTERLEAVE IN customer;

远程索引

远程索引在不是被索引表的交织层次结构中祖先的表下交织索引数据。主键类型必须与相应索引列的类型一致,但名称可以不同。

使用地理位置分区时,您只能在自动管理的根放置位置表下交错放置远程索引。此表中的每一行对应数据库中的一个展示位置。

远程索引具有以下特征:

  • 如果表的主键未使用放置位置键作为前缀,但您希望根据放置位置键在地理位置上将索引分区并置,那么这些函数就特别有用。
  • 它们仅支持对放置表中的列进行索引,而不支持对交织子表中的任何列进行索引。

如需将远程索引与地理分区搭配使用,请在 ALTER DATABASE DDL 语句中设置 auto_managed_root_placement_table_name 选项,以创建根放置位置表。

  1. 使用 ALTER DATABASE DDL 语句创建根放置位置表。

    GoogleSQL

    ALTER DATABASE DATABASE_NAME SET OPTIONS
      (auto_managed_root_placement_table_name="TABLE_NAME");
    

    替换以下内容:

    • DATABASE_NAME:数据库的名称。
    • TABLE_NAME:要创建的表的名称。我们建议使用名称 root_placement_table

    例如,以下命令会创建一个名为 root_placement_table 的表。

    ALTER DATABASE example_db SET OPTIONS
      (auto_managed_root_placement_table_name='root_placement_table');
    

    创建根放置位置表后,Spanner 会创建一个内部表,并在您创建或删除放置位置时自动插入和删除行。以下是由 Spanner 创建的系统定义的布置表示例,其中示例表名称设置为 root_placement_table。(请勿运行此示例。)

    // Automatically generated after you run the previous example.
    // Don't put this in your schema explicitly.
    CREATE TABLE root_placement_table (
    location STRING(MAX) NOT NULL PLACEMENT KEY
    ) PRIMARY KEY(location);
    

    PostgreSQL

    ALTER DATABASE DATABASE_NAME SET
      spanner.auto_managed_root_placement_table_name='TABLE_NAME';
    

    替换以下内容:

    • DATABASE_NAME:数据库的名称。
    • TABLE_NAME:要创建的表的名称。

    例如,如需创建用作交错根的 root_placement_table 表,请运行以下命令:

    ALTER DATABASE example_db SET
      spanner.auto_managed_root_placement_table_name='root_placement_table';
    

    创建根放置位置表后,Spanner 会创建一个内部表,并在您创建或删除放置位置时自动插入和删除行。以下是由 Spanner 创建的系统定义的布置表示例,其中示例表名称设置为 root_placement_table。(请勿运行此示例。)

    // Automatically generated after you run the previous example.
    // Don't put this in your schema explicitly.
    CREATE TABLE root_placement_table (
      location varchar NOT NULL PLACEMENT KEY,
      PRIMARY KEY (location)
    );
    
  2. 在自动管理的 root_placement_table 表下创建交织的远程索引。

    GoogleSQL

    -- Create a customer table with a primary key that is not the location
    CREATE TABLE customer (
      customerId INT64 NOT NULL ,
      email STRING(MAX),
      webcookie STRING(64),
      location STRING(MAX) NOT NULL PLACEMENT KEY,
    ) PRIMARY KEY(customerId);
    
    -- Create a remote index on the customer table
    CREATE INDEX idx_customer_email_remote ON customer(location, email),
    INTERLEAVE IN root_placement_table;
    

    PostgreSQL

    -- Create a customer table with a primary key that is not the location
    CREATE TABLE customer (
      customerId BIGINT NOT NULL PRIMARY KEY,
      email varchar(1024),
      webcookie varchar(64),
      location varchar NOT NULL PLACEMENT KEY
    );
    
    -- Creates a remote index on the customer table
    CREATE INDEX idx_customer_email_remote ON customer(location, email)
    INTERLEAVE IN root_placement_table;
    
  3. 在读写事务中查询数据时,请在查询谓词中指定索引的键前缀,这样就不需要进行全表扫描。例如:

    GoogleSQL

    -- Specify the location (the index key prefix) in query
    SELECT *
    FROM customer
    WHERE location= @location AND email= @email;
    

    如需了解无需指定位置的优化,请参阅具有本地或远程索引的全局唯一索引部分

    PostgreSQL

    -- Specify the location (the index key prefix) in query
    SELECT *
    FROM customer
    WHERE location= @location AND email= @email;
    

    如需了解无需指定位置的优化,请参阅具有本地或远程索引的全局唯一索引部分。

针对全局唯一索引的优化

当您使用全局唯一索引时,在以下使用情形中,Spanner 可能会通过基于启发式的优化来缩短查询延迟时间:

  • 将全局唯一索引与本地或远程索引搭配使用时
  • 使用具有部分主键的全局唯一索引时

以下各部分介绍了 Spanner 在每种使用情形下可能会如何应用优化。

具有本地或远程索引的全局唯一索引

为了缩短本地查询延迟时间,当全局唯一索引与本地或远程索引结合使用时,Spanner 可能会启动基于启发法的优化。

此优化功能可最大限度地缩短区域内查询的延迟时间,即使未指定地理位置分区数据的位置也是如此。它会猜测放置位置与客户端所在位置相同,并绕过全局索引的默认分区,或者消除过滤后的全表扫描的必要性。如果客户主要访问存储在自己区域内的数据,这种方法尤其有用。

如果区域内查询延迟时间是主要考虑因素,并且您可以容忍写入延迟时间有所增加,那么混合使用不同类型的索引会很有帮助。即使您未在查询中指定位置,结合使用不同的索引类型也能提高区域内查询的性能。

此优化要求您在同一列上创建全局唯一索引和相应的本地或远程索引。索引的数据必须具有全局唯一性。如果满足以下条件,Spanner 会将此优化应用于您的查询:

  • 您不知道主键前缀,并且未指定数据的位置。
  • 您的请求源自数据放置位置的默认主要区域(包含本地或远程索引分片)。

Spanner 会通过以下方式应用优化:

  • 如果触发了优化,并且在本地放置位置找到了相应行:鉴于全局唯一索引,Spanner 无需搜索其他位置。您的查询存在区域内延迟。
  • 如果初始位置搜索未返回任何行:这表示相应查询不是区域内查询。Spanner 会回退到使用全局索引。

以下示例会创建全局唯一索引和本地索引:

GoogleSQL

CREATE UNIQUE INDEX idx_customer_email ON customer(email);
CREATE INDEX idx_customer_email_local ON customer(location, email), INTERLEAVE IN locations;

PostgreSQL

CREATE UNIQUE INDEX idx_customer_email ON customer(email);
CREATE INDEX idx_customer_email_local ON customer(location, email) INTERLEAVE IN locations;

以下示例会创建一个全局唯一索引和一个远程索引:

GoogleSQL

CREATE UNIQUE INDEX idx_customer_email ON customer(email);
CREATE INDEX idx_customer_email_remote ON customer(location, email), INTERLEAVE IN root_placement_table;

PostgreSQL

CREATE UNIQUE INDEX idx_customer_email ON customer(email);
CREATE INDEX idx_customer_email_remote ON customer(location, email) INTERLEAVE IN root_placement_table;

根据上一个示例中的索引,以下示例查询具有区域内延迟时间:

GoogleSQL

SELECT *
FROM customer
WHERE email= @email;

PostgreSQL

SELECT *
FROM customer
WHERE email= @email;

基于部分主键的全局唯一索引

当对部分主键使用全局唯一索引时,Spanner 可以应用类似于使用具有本地或远程索引的全局唯一索引中详述的优化。

以下示例在父表 locations 中创建了 customer 交错表,然后在 customerId 列上创建了全局唯一索引。

GoogleSQL

-- Create locations placement table
CREATE TABLE locations (
location STRING(MAX) NOT NULL PLACEMENT KEY,
) PRIMARY KEY(location);

-- Create customer table interleaved in the locations table
CREATE TABLE customer (
  location STRING(MAX) NOT NULL,
  customerId  INT64 NOT NULL,
  email STRING(MAX),
  webcookie STRING(64),
) PRIMARY KEY(location, customerId), INTERLEAVE IN PARENT locations;

-- Create global unique index on customerId column
CREATE UNIQUE INDEX idx_customer_customerid ON customer(customerId);

PostgreSQL

-- Create locations placement table
CREATE TABLE locations (
  location varchar NOT NULL PLACEMENT KEY PRIMARY KEY
);

-- Create customer table interleaved in the locations table
CREATE TABLE customer (
  location varchar NOT NULL,
  customerId  BIGINT NOT NULL,
  email varchar(1024),
  webcookie varchar(64),
  PRIMARY KEY(location, customerId)
) INTERLEAVE IN PARENT locations;

-- Create global unique index on customerId column
CREATE UNIQUE INDEX idx_customer_customerid ON customer(customerId);

此优化适用于如下查询:

GoogleSQL

SELECT * FROM customer WHERE customerId= @customerId;

PostgreSQL

SELECT * FROM customer WHERE customerId= @customerId;

如果您未创建全局唯一索引,则此查询可能需要进行全表扫描。如果您未使用全局唯一索引,则需要在查询中添加位置过滤条件,以实现良好的查询延迟时间:

GoogleSQL

SELECT * FROM customer WHERE location = @location AND customerId= @customerId;

PostgreSQL

SELECT * FROM customer WHERE location = @location AND customerId= @customerId;

选择索引类型以实现最佳延迟时间的一般准则

您选择的索引类型会直接影响查询延迟时间。索引数据相对于表数据的位置是地理位置分区工作负载性能的主要因素。

本部分介绍了如何选择全局索引、本地索引和远程索引。

何时选择全局索引

如果您的工作负载可以容忍相关的读写延迟,或者您需要对索引列强制执行全局唯一性,请使用全局索引。

选择全局索引时,请考虑以下事项:

  • 客户端与默认写入法定人数的领导者之间的距离,以及法定人数的默认延迟时间,决定了写入延迟时间的增加幅度。此效应专门限于涉及索引列的操作,例如插入行或更新索引列。
  • 添加只读副本或使用读取租约可以缓解读取延迟时间增加的问题:
    • 在地理位置上靠近主实例的位置添加只读副本可以缩短过时数据读取延迟时间。
    • 添加只读副本并使用读取租约区域可以缩短强一致性读取延迟。如果您添加只读副本而不使用读取租约区域,强一致性读取延迟时间不会缩短,但读取吞吐量可以增加。
    • Spanner 始终从主节点提供悲观事务性读取。向默认放置位置添加副本无助于对默认放置位置中的数据进行悲观事务性读取。
  • 全局索引(包括键和存储值)放置在默认布置中,该布置不提供布置级数据留存。如需了解详情,请参阅 Spanner 数据驻留概览

何时选择本地索引和远程索引

在选择本地索引和远程索引时,请考虑以下事项:

  • 本地索引和远程索引可提供放置在本地的读写性能,但会牺牲唯一索引列的全局唯一性和排序属性。相反,本地索引和远程索引可提供父行(它们交织在其中)内索引列的排序和唯一性。
  • 使用本地或远程索引时,您必须在查询谓词中包含放置位置,除非还存在一个全局唯一索引,让 Spanner 可以猜测本地放置位置。否则,查询计划和性能将不确定。根据查询统计信息,Spanner 可能会执行基表扫描或从各个放置位置的索引中分散收集数据,从而增加延迟时间。

何时选择具有本地或远程索引的全局唯一索引

在选择将全局唯一索引与本地或远程索引相结合时,请考虑以下事项:

  • 如果不知道具体放置位置,请将全局唯一索引与本地或远程索引结合使用。如果大多数查询都来自地理位置与所请求数据放置位置相同的区域内的服务,那么这种方法非常适合。
  • 写入全局索引时,写入延迟时间会受到额外的默认写入仲裁延迟时间的影响。
  • 借助基于启发式的优化,查询由本地索引分片提供服务,并且大多数时候都表现出区域内延迟。

针对特定架构设计选择索引的详细指南

最佳索引策略取决于表的主键结构和应用的查询模式。本部分针对三种常见的架构设计提供了有关选择合适索引类型的指导:

  • 使用实体作为主键的架构
  • 使用位置作为主键的架构
  • 使用与位置相关的值作为主键的架构

架构设计:以实体作为主键

如果您的架构使用实体作为主键,则应根据查询中是否指定了位置信息来选择索引编制策略。

如果实体(例如 customerID)是主键,而单独的非键列(例如 location)是放置键,请根据查询模式确定放置表的索引编制策略。(如果实体插入延迟时间是一个问题,请勿使用实体作为布置表的主键。)

如果您想为特定实体(例如 customerID)下的数据编制索引,请使用本地索引。数据在实体内编入索引并排序,但不会跨实体编入索引和排序。例如,如果您想按日期为每位客户的订单编制索引,则可以在 customerID 身份下创建一个交错式本地索引。

如果查询中始终知道位置信息,请使用以下策略之一:

  • 如果您的查询始终知道位置,并且不需要强制执行全局唯一性,请使用远程索引。这些索引可为读取和写入操作提供区域内延迟。

    远程索引仅支持对放置表中的列进行索引,不支持对交错表中的列进行索引。远程索引必须交错放置在根放置表下。远程索引会为相应布置的所有布置行中的数据编制索引。

如果查询中总是知道位置信息,请使用以下某种策略:

  • 如果索引列具有全局唯一性,请创建全局唯一索引来强制执行该唯一性。

    如需实现低延迟强读取,请创建远程索引以及全局唯一索引。

    在这种组合下,写入操作可能会产生跨区域延迟,而指定了位置(使用 WHERE location= @location)的查询则可以通过使用远程索引来受益于区域内延迟。对于未指定位置的查询,Spanner 会先在本地搜索,然后使用基于启发式的优化。如果找不到相应数据,则会回退到全局索引。

    如果您使用的是读取租约区域,并且在与数据相同的区域中拥有默认分区的只读副本,则无需远程索引,因为读取租约区域已经为全局索引的读取提供了较低的强读取延迟时间。

  • 如果您的查询未指定位置,且索引列不具有全局唯一性,则仅创建全局(非唯一)索引。在这种情况下,添加本地或远程索引不会缩短读取延迟时间,因为即使 Spanner 在本地位置找到数据,也无法确定在其他位置是否存在匹配的数据。

架构设计:将位置信息作为主键

location 列同时用作主键和放置键时,您选择的索引取决于交叉延迟问题以及查询是否始终指定位置。

  • 如果您不担心跨区域延迟,或者需要全局唯一性,请使用全局索引。
  • 如果您担心跨区域延迟时间,并且查询始终包含位置信息,并且您不需要 Spanner 强制执行全局唯一性,则只需使用本地索引。这样可确保读取和写入操作的本地延迟。
  • 如果跨区域查询延迟时间是一个问题,但跨区域写入延迟时间可以接受,并且位置信息并不总是已知,那么以下策略适用:

    条件 建议
    按全局唯一的部分主键查询

    创建唯一的全局索引以强制执行唯一性。不需要本地索引,因为主键可执行类似的功能。 应用基于启发法的优化。首先,Spanner 会检查具有本地位置的全主键,然后再回退到全局索引。

    如需查看示例,请参阅部分主键上的全局唯一索引

    按非键全局唯一列查询

    创建唯一的全局索引以强制执行唯一性。

    对于区域内延迟时间,可能会出现以下情况:

    • 在与全局索引相同的列上创建本地索引。应用基于启发法的优化。全球索引和本地索引的组合可为区域内强一致性查询和过时查询提供低延迟,而写入和跨区域查询及写入则具有跨区域延迟。
    • 如果默认分区的读写副本或只读副本与您的数据位于同一区域,则:
      • 如果您只需要过时读取的区域内延迟,而不需要强一致性读取的区域内延迟,则无需本地索引。本地副本可提供区域内延迟时间。
      • 如果需要实现强读取的区域内延迟,您可以在与全局索引相同的列上创建本地索引,或者使用读取租约区域。读取租约区域以牺牲写入延迟为代价,提供较低的强一致性读取延迟。
    已编入索引的列不具有全局唯一性 仅创建全局索引。本地索引不会缩短读取延迟时间,因为 Spanner 可能需要检查所有位置。

如果这三种情形不适用于您的使用情形,您可能需要通过持续提供位置信息来牺牲应用简洁性或写入延迟时间。

如果表的主键基于位置相关的值,但不是直接的放置键列(例如,当放置数量少于国家/地区数量时,使用 country 作为主键),则可以使用全局索引或本地索引(交错在 country 列下)。不过,对于放置在此类放置表下的任何交织表,都不支持远程索引。

在此场景中,基于启发式的优化不支持本地索引。因此,只有当查询明确指定主键前缀时,才能实现本地延迟。