Introdução ao Spanner em C++


Objetivos

Este tutorial explica os seguintes passos através da biblioteca do cliente do Spanner para C++:

  • Crie uma instância e uma base de dados do Spanner.
  • Escrever, ler e executar consultas SQL em dados na base de dados.
  • Atualize o esquema da base de dados.
  • Atualize os dados através de uma transação de leitura/escrita.
  • Adicione um índice secundário à base de dados.
  • Use o índice para ler e executar consultas SQL em dados.
  • Recuperar dados através de uma transação só de leitura.

Custos

Este tutorial usa o Spanner, que é um componente faturável do Google Cloud. Para obter informações sobre o custo de utilização do Spanner, consulte a secção Preços.

Antes de começar

Conclua os passos descritos em Configuração, que abrangem a criação e a definição de um projeto Google Cloud predefinido, a ativação da faturação, a ativação da API Cloud Spanner e a configuração do OAuth 2.0 para obter credenciais de autenticação para usar a API Cloud Spanner.

Em particular, certifique-se de que executa gcloud auth application-default login para configurar o seu ambiente de desenvolvimento local com credenciais de autenticação.

Prepare o seu ambiente C++ local

  1. Clone o repositório da app de exemplo para a sua máquina local:

    git clone https://github.com/googleapis/google-cloud-cpp $HOME/google-cloud-cpp
    
  2. Instale o Bazel para Linux através destas instruções.

  3. Altere para o diretório que contém o código de exemplo do Spanner:

    cd $HOME/google-cloud-cpp
    
  4. Crie os exemplos com este comando:

    bazel build //google/cloud/spanner/samples:samples
    
  5. Configure a autenticação e a autorização para o google-cloud-cpp projeto.

    gcloud auth application-default login
    
  6. Crie uma variável de ambiente denominada GCLOUD_PROJECT. Substitua [MY_PROJECT_ID] pelo ID do seu Google Cloud projeto. Pode encontrar este ID na página Boas-vindas do seu projeto.

    export GCLOUD_PROJECT=[MY_PROJECT_ID]
    

Crie uma instância

Quando usa o Spanner pela primeira vez, tem de criar uma instância, que é uma atribuição de recursos usados pelas bases de dados do Spanner. Quando cria uma instância, escolhe uma configuração da instância, que determina onde os seus dados são armazenados, bem como o número de nós a usar, o que determina a quantidade de recursos de publicação e armazenamento na sua instância.

Consulte o artigo Crie uma instância para saber como criar uma instância do Spanner através de qualquer um dos seguintes métodos. Pode dar o nome test-instance à sua instância para a usar com outros tópicos neste documento que façam referência a uma instância com o nome test-instance.

  • A CLI do Google Cloud
  • A Google Cloud consola
  • Uma biblioteca cliente (C++, C#, Go, Java, Node.js, PHP, Python ou Ruby)

Explore ficheiros de exemplo

O repositório de exemplos contém um exemplo que mostra como usar o Spanner com C++.

Consulte o ficheiro google/cloud/spanner/samples/samples.cc, que mostra como criar uma base de dados e modificar um esquema de base de dados. Os dados usam o esquema de exemplo apresentado na página Esquema e modelo de dados.

Crie uma base de dados

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

Deve ver:

Created database [projects/${GCLOUD_PROJECT}/instances/test-instance/databases/example-db]
O código seguinte cria uma base de dados e duas tabelas na base de dados.

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

No dialeto PostgreSQL, a base de dados tem de ser criada antes de enviar um pedido DDL para criar uma tabela.

O exemplo seguinte cria uma base de dados:

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

O exemplo seguinte cria as duas tabelas na base de dados:

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();
}

O passo seguinte é escrever dados na sua base de dados.

Crie um cliente de base de dados

Antes de poder fazer leituras ou escritas, tem de criar um Client:

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

Um Client permite-lhe ler, escrever, consultar e executar transações numa base de dados do Spanner. Normalmente, cria um Client quando a sua aplicação é iniciada e, em seguida, reutiliza esse Client para ler, escrever e executar transações. Cada cliente usa recursos no Spanner. O destruidor de Client limpa automaticamente os recursos de Client, incluindo as ligações de rede.

Leia mais acerca de Client na Google Cloud referência de C++ do Spanner.

Escreva dados com DML

Pode inserir dados através da linguagem de manipulação de dados (DML) numa transação de leitura/escrita.

Use a função Client::ExecuteDml() para executar uma declaração 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";
}

Execute o exemplo com o argumento getting-started-insert.

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

Deve ver:

Insert was successful [spanner_dml_getting_started_insert]

Escreva dados com mutações

Também pode inserir dados através de alterações.

Escreve dados através de um objeto Client. A função Client::Commit() cria e confirma uma transação para escritas que são executadas atomicamente num único ponto lógico no tempo em colunas, linhas e tabelas numa base de dados.

Este código mostra como escrever os dados através de mutações:

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

Execute o exemplo com o argumento insert-data.

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

Deve ver:

Insert was successful [spanner_insert_data]

Consultar dados através de SQL

O Spanner suporta uma interface SQL para ler dados, à qual pode aceder na linha de comandos através da CLI do Google Cloud ou programaticamente através da biblioteca de cliente do Spanner para C++.

Na linha de comandos

Execute a seguinte declaração SQL para ler os valores de todas as colunas da tabela Albums:

gcloud spanner databases execute-sql example-db --instance=test-instance \
    --sql='SELECT SingerId, AlbumId, AlbumTitle FROM Albums'

O resultado mostra:

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

Use a biblioteca cliente do Spanner para C++

Além de executar uma declaração SQL na linha de comandos, pode emitir a mesma declaração SQL de forma programática através da biblioteca cliente do Spanner para C++.

Use a função Client::ExecuteQuery() para executar a consulta SQL. Veja como emitir a consulta e aceder aos dados:

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

Execute o exemplo com o argumento query_data.

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

Deverá ver o seguinte resultado:

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

Consultar através de um parâmetro SQL

Se a sua aplicação tiver uma consulta executada com frequência, pode melhorar o respetivo desempenho parametrizando-a. A consulta paramétrica resultante pode ser colocada em cache e reutilizada, o que reduz os custos de compilação. Para mais informações, consulte o artigo Use parâmetros de consulta para acelerar as consultas executadas com frequência.

Segue-se um exemplo da utilização de um parâmetro na cláusula WHERE para consultar registos que contêm um valor específico para 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";
}

Execute o exemplo com o comando query-with-parameter.

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

Deverá ver o seguinte resultado:

SingerId: 12    FirstName: Melissa      LastName: Garcia

Leia dados através da API de leitura

Além da interface SQL do Spanner, o Spanner também suporta uma interface de leitura.

Use a função Client::Read() para ler linhas da base de dados. Use um KeySetobjeto para definir uma coleção de chaves e intervalos de chaves a ler.

Veja como ler os dados:

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

Execute o exemplo com o argumento read-data.

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

Deverá ver uma saída semelhante à seguinte:

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

Atualize o esquema da base de dados

Suponha que precisa de adicionar uma nova coluna denominada MarketingBudget à tabela Albums. A adição de uma nova coluna a uma tabela existente requer uma atualização ao esquema da base de dados. O Spanner suporta atualizações de esquemas a uma base de dados enquanto a base de dados continua a servir tráfego. As atualizações do esquema não requerem que a base de dados fique offline e não bloqueiam tabelas nem colunas inteiras. Pode continuar a escrever dados na base de dados durante a atualização do esquema. Leia mais acerca das atualizações de esquemas suportadas e do desempenho das alterações de esquemas em Faça atualizações de esquemas.

Adicione uma coluna

Pode adicionar uma coluna na linha de comandos através da Google Cloud CLI ou de forma programática através da biblioteca de cliente do Spanner para C++.

Na linha de comandos

Use o seguinte comando ALTER TABLE para adicionar a nova coluna à tabela:

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'

Deve ver:

Schema updating...done.

Use a biblioteca cliente do Spanner para C++

Use a função DatabaseAdminClient::UpdateDatabase() para modificar o esquema.

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

Execute o exemplo com o comando add-column.

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

Deve ver:

Added MarketingBudget column

Escreva dados na nova coluna

O código seguinte escreve dados na nova coluna. Define MarketingBudget como 100000 para a linha identificada por Albums(1, 1) e como 500000 para a linha identificada por Albums(2, 2).

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

Execute o exemplo com o argumento update-data.

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

Também pode executar uma consulta SQL ou uma chamada de leitura para obter os valores que acabou de escrever.

Aqui está o código para executar a consulta:

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

Para executar esta consulta, execute o exemplo com o argumento query-new-column.

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

Deve ver:

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

Atualize os dados

Pode atualizar dados através de DML numa transação de leitura/escrita.

Use a função Client::ExecuteDml() para executar uma declaração 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";
}

Execute o exemplo com o argumento getting-started-update.

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

Deve ver:

Update was successful [spanner_dml_getting_started_update]

Use um índice secundário

Suponhamos que quer obter todas as linhas de Albums que têm valores AlbumTitle num determinado intervalo. Pode ler todos os valores da coluna AlbumTitle através de uma declaração SQL ou de uma chamada de leitura e, em seguida, rejeitar as linhas que não cumprem os critérios, mas esta análise completa da tabela é dispendiosa, especialmente para tabelas com muitas linhas. Em alternativa, pode acelerar a obtenção de linhas quando pesquisa por colunas de chaves não primárias criando um índice secundário na tabela.

A adição de um índice secundário a uma tabela existente requer uma atualização do esquema. Tal como outras atualizações de esquemas, o Spanner suporta a adição de um índice enquanto a base de dados continua a publicar tráfego. O Spanner repreenche automaticamente o índice com os seus dados existentes. Os preenchimentos podem demorar alguns minutos a serem concluídos, mas não tem de colocar a base de dados offline nem evitar escrever na tabela indexada durante este processo. Para mais detalhes, consulte o artigo Adicione um índice secundário.

Depois de adicionar um índice secundário, o Spanner usa-o automaticamente para consultas SQL que provavelmente são executadas mais rapidamente com o índice. Se usar a interface read, tem de especificar o índice que quer usar.

Adicione um índice secundário

Pode adicionar um índice na linha de comandos através da CLI gcloud ou programaticamente através da biblioteca de cliente do Spanner para C++.

Na linha de comandos

Use o seguinte comando CREATE INDEX para adicionar um índice à base de dados:

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

Deve ver:

Schema updating...done.

Usar a biblioteca cliente do Spanner para C++

Use a função DatabaseAdminClient::UpdateDatabase() para adicionar um índice:

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();
}

Execute o exemplo com o argumento add-index.

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

A adição de um índice pode demorar alguns minutos. Depois de adicionar o índice, deve ver um resultado semelhante a este:

`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
}

Leia através do índice

Para consultas SQL, o Spanner usa automaticamente um índice adequado. Na interface de leitura, tem de especificar o índice no seu pedido.

Para usar o índice na interface de leitura, use a função Client::Read(), que lê zero ou mais linhas de uma base de dados usando um índice.

O código seguinte obtém todas as colunas AlbumId e AlbumTitle do índice AlbumsByAlbumTitle.

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

Execute o exemplo com o argumento read-data-with-index.

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

Deve ver:

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

Adicione um índice para leituras apenas de índice

Pode ter reparado que o exemplo de leitura anterior não inclui a leitura da coluna MarketingBudget. Isto deve-se ao facto de a interface de leitura do Spanner não suportar a capacidade de associar um índice a uma tabela de dados para procurar valores que não estão armazenados no índice.

Crie uma definição alternativa de AlbumsByAlbumTitle que armazene uma cópia de MarketingBudget no índice.

Na linha de comandos

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)

A adição de um índice pode demorar alguns minutos. Depois de adicionar o índice, deve ver o seguinte:

Schema updating...done.

Usar a biblioteca cliente do Spanner para C++

Use a função DatabaseAdminClient::UpdateDatabase() para adicionar um índice com uma cláusula STORING para :

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();
}

Execute o exemplo com o argumento add-storing-index.

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

Deverá ver uma saída semelhante a esta:

`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
}

Agora, pode executar uma leitura que obtenha todas as colunas AlbumId, AlbumTitle e MarketingBudget do índice AlbumsByAlbumTitle2:

Leia os dados através do índice de armazenamento que criou executando uma consulta que especifica explicitamente o índice:

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

Execute o exemplo com o argumento read-data-with-storing-index.

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

Deverá ver uma saída semelhante à seguinte:

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

Obtenha dados através de transações só de leitura

Suponhamos que quer executar mais do que uma leitura na mesma data/hora. As transações de leitura exclusiva observam um prefixo consistente do histórico de confirmações de transações, pelo que a sua aplicação recebe sempre dados consistentes. O tipo Transaction é usado para representar todos os tipos de transações. Use a função de fábrica MakeReadOnlyTransaction() para criar uma transação de leitura apenas.

O exemplo seguinte mostra como executar uma consulta e fazer uma leitura na mesma transação só de leitura:

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

Execute o exemplo com o argumento read-only-transaction.

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

Deverá ver uma saída semelhante à seguinte:

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

Limpeza

Para evitar incorrer em cobranças adicionais na sua conta do Cloud Billing pelos recursos usados neste tutorial, elimine a base de dados e a instância que criou.

Elimine a base de dados

Se eliminar uma instância, todas as bases de dados na mesma são eliminadas automaticamente. Este passo mostra como eliminar uma base de dados sem eliminar uma instância (continua a incorrer em custos pela instância).

Na linha de comandos

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

Usar a Google Cloud consola

  1. Aceda à página Instâncias do Spanner na Google Cloud consola.

    Aceda à página Instâncias

  2. Clique na instância.

  3. Clique na base de dados que quer eliminar.

  4. Na página Detalhes da base de dados, clique em Eliminar.

  5. Confirme que quer eliminar a base de dados e clique em Eliminar.

Elimine a instância

A eliminação de uma instância elimina automaticamente todas as bases de dados criadas nessa instância.

Na linha de comandos

gcloud spanner instances delete test-instance

Usar a Google Cloud consola

  1. Aceda à página Instâncias do Spanner na Google Cloud consola.

    Aceda à página Instâncias

  2. Clique na instância.

  3. Clique em Eliminar.

  4. Confirme que quer eliminar a instância e clique em Eliminar.

O que se segue?