開始使用 C++ 版 Spanner


目標

本教學課程將逐步引導您使用 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 檔案,瞭解如何建立資料庫及修改資料庫結構定義。這份資料會使用結構定義與資料模型頁面中所列出的範例結構定義。

建立資料庫

GoogleSQL

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

PostgreSQL

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

bazel run //google/cloud/spanner/samples:postgresql_samples -- \
      interleaved-table $GCLOUD_PROJECT test-instance example-db

畫面上會顯示下列訊息:

Created database [projects/${GCLOUD_PROJECT}/instances/test-instance/databases/example-db]
以下程式碼會在資料庫中建立資料庫和兩個資料表。

GoogleSQL

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";
}

PostgreSQL

在 PostgreSQL 方言中,您必須先建立資料庫,才能提交 DDL 要求來建立資料表。

以下範例會建立資料庫:

void CreateDatabase(google::cloud::spanner_admin::DatabaseAdminClient client,
                    google::cloud::spanner::Database const& database) {
  google::spanner::admin::database::v1::CreateDatabaseRequest request;
  request.set_parent(database.instance().FullName());
  request.set_create_statement("CREATE DATABASE \"" + database.database_id() +
                               "\"");
  request.set_database_dialect(
      google::spanner::admin::database::v1::DatabaseDialect::POSTGRESQL);
  auto db = client.CreateDatabase(request).get();
  if (!db) throw std::move(db).status();
  std::cout << "Database " << db->name() << " created.\n";
}

以下範例會在資料庫中建立兩個資料表:

void InterleavedTable(google::cloud::spanner_admin::DatabaseAdminClient client,
                      google::cloud::spanner::Database const& database) {
  // The Spanner PostgreSQL dialect extends the PostgreSQL dialect with
  // certain Spanner specific features, such as interleaved tables. See
  // https://cloud.google.com/spanner/docs/postgresql/data-definition-language#create_table
  // for the full CREATE TABLE syntax.
  std::vector<std::string> statements = {
      R"""(
        CREATE TABLE Singers (
            SingerId        BIGINT NOT NULL,
            FirstName       CHARACTER VARYING(1024) NOT NULL,
            LastName        CHARACTER VARYING(1024) NOT NULL,
            PRIMARY KEY(SingerId)
        )
      )""",
      R"""(
        CREATE TABLE Albums (
            SingerId        BIGINT NOT NULL,
            AlbumId         BIGINT NOT NULL,
            AlbumTitle      CHARACTER VARYING NOT NULL,
            MarketingBudget BIGINT,
            PRIMARY KEY(SingerId, AlbumId)
        ) INTERLEAVE IN PARENT Singers ON DELETE CASCADE
      )""",
  };
  auto metadata =
      client.UpdateDatabaseDdl(database.FullName(), statements).get();
  google::cloud::spanner_testing::LogUpdateDatabaseDdl(  //! TODO(#4758)
      client, database, metadata.status());              //! TODO(#4758)
  if (!metadata) throw std::move(metadata).status();
  std::cout << "Tables created.\nNew DDL:\n" << metadata->DebugString();
}

下一個步驟是將資料寫入資料庫。

建立資料庫用戶端

您必須先建立 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 的析構函式會自動清除 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 databases execute-sql example-db --instance=test-instance \
    --sql='SELECT SingerId, AlbumId, AlbumTitle FROM Albums'

結果應為:

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

清除所用資源

如要避免系統向您的 Cloud Billing 帳戶收取您在本教學課程中所用資源的額外費用,請捨棄資料庫並刪除您建立的執行個體。

刪除資料庫

您刪除執行個體時,也會自動刪除其中所有資料庫。這個步驟將示範如何在保留執行個體的情況下刪除資料庫 (您仍須支付執行個體費用)。

使用指令列

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

使用 Google Cloud 主控台

  1. 前往 Google Cloud 控制台的「Spanner 執行個體」頁面。

    前往「Instances」(執行個體) 頁面

  2. 點選執行個體。

  3. 點選您要刪除的資料庫。

  4. 在「Database details」(資料庫詳細資料) 頁面,按一下 [Delete] (刪除)

  5. 確認您要刪除資料庫,然後按一下 [Delete] (刪除)

刪除執行個體

您刪除執行個體時,也會自動捨棄您在其中建立的所有資料庫。

使用指令列

gcloud spanner instances delete test-instance

使用 Google Cloud 主控台

  1. 前往 Google Cloud 控制台的「Spanner 執行個體」頁面。

    前往「Instances」(執行個體) 頁面

  2. 點選執行個體。

  3. 按一下 [Delete] (刪除)

  4. 確認您要刪除執行個體,然後按一下 [Delete] (刪除)

後續步驟