Spanner 使用入门 (C++)


目标

本教程将介绍如何使用适用于 C++ 的 Spanner 客户端库完成以下步骤:

  • 创建 Spanner 实例和数据库。
  • 写入、读取数据库中的数据和对数据执行 SQL 查询。
  • 更新数据库架构。
  • 使用读写事务更新数据。
  • 向数据库添加二级索引。
  • 使用索引来读取数据和对数据执行 SQL 查询。
  • 使用只读事务检索数据。

费用

本教程使用 Spanner,它是 Google Cloud 的收费组件。如需了解 Spanner 的使用费用,请参阅价格

准备工作

完成设置中介绍的步骤,包括创建和设置默认 Google Cloud 项目、启用结算功能、启用 Cloud Spanner API 以及设置 OAuth 2.0 来获取身份验证凭据以使用 Cloud Spanner API。

尤其要确保运行 gcloud auth application-default login,以便使用身份验证凭据设置本地开发环境。

准备本地 C++ 环境

  1. 将示例应用代码库克隆到本地机器:

    git clone https://github.com/googleapis/google-cloud-cpp $HOME/google-cloud-cpp
    
  2. 按照这些说明安装适用于 Linux 的 Bazel。

  3. 切换到包含 Spanner 示例代码的目录:

    cd $HOME/google-cloud-cpp
    
  4. 使用以下命令构建示例:

    bazel build //google/cloud/spanner/samples:samples
    
  5. google-cloud-cpp 项目设置身份验证和授权。

    gcloud auth application-default login
    
  6. 创建一个名为 GCLOUD_PROJECT 的环境变量。将 [MY_PROJECT_ID] 替换为您的 Google Cloud 项目 ID。您可以在项目的欢迎页面中找到此 ID。

    export GCLOUD_PROJECT=[MY_PROJECT_ID]
    

创建实例

首次使用 Spanner 时,您必须创建一个实例,用于分配 Spanner 数据库所使用的资源。创建实例时,请选择一个实例配置(决定数据的存储位置),同时选择要使用的节点数(决定实例中服务资源和存储资源的数量)。

执行以下命令,在区域 us-central1 中创建具有 1 个节点的 Spanner 实例:

gcloud spanner instances create test-instance --config=regional-us-central1 \
    --description="Test Instance" --nodes=1

请注意,此命令将创建一个具有以下特征的实例:

  • 实例 ID 为 test-instance
  • 显示名为 Test Instance
  • 实例配置为 regional-us-central1(单区域配置将数据存储在单个区域中,而多区域配置则将数据分布在多个区域中。如需了解详情,请参阅关于实例。)
  • 节点数为 1(node_count 对应于实例中数据库可用的服务资源和存储资源的数量。如需了解详情,请参阅节点和处理单元。)

您应该会看到:

Creating instance...done.

浏览示例文件

示例代码库包含一个示例,其展示了如何在 C++ 中使用 Spanner。

请浏览 google/cloud/spanner/samples/samples.cc 文件,其中说明了如何创建数据库和修改数据库架构。该数据使用架构和数据模型页面中显示的示例架构。

创建数据库

通过在命令行运行以下命令,在名为 test-instance 的实例中创建名为 example-db 的数据库。

bazel run //google/cloud/spanner/samples:samples -- \
    create-database $GCLOUD_PROJECT test-instance example-db

您应该会看到:

Created database [projects/${GCLOUD_PROJECT}/instances/test-instance/databases/example-db]

以下代码会在数据库中创建一个数据库和两个表。

void CreateDatabase(google::cloud::spanner_admin::DatabaseAdminClient client,
                    std::string const& project_id,
                    std::string const& instance_id,
                    std::string const& database_id) {
  google::cloud::spanner::Database database(project_id, instance_id,
                                            database_id);
  google::spanner::admin::database::v1::CreateDatabaseRequest request;
  request.set_parent(database.instance().FullName());
  request.set_create_statement("CREATE DATABASE `" + database.database_id() +
                               "`");
  request.add_extra_statements(R"""(
      CREATE TABLE Singers (
          SingerId   INT64 NOT NULL,
          FirstName  STRING(1024),
          LastName   STRING(1024),
          SingerInfo BYTES(MAX),
          FullName   STRING(2049)
              AS (ARRAY_TO_STRING([FirstName, LastName], " ")) STORED
      ) PRIMARY KEY (SingerId))""");
  request.add_extra_statements(R"""(
      CREATE TABLE Albums (
          SingerId     INT64 NOT NULL,
          AlbumId      INT64 NOT NULL,
          AlbumTitle   STRING(MAX)
      ) PRIMARY KEY (SingerId, AlbumId),
          INTERLEAVE IN PARENT Singers ON DELETE CASCADE)""");
  auto db = client.CreateDatabase(request).get();
  if (!db) throw std::move(db).status();
  std::cout << "Database " << db->name() << " created.\n";
}

下一步是将数据写入数据库。

创建数据库客户端

您必须先创建一个 Client,然后才能执行读写操作:

auto database = spanner::Database(project_id, instance_id, database_id);
auto connection = spanner::MakeConnection(database);
auto client = spanner::Client(connection);

借助 Client,您可以对 Spanner 数据库执行读取、写入、查询和执行事务。通常,您可以在应用启动时创建 Client,然后重复使用该 Client 来读取、写入和执行事务。每个客户端均使用 Spanner 中的资源。Client 的 destructor 将自动清理 Client 资源,包括网络连接。

如需详细了解 Client,请参阅 Google Cloud Spanner C++ 参考

使用 DML 写入数据

您可以在读写事务中使用数据操纵语言 (DML) 插入数据。

使用 Client::ExecuteDml() 函数来执行 DML 语句。

void DmlGettingStartedInsert(google::cloud::spanner::Client client) {
  using ::google::cloud::StatusOr;
  namespace spanner = ::google::cloud::spanner;

  auto commit_result = client.Commit(
      [&client](spanner::Transaction txn) -> StatusOr<spanner::Mutations> {
        auto insert = client.ExecuteDml(
            std::move(txn),
            spanner::SqlStatement(
                "INSERT INTO Singers (SingerId, FirstName, LastName) VALUES"
                " (12, 'Melissa', 'Garcia'),"
                " (13, 'Russell', 'Morales'),"
                " (14, 'Jacqueline', 'Long'),"
                " (15, 'Dylan', 'Shaw')"));
        if (!insert) return std::move(insert).status();
        return spanner::Mutations{};
      });
  if (!commit_result) throw std::move(commit_result).status();
  std::cout << "Insert was successful [spanner_dml_getting_started_insert]\n";
}

使用 getting-started-insert 参数运行示例。

bazel run //google/cloud/spanner/samples:samples -- \
    getting-started-insert $GCLOUD_PROJECT test-instance example-db

您应该会看到:

Insert was successful [spanner_dml_getting_started_insert]

使用变更写入数据

您还可以使用变更插入数据。

您可以使用 Client 对象来写入数据。Client::Commit() 函数跨数据库中的列、行和表,为在单个逻辑时间点以原子方式执行的写入创建并提交事务。

此代码演示了如何使用变更写入数据:

void InsertData(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;
  auto insert_singers = spanner::InsertMutationBuilder(
                            "Singers", {"SingerId", "FirstName", "LastName"})
                            .EmplaceRow(1, "Marc", "Richards")
                            .EmplaceRow(2, "Catalina", "Smith")
                            .EmplaceRow(3, "Alice", "Trentor")
                            .EmplaceRow(4, "Lea", "Martin")
                            .EmplaceRow(5, "David", "Lomond")
                            .Build();

  auto insert_albums = spanner::InsertMutationBuilder(
                           "Albums", {"SingerId", "AlbumId", "AlbumTitle"})
                           .EmplaceRow(1, 1, "Total Junk")
                           .EmplaceRow(1, 2, "Go, Go, Go")
                           .EmplaceRow(2, 1, "Green")
                           .EmplaceRow(2, 2, "Forever Hold Your Peace")
                           .EmplaceRow(2, 3, "Terrified")
                           .Build();

  auto commit_result =
      client.Commit(spanner::Mutations{insert_singers, insert_albums});
  if (!commit_result) throw std::move(commit_result).status();
  std::cout << "Insert was successful [spanner_insert_data]\n";
}

使用 insert-data 参数运行示例。

bazel run //google/cloud/spanner/samples:samples -- \
    insert-data $GCLOUD_PROJECT test-instance example-db

您应该会看到:

Insert was successful [spanner_insert_data]

使用 SQL 查询数据

Spanner 支持用于读取数据的 SQL 接口,您可以使用 Google Cloud CLI 在命令行上访问该接口,也可以使用适用于 C++ 的 Spanner 客户端库以编程方式访问该接口。

在命令行中

执行以下 SQL 语句,读取 Albums 表中所有列的值:

gcloud spanner database execute-sql example-db --instance=test-instance \ --sql='SELECT SingerId, AlbumId, AlbumTitle FROMAlbums'

结果应为:

SingerId AlbumId AlbumTitle
1        1       Total Junk
1        2       Go, Go, Go
2        1       Green
2        2       Forever Hold Your Peace
2        3       Terrified

使用适用于 C++ 的 Spanner 客户端库

除了在命令行上执行 SQL 语句外,还可以使用适用于 C++ 的 Spanner 客户端库以编程方式发出相同的 SQL 语句。

您可以使用 Client::ExecuteQuery() 函数运行 SQL 查询。下面演示了如何发出查询并访问数据:

void QueryData(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;

  spanner::SqlStatement select("SELECT SingerId, LastName FROM Singers");
  using RowType = std::tuple<std::int64_t, std::string>;
  auto rows = client.ExecuteQuery(std::move(select));
  for (auto& row : spanner::StreamOf<RowType>(rows)) {
    if (!row) throw std::move(row).status();
    std::cout << "SingerId: " << std::get<0>(*row) << "\t";
    std::cout << "LastName: " << std::get<1>(*row) << "\n";
  }

  std::cout << "Query completed for [spanner_query_data]\n";
}

使用 query_data 参数运行示例。

bazel run //google/cloud/spanner/samples:samples -- \
    query-data $GCLOUD_PROJECT test-instance example-db

您应该会看到以下结果:

SingerId: 1     LastName: Richards
SingerId: 2     LastName: Smith
SingerId: 3     LastName: Trentor
SingerId: 4     LastName: Martin
SingerId: 5     LastName: Lomond
SingerId: 12    LastName: Garcia
SingerId: 13    LastName: Morales
SingerId: 14    LastName: Long
SingerId: 15    LastName: Shaw

使用 SQL 参数进行查询

如果您的应用具有频繁执行的查询,您可以通过参数化来提高其性能。生成的参数查询可以缓存并重复使用,从而降低编译成本。如需了解详情,请参阅使用查询参数加快频繁执行的查询

以下示例展示了如何在 WHERE 子句中使用参数,查询包含 LastName 的特定值的记录。

void QueryWithParameter(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;

  spanner::SqlStatement select(
      "SELECT SingerId, FirstName, LastName FROM Singers"
      " WHERE LastName = @last_name",
      {{"last_name", spanner::Value("Garcia")}});
  using RowType = std::tuple<std::int64_t, std::string, std::string>;
  auto rows = client.ExecuteQuery(std::move(select));
  for (auto& row : spanner::StreamOf<RowType>(rows)) {
    if (!row) throw std::move(row).status();
    std::cout << "SingerId: " << std::get<0>(*row) << "\t";
    std::cout << "FirstName: " << std::get<1>(*row) << "\t";
    std::cout << "LastName: " << std::get<2>(*row) << "\n";
  }

  std::cout << "Query completed for [spanner_query_with_parameter]\n";
}

使用 query-with-parameter 命令运行示例。

bazel run //google/cloud/spanner/samples:samples -- \
    query-with-parameter $GCLOUD_PROJECT test-instance example-db

您应该会看到以下结果:

SingerId: 12    FirstName: Melissa      LastName: Garcia

使用读取 API 读取数据

除了 Spanner 的 SQL 接口之外,Spanner 还支持读取接口。

您可以使用 Client::Read() 函数从数据库中读取行。使用 KeySet 对象定义要读取的键和键范围的集合。

下面演示了如何读取数据:

void ReadData(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;

  auto rows = client.Read("Albums", google::cloud::spanner::KeySet::All(),
                          {"SingerId", "AlbumId", "AlbumTitle"});
  using RowType = std::tuple<std::int64_t, std::int64_t, std::string>;
  for (auto& row : spanner::StreamOf<RowType>(rows)) {
    if (!row) throw std::move(row).status();
    std::cout << "SingerId: " << std::get<0>(*row) << "\t";
    std::cout << "AlbumId: " << std::get<1>(*row) << "\t";
    std::cout << "AlbumTitle: " << std::get<2>(*row) << "\n";
  }

  std::cout << "Read completed for [spanner_read_data]\n";
}

使用 read-data 参数运行示例。

bazel run //google/cloud/spanner/samples:samples -- \
    read-data $GCLOUD_PROJECT test-instance example-db

您看到的输出结果应该类似于以下内容:

SingerId: 1, AlbumId: 1, AlbumTitle: Total Junk
SingerId: 1, AlbumId: 2, AlbumTitle: Go, Go, Go
SingerId: 2, AlbumId: 1, AlbumTitle: Green
SingerId: 2, AlbumId: 2, AlbumTitle: Forever Hold Your Peace
SingerId: 2, AlbumId: 3, AlbumTitle: Terrified

更新数据库架构

假设您需要将名为 MarketingBudget 的新列添加到 Albums 表。向现有表添加新列需要更新数据库架构。Spanner 支持在数据库继续处理流量的同时,对数据库进行架构更新。架构更新不需要使数据库离线,也不会锁定整个表或列;在架构更新期间,您可以继续将数据写入数据库。如需详细了解支持的架构更新和架构更改性能,请参阅进行架构更新

添加列

您可以使用 Google Cloud CLI 在命令行中添加列,也可以使用适用于 C++ 的 Spanner 客户端库以编程方式添加列。

在命令行中

使用以下 ALTER TABLE 命令向表添加新列:

GoogleSQL

gcloud spanner databases ddl update example-db --instance=test-instance \
    --ddl='ALTER TABLE Albums ADD COLUMN MarketingBudget INT64'

PostgreSQL

gcloud spanner databases ddl update example-db --instance=test-instance \
    --ddl='ALTER TABLE Albums ADD COLUMN MarketingBudget BIGINT'

您应该会看到:

Schema updating...done.

使用适用于 C++ 的 Spanner 客户端库

使用 DatabaseAdminClient::UpdateDatabase() 函数修改架构。

void AddColumn(google::cloud::spanner_admin::DatabaseAdminClient client,
               std::string const& project_id, std::string const& instance_id,
               std::string const& database_id) {
  google::cloud::spanner::Database database(project_id, instance_id,
                                            database_id);
  auto metadata =
      client
          .UpdateDatabaseDdl(
              database.FullName(),
              {"ALTER TABLE Albums ADD COLUMN MarketingBudget INT64"})
          .get();
  if (!metadata) throw std::move(metadata).status();
  std::cout << "Added MarketingBudget column\n";
}

使用 add-column 命令运行示例。

bazel run //google/cloud/spanner/samples:samples -- \
    add-column $GCLOUD_PROJECT test-instance example-db

您应该会看到:

Added MarketingBudget column

将数据写入新列

以下代码可将数据写入新列。对于键分别为 Albums(1, 1)Albums(2, 2) 的行,该代码会将 MarketingBudget 分别设置为 100000500000

void UpdateData(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;
  auto commit_result = client.Commit(spanner::Mutations{
      spanner::UpdateMutationBuilder("Albums",
                                     {"SingerId", "AlbumId", "MarketingBudget"})
          .EmplaceRow(1, 1, 100000)
          .EmplaceRow(2, 2, 500000)
          .Build()});
  if (!commit_result) throw std::move(commit_result).status();
  std::cout << "Update was successful [spanner_update_data]\n";
}

使用 update-data 参数运行示例。

bazel run //google/cloud/spanner/samples:samples -- \
    update-data $GCLOUD_PROJECT test-instance example-db

您还可以执行 SQL 查询或读取调用来获取刚才写入的值。

以下是执行查询的代码:

void QueryNewColumn(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;

  spanner::SqlStatement select(
      "SELECT SingerId, AlbumId, MarketingBudget FROM Albums");
  using RowType =
      std::tuple<std::int64_t, std::int64_t, absl::optional<std::int64_t>>;

  auto rows = client.ExecuteQuery(std::move(select));
  for (auto& row : spanner::StreamOf<RowType>(rows)) {
    if (!row) throw std::move(row).status();
    std::cout << "SingerId: " << std::get<0>(*row) << "\t";
    std::cout << "AlbumId: " << std::get<1>(*row) << "\t";
    auto marketing_budget = std::get<2>(*row);
    if (marketing_budget) {
      std::cout << "MarketingBudget: " << *marketing_budget << "\n";
    } else {
      std::cout << "MarketingBudget: NULL\n";
    }
  }
  std::cout << "Read completed for [spanner_read_data_with_new_column]\n";
}

如需执行此查询,请使用 query-new-column 参数运行示例。

bazel run //google/cloud/spanner/samples:samples -- \
    query-new-column $GCLOUD_PROJECT test-instance example-db

您应该会看到:

SingerId: 1 AlbumId: 1  MarketingBudget: 100000
SingerId: 1 AlbumId: 2  MarketingBudget: NULL
SingerId: 2 AlbumId: 1  MarketingBudget: NULL
SingerId: 2 AlbumId: 2  MarketingBudget: 500000
SingerId: 2 AlbumId: 3  MarketingBudget: NULL

更新数据

您可以在读写事务中使用 DML 来更新数据。

使用 Client::ExecuteDml() 函数来执行 DML 语句。

void DmlGettingStartedUpdate(google::cloud::spanner::Client client) {
  using ::google::cloud::StatusOr;
  namespace spanner = ::google::cloud::spanner;

  // A helper to read the budget for the given album and singer.
  auto get_budget = [&](spanner::Transaction txn, std::int64_t album_id,
                        std::int64_t singer_id) -> StatusOr<std::int64_t> {
    auto key = spanner::KeySet().AddKey(spanner::MakeKey(album_id, singer_id));
    auto rows = client.Read(std::move(txn), "Albums", key, {"MarketingBudget"});
    using RowType = std::tuple<absl::optional<std::int64_t>>;
    auto row = spanner::GetSingularRow(spanner::StreamOf<RowType>(rows));
    if (!row) return std::move(row).status();
    auto const budget = std::get<0>(*row);
    return budget ? *budget : 0;
  };

  // A helper to update the budget for the given album and singer.
  auto update_budget = [&](spanner::Transaction txn, std::int64_t album_id,
                           std::int64_t singer_id, std::int64_t budget) {
    auto sql = spanner::SqlStatement(
        "UPDATE Albums SET MarketingBudget = @AlbumBudget"
        "  WHERE SingerId = @SingerId AND AlbumId = @AlbumId",
        {{"AlbumBudget", spanner::Value(budget)},
         {"AlbumId", spanner::Value(album_id)},
         {"SingerId", spanner::Value(singer_id)}});
    return client.ExecuteDml(std::move(txn), std::move(sql));
  };

  auto const transfer_amount = 20000;
  auto commit_result = client.Commit(
      [&](spanner::Transaction const& txn) -> StatusOr<spanner::Mutations> {
        auto budget1 = get_budget(txn, 1, 1);
        if (!budget1) return std::move(budget1).status();
        if (*budget1 < transfer_amount) {
          return google::cloud::Status(
              google::cloud::StatusCode::kUnknown,
              "cannot transfer " + std::to_string(transfer_amount) +
                  " from budget of " + std::to_string(*budget1));
        }
        auto budget2 = get_budget(txn, 2, 2);
        if (!budget2) return std::move(budget2).status();
        auto update = update_budget(txn, 1, 1, *budget1 - transfer_amount);
        if (!update) return std::move(update).status();
        update = update_budget(txn, 2, 2, *budget2 + transfer_amount);
        if (!update) return std::move(update).status();
        return spanner::Mutations{};
      });
  if (!commit_result) throw std::move(commit_result).status();
  std::cout << "Update was successful [spanner_dml_getting_started_update]\n";
}

使用 getting-started-update 参数运行示例。

bazel run //google/cloud/spanner/samples:samples -- \
    getting-started-update $GCLOUD_PROJECT test-instance example-db

您应该会看到:

Update was successful [spanner_dml_getting_started_update]

使用二级索引

假设您想要提取 Albums 表中 AlbumTitle 值在特定范围内的所有行。您可以使用 SQL 语句或读取调用读取 AlbumTitle 列中的所有值,然后舍弃不符合条件的行。不过,执行全表扫描费用高昂,特别是对内含大量行的表来说更是如此。相反,如果对表创建二级索引,按非主键列进行搜索,则可以提高行检索速度。

向现有表添加二级索引需要更新架构。与其他架构更新一样,Spanner 支持在数据库继续处理流量的同时添加索引。Spanner 会使用您的现有数据自动回填索引。回填可能需要几分钟时间才能完成,但在此过程中,您无需使数据库离线,也无需避免写入已编入索引的表。如需了解详情,请参阅添加二级索引

添加二级索引后,Spanner 会自动将其用于 SQL 查询,使用该索引后,查询运行速度可能会更快。如果使用读取接口,则必须指定要使用的索引。

添加二级索引

您可以使用 gcloud CLI 在命令行上添加索引,也可以使用适用于 C++ 的 Spanner 客户端库以编程方式添加索引。

在命令行中

使用以下 CREATE INDEX 命令向数据库添加索引:

gcloud spanner databases ddl update example-db --instance=test-instance \
    --ddl='CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)'

您应该会看到:

Schema updating...done.

使用适用于 C++ 的 Spanner 客户端库

您可以使用 DatabaseAdminClient::UpdateDatabase() 函数添加索引:

void AddIndex(google::cloud::spanner_admin::DatabaseAdminClient client,
              std::string const& project_id, std::string const& instance_id,
              std::string const& database_id) {
  google::cloud::spanner::Database database(project_id, instance_id,
                                            database_id);
  auto metadata =
      client
          .UpdateDatabaseDdl(
              database.FullName(),
              {"CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)"})
          .get();
  if (!metadata) throw std::move(metadata).status();
  std::cout << "`AlbumsByAlbumTitle` Index successfully added, new DDL:\n"
            << metadata->DebugString();
}

使用 add-index 参数运行示例。

bazel run //google/cloud/spanner/samples:samples -- \
    add-index $GCLOUD_PROJECT test-instance example-db

添加索引可能需要几分钟时间。添加索引后,您应该会看到类似如下所示的输出:

`AlbumsByAlbumTitle` Index successfully added, new DDL:
database: "projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db"
statements: "CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)"
commit_timestamps {
  seconds: 1581011550
  nanos: 531102000
}

使用索引进行读取

对于 SQL 查询,Spanner 会自动使用适当的索引。在读取接口中,您必须在请求中指定索引。

要在读取界面中使用索引,请使用 Client::Read() 函数,该函数使用索引从数据库中读取零个或多个行。

以下代码从 AlbumsByAlbumTitle 索引中提取所有 AlbumIdAlbumTitle 列。

void ReadDataWithIndex(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;

  auto rows =
      client.Read("Albums", google::cloud::spanner::KeySet::All(),
                  {"AlbumId", "AlbumTitle"},
                  google::cloud::Options{}.set<spanner::ReadIndexNameOption>(
                      "AlbumsByAlbumTitle"));
  using RowType = std::tuple<std::int64_t, std::string>;
  for (auto& row : spanner::StreamOf<RowType>(rows)) {
    if (!row) throw std::move(row).status();
    std::cout << "AlbumId: " << std::get<0>(*row) << "\t";
    std::cout << "AlbumTitle: " << std::get<1>(*row) << "\n";
  }
  std::cout << "Read completed for [spanner_read_data_with_index]\n";
}

使用 read-data-with-index 参数运行示例。

bazel run //google/cloud/spanner/samples:samples -- \
    read-data-with-index $GCLOUD_PROJECT test-instance example-db

您应该会看到:

AlbumId: 2  AlbumTitle: Forever Hold Your Peace
AlbumId: 2  AlbumTitle: Go, Go, Go
AlbumId: 1  AlbumTitle: Green
AlbumId: 3  AlbumTitle: Terrified
AlbumId: 1  AlbumTitle: Total Junk

为仅索引读取添加索引

您可能已经注意到,前面的读取示例不包括读取 MarketingBudget 列的内容。这是因为 Spanner 的读取接口不支持将索引与数据表联接以查找未存储在索引中的值。

请创建 AlbumsByAlbumTitle 的备用定义,用于将 MarketingBudget 的副本存储到索引中。

在命令行中

GoogleSQL

gcloud spanner databases ddl update example-db --instance=test-instance \
    --ddl='CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget)

PostgreSQL

gcloud spanner databases ddl update example-db --instance=test-instance \
    --ddl='CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) INCLUDE (MarketingBudget)

添加索引可能需要几分钟时间。添加索引后,您应该会看到:

Schema updating...done.

使用适用于 C++ 的 Spanner 客户端库

您可以使用 DatabaseAdminClient::UpdateDatabase() 函数为以下对象添加带 STORING 子句的索引:

void AddStoringIndex(google::cloud::spanner_admin::DatabaseAdminClient client,
                     std::string const& project_id,
                     std::string const& instance_id,
                     std::string const& database_id) {
  google::cloud::spanner::Database database(project_id, instance_id,
                                            database_id);
  auto metadata = client
                      .UpdateDatabaseDdl(database.FullName(), {R"""(
                        CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle)
                            STORING (MarketingBudget))"""})
                      .get();
  if (!metadata) throw std::move(metadata).status();
  std::cout << "`AlbumsByAlbumTitle2` Index successfully added, new DDL:\n"
            << metadata->DebugString();
}

使用 add-storing-index 参数运行示例。

bazel run //google/cloud/spanner/samples:samples -- \
    add-storing-index $GCLOUD_PROJECT test-instance example-db

您应该会看到如下所示的输出:

`AlbumsByAlbumTitle2` Index successfully added, new DDL:
database: "projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db"
statements: "CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget)"
commit_timestamps {
  seconds: 1581012328
  nanos: 416682000
}

现在,当您执行读取操作时便可从 AlbumsByAlbumTitle2 索引中提取所有 AlbumIdAlbumTitleMarketingBudget 列:

通过执行一个明确指定您创建的存储索引的查询,使用该索引读取数据:

void ReadDataWithStoringIndex(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;

  auto rows =
      client.Read("Albums", google::cloud::spanner::KeySet::All(),
                  {"AlbumId", "AlbumTitle", "MarketingBudget"},
                  google::cloud::Options{}.set<spanner::ReadIndexNameOption>(
                      "AlbumsByAlbumTitle2"));
  using RowType =
      std::tuple<std::int64_t, std::string, absl::optional<std::int64_t>>;
  for (auto& row : spanner::StreamOf<RowType>(rows)) {
    if (!row) throw std::move(row).status();
    std::cout << "AlbumId: " << std::get<0>(*row) << "\t";
    std::cout << "AlbumTitle: " << std::get<1>(*row) << "\t";
    auto marketing_budget = std::get<2>(*row);
    if (marketing_budget) {
      std::cout << "MarketingBudget: " << *marketing_budget << "\n";
    } else {
      std::cout << "MarketingBudget: NULL\n";
    }
  }
  std::cout << "Read completed for [spanner_read_data_with_storing_index]\n";
}

使用 read-data-with-storing-index 参数运行示例。

bazel run //google/cloud/spanner/samples:samples -- \
    read-data-with-storing-index $GCLOUD_PROJECT test-instance example-db

您看到的输出结果应该类似于以下内容:

AlbumId: 2  AlbumTitle: Forever Hold Your Peace MarketingBudget: 520000
AlbumId: 2  AlbumTitle: Go, Go, Go  MarketingBudget: NULL
AlbumId: 1  AlbumTitle: Green   MarketingBudget: NULL
AlbumId: 3  AlbumTitle: Terrified   MarketingBudget: NULL
AlbumId: 1  AlbumTitle: Total Junk  MarketingBudget: 80000

使用只读事务检索数据

假设您要在同一时间戳执行多个读取操作。只读事务会遵从一致的事务提交历史记录前缀,因此您的应用始终可获得一致的数据。 Transaction 类型用于表示所有类型的事务。使用 MakeReadOnlyTransaction() 工厂函数创建只读事务。

下面演示了如何运行查询并在同一只读事务中执行读取操作:

void ReadOnlyTransaction(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;
  auto read_only = spanner::MakeReadOnlyTransaction();

  spanner::SqlStatement select(
      "SELECT SingerId, AlbumId, AlbumTitle FROM Albums");
  using RowType = std::tuple<std::int64_t, std::int64_t, std::string>;

  // Read#1.
  auto rows1 = client.ExecuteQuery(read_only, select);
  std::cout << "Read 1 results\n";
  for (auto& row : spanner::StreamOf<RowType>(rows1)) {
    if (!row) throw std::move(row).status();
    std::cout << "SingerId: " << std::get<0>(*row)
              << " AlbumId: " << std::get<1>(*row)
              << " AlbumTitle: " << std::get<2>(*row) << "\n";
  }
  // Read#2. Even if changes occur in-between the reads the transaction ensures
  // that Read #1 and Read #2 return the same data.
  auto rows2 = client.ExecuteQuery(read_only, select);
  std::cout << "Read 2 results\n";
  for (auto& row : spanner::StreamOf<RowType>(rows2)) {
    if (!row) throw std::move(row).status();
    std::cout << "SingerId: " << std::get<0>(*row)
              << " AlbumId: " << std::get<1>(*row)
              << " AlbumTitle: " << std::get<2>(*row) << "\n";
  }
}

使用 read-only-transaction 参数运行示例。

bazel run //google/cloud/spanner/samples:samples -- \
    read-only-transaction $GCLOUD_PROJECT test-instance example-db

您看到的输出结果应该类似于以下内容:

Read 1 results
SingerId: 2 AlbumId: 2 AlbumTitle: Forever Hold Your Peace
SingerId: 1 AlbumId: 2 AlbumTitle: Go, Go, Go
SingerId: 2 AlbumId: 1 AlbumTitle: Green
SingerId: 2 AlbumId: 3 AlbumTitle: Terrified
SingerId: 1 AlbumId: 1 AlbumTitle: Total Junk
Read 2 results
SingerId: 2 AlbumId: 2 AlbumTitle: Forever Hold Your Peace
SingerId: 1 AlbumId: 2 AlbumTitle: Go, Go, Go
SingerId: 2 AlbumId: 1 AlbumTitle: Green
SingerId: 2 AlbumId: 3 AlbumTitle: Terrified
SingerId: 1 AlbumId: 1 AlbumTitle: Total Junk

清理

为避免因本教程中使用的资源导致您的 Google Cloud 账号产生额外费用,请删除数据库和您创建的实例。

删除数据库

如果您删除一个实例,则该实例中的所有数据库都会自动删除。 本步骤演示了如何在不删除实例的情况下删除数据库(您仍需为该实例付费)。

在命令行中

gcloud spanner databases delete example-db --instance=test-instance

使用 Google Cloud 控制台

  1. 转到 Google Cloud 控制台中的 Spanner 实例页面。

    转到“实例”页面

  2. 点击实例。

  3. 点击您想删除的数据库。

  4. 数据库详细信息页面中,点击删除

  5. 确认您要删除数据库并点击删除

删除实例

删除实例会自动删除在该实例中创建的所有数据库。

在命令行中

gcloud spanner instances delete test-instance

使用 Google Cloud 控制台

  1. 转到 Google Cloud 控制台中的 Spanner 实例页面。

    转到“实例”页面

  2. 点击您的实例。

  3. 点击删除

  4. 确认您要删除实例并点击删除

后续步骤