Primeros pasos con Spanner en C++


Objetivos

En este tutorial, se explican los siguientes pasos para usar la biblioteca de cliente de Spanner para C++:

  • Crea una instancia y una base de datos de Spanner.
  • Escribir, leer y ejecutar consultas SQL sobre datos en la base de datos.
  • Actualizar el esquema de la base de datos.
  • Actualizar datos mediante una transacción de lectura y escritura.
  • Agregar un índice secundario a la base de datos.
  • Usar el índice para leer los datos y ejecutar consultas SQL sobre ellos.
  • Recuperar datos mediante una transacción de solo lectura.

Costes

En este tutorial se usa Spanner, que es un componente facturable deGoogle Cloud. Para obtener información sobre el coste de usar Spanner, consulta la página Precios.

Antes de empezar

Sigue los pasos que se describen en la sección Configuración, donde se explica cómo crear y definir un proyecto predeterminado Google Cloud , habilitar la facturación y la API Cloud Spanner, y configurar OAuth 2.0 para obtener las credenciales de autenticación que se usarán con la API Cloud Spanner.

En concreto, asegúrate de ejecutar gcloud auth application-default login para configurar tu entorno de desarrollo local con credenciales de autenticación.

Preparar el entorno local de C++

  1. Clona el repositorio de aplicaciones de muestra en la máquina local:

    git clone https://github.com/googleapis/google-cloud-cpp $HOME/google-cloud-cpp
    
  2. Instala Bazel para Linux siguiendo estas instrucciones.

  3. Cambia al directorio que contiene el código de ejemplo de Spanner:

    cd $HOME/google-cloud-cpp
    
  4. Compila los ejemplos con este comando:

    bazel build //google/cloud/spanner/samples:samples
    
  5. Configura la autenticación y la autorización del proyecto google-cloud-cpp.

    gcloud auth application-default login
    
  6. Crea una variable de entorno llamada GCLOUD_PROJECT. Sustituye [MY_PROJECT_ID] por el ID de tu proyecto. Google Cloud Puedes encontrar este ID en la página Bienvenida de tu proyecto.

    export GCLOUD_PROJECT=[MY_PROJECT_ID]
    

Crear una instancia

La primera vez que uses Spanner, debes crear una instancia, que es una asignación de recursos que utilizan las bases de datos de Spanner. Cuando creas una instancia, tienes que elegir una configuración de instancia, que determina dónde se almacenan tus datos y la cantidad de nodos que se van a usar, lo que permite conocer la cantidad de recursos de almacenamiento y publicación de la instancia.

Consulta Crear una instancia para saber cómo crear una instancia de Spanner con cualquiera de los siguientes métodos. Puedes llamar a tu instancia test-instance para usarla con otros temas de este documento que hagan referencia a una instancia llamada test-instance.

  • Google Cloud CLI
  • La Google Cloud consola
  • Una biblioteca de cliente (C++, C#, Go, Java, Node.js, PHP, Python o Ruby)

Consultar los archivos de muestra

El repositorio de ejemplos contiene un ejemplo que muestra cómo usar Spanner con C++.

Consulta el archivo google/cloud/spanner/samples/samples.cc, que muestra cómo crear una base de datos y modificar un esquema de base de datos. Los datos usan el esquema de ejemplo que se muestra en la página Esquema y modelo de datos.

Crear una base de datos

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

Deberías ver lo siguiente:

Created database [projects/${GCLOUD_PROJECT}/instances/test-instance/databases/example-db]
El siguiente código crea una base de datos y dos tablas en ella.

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

En el dialecto de PostgreSQL, la base de datos debe crearse antes de enviar una solicitud DDL para crear una tabla.

En el siguiente ejemplo se crea una base de datos:

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

En el siguiente ejemplo se crean las dos tablas en la base de datos:

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

El siguiente paso consiste en escribir datos en la base de datos.

Crear un cliente de base de datos

Antes de poder leer o escribir datos, debes crear un Client:

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

Un Client te permite leer, escribir, consultar y ejecutar transacciones en una base de datos de Spanner. Normalmente, se crea un Client cuando se inicia la aplicación y, a continuación, se vuelve a usar ese Client para leer, escribir y ejecutar transacciones. Cada cliente usa recursos en Spanner. El destructor de Client limpiará automáticamente los recursos de Client, incluidas las conexiones de red.

Consulta más información sobre Client en la Google Cloud referencia de C++ de Spanner.

Escribir datos con DML

Puedes insertar datos mediante el lenguaje de manipulación de datos (DML) en una transacción de lectura y escritura.

Usa la función Client::ExecuteDml() para ejecutar una instrucción 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";
}

Ejecuta la muestra con el argumento getting-started-insert.

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

Deberías ver lo siguiente:

Insert was successful [spanner_dml_getting_started_insert]

Escribir datos con mutaciones

También puedes insertar datos mediante mutaciones.

Para escribir datos, se usa un objeto Client. La función Client::Commit() crea y confirma una transacción para las escrituras que se ejecutan de forma atómica en un único punto lógico en el tiempo en columnas, filas y tablas de una base de datos.

En este código se muestra cómo escribir los datos mediante mutaciones:

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

Ejecuta la muestra con el argumento insert-data.

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

Deberías ver lo siguiente:

Insert was successful [spanner_insert_data]

Consultar datos mediante SQL

Spanner admite una interfaz SQL para leer datos, a la que puedes acceder en la línea de comandos mediante la CLI de Google Cloud o de forma programática mediante la biblioteca de cliente de Spanner para C++.

En la línea de comandos

Ejecuta la siguiente instrucción SQL para leer los valores de todas las columnas de la tabla Albums:

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

El resultado muestra lo siguiente:

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

Usar la biblioteca de cliente de Spanner para C++

Además de ejecutar una instrucción SQL en la línea de comandos, puedes emitir la misma instrucción SQL de forma programática mediante la biblioteca de cliente de Spanner para C++.

Usa la función Client::ExecuteQuery() para ejecutar la consulta de SQL. A continuación, se indica cómo emitir la consulta y acceder a los datos:

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

Ejecuta la muestra con el argumento query_data.

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

El resultado debe ser el siguiente:

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 usando un parámetro de SQL

Si tu aplicación tiene una consulta que se ejecuta con frecuencia, puedes mejorar su rendimiento parametrizándola. La consulta paramétrica resultante se puede almacenar en caché y reutilizar, lo que reduce los costes de compilación. Para obtener más información, consulta Usar parámetros de consulta para acelerar las consultas que se ejecutan con frecuencia.

Aquí tienes un ejemplo de cómo usar un parámetro en la cláusula WHERE para consultar registros que contengan un valor específico de 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";
}

Ejecuta el ejemplo con el comando query-with-parameter.

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

El resultado debe ser el siguiente:

SingerId: 12    FirstName: Melissa      LastName: Garcia

Leer datos mediante la API de lectura

Además de la interfaz SQL de Spanner, Spanner también admite una interfaz de lectura.

La función Client::Read() se usa para leer filas de la base de datos. Usa un KeySetobjeto para definir una colección de claves y rangos de claves que se van a leer.

A continuación, mostramos cómo leer los datos:

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

Ejecuta la muestra con el argumento read-data.

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

El resultado que verás debe parecerse al siguiente:

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

Actualizar el esquema de la base de datos

Supongamos que quiere añadir una nueva columna llamada MarketingBudget a la tabla Albums. Para agregar una nueva columna a una tabla existente, es preciso actualizar el esquema de base de datos. Spanner admite actualizaciones de esquemas en una base de datos mientras esta sigue atendiendo tráfico. Para actualizar el esquema, no es necesario desconectar la base de datos y no se bloquean tablas ni columnas completas. Puedes seguir escribiendo datos en la base de datos durante la actualización del esquema. Consulta más información sobre las actualizaciones de esquemas y el rendimiento de los cambios de esquemas admitidos en el artículo Hacer actualizaciones de esquemas.

Añadir una columna

Puedes añadir una columna en la línea de comandos con la CLI de Google Cloud o de forma programática con la biblioteca de cliente de Spanner para C++.

En la línea de comandos

Usa el siguiente comando ALTER TABLE para añadir la nueva columna a la tabla:

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'

Deberías ver lo siguiente:

Schema updating...done.

Usar la biblioteca de cliente de Spanner para C++

Usa la función DatabaseAdminClient::UpdateDatabase() para modificar el 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";
}

Ejecuta el ejemplo con el comando add-column.

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

Deberías ver lo siguiente:

Added MarketingBudget column

Escribir datos en la nueva columna

El siguiente código sirve para escribir datos en la nueva columna. Define MarketingBudget como 100000 en la fila con la clave Albums(1, 1) y como 500000 en la fila con la clave 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";
}

Ejecuta la muestra con el argumento update-data.

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

También puedes ejecutar una consulta SQL o una llamada de lectura para recuperar los valores que acabas de escribir.

Aquí está el código para ejecutar la 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 ejecutar esta consulta, ejecuta la muestra con el argumento query-new-column.

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

Deberías ver lo siguiente:

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

Actualizar datos

Puedes actualizar datos mediante DML en una transacción de lectura y escritura.

Usa la función Client::ExecuteDml() para ejecutar una instrucción 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";
}

Ejecuta la muestra con el argumento getting-started-update.

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

Deberías ver lo siguiente:

Update was successful [spanner_dml_getting_started_update]

Usar un índice secundario

Supongamos que quieres obtener todas las filas de Albums que tengan valores de AlbumTitle en un intervalo determinado. Podría leer todos los valores de la columna AlbumTitle con una instrucción SQL o una llamada de lectura y, a continuación, descartar las filas que no cumplan los criterios, pero hacer este análisis de toda la tabla es caro, sobre todo en el caso de las tablas con muchas filas. En su lugar, puedes acelerar la recuperación de filas al buscar por columnas que no sean de clave principal creando un índice secundario en la tabla.

Para añadir un índice secundario a una tabla existente, es preciso actualizar el esquema. Al igual que otras actualizaciones de esquema, Spanner permite añadir un índice mientras la base de datos sigue sirviendo tráfico. Spanner rellena automáticamente el índice con los datos que ya tengas. Los rellenos pueden tardar unos minutos en completarse, pero no es necesario que pongas la base de datos sin conexión ni que evites escribir en la tabla indexada durante este proceso. Para obtener más información, consulta Añadir un índice secundario.

Después de añadir un índice secundario, Spanner lo usa automáticamente en las consultas de SQL que probablemente se ejecuten más rápido con el índice. Si usas la interfaz de lectura, debes especificar el índice que quieras usar.

Añadir un índice secundario

Puedes añadir un índice en la línea de comandos mediante la CLI de gcloud o de forma programática mediante la biblioteca de cliente de Spanner para C++.

En la línea de comandos

Usa el siguiente comando CREATE INDEX para añadir un índice a la base de datos:

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

Deberías ver lo siguiente:

Schema updating...done.

Usar la biblioteca de cliente de Spanner para C++

Usa la función DatabaseAdminClient::UpdateDatabase() para añadir un í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();
}

Ejecuta la muestra con el argumento add-index.

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

Añadir un índice es un proceso que puede llevar unos minutos. Una vez que se haya añadido el índice, debería ver un resultado similar 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
}

Leer datos mediante el índice

En el caso de las consultas de SQL, Spanner usa automáticamente un índice adecuado. En la interfaz de lectura, debe especificar el índice en su solicitud.

Para usar el índice en la interfaz de lectura, usa la función Client::Read(), que lee cero o más filas de una base de datos mediante un índice.

El siguiente código obtiene todas las columnas AlbumId y AlbumTitle del í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";
}

Ejecuta la muestra con el argumento read-data-with-index.

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

Deberías ver lo siguiente:

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

Añadir un índice para lecturas solo de índice

Puede que hayas observado que en el ejemplo de lectura anterior no se incluye la lectura de la columna MarketingBudget. Esto se debe a que la interfaz de lectura de Spanner no admite la posibilidad de combinar un índice con una tabla de datos para buscar valores que no estén almacenados en el índice.

Crea una definición alternativa de AlbumsByAlbumTitle que almacene una copia de MarketingBudget en el índice.

En la línea 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ñadir un índice es un proceso que puede llevar unos minutos. Esto es lo que debes ver después de añadir el índice:

Schema updating...done.

Usar la biblioteca de cliente de Spanner para C++

Usa la función DatabaseAdminClient::UpdateDatabase() para añadir un índice con una 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();
}

Ejecuta la muestra con el argumento add-storing-index.

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

Debería ver un resultado similar a este:

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

Ahora puedes ejecutar una lectura que obtenga todas las columnas AlbumId, AlbumTitle y MarketingBudget del índice AlbumsByAlbumTitle2:

Lee datos con el índice de almacenamiento que has creado ejecutando una consulta que especifica explícitamente el í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";
}

Ejecuta la muestra con el argumento read-data-with-storing-index.

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

El resultado que verás debe parecerse al siguiente:

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

Recuperar datos mediante transacciones de solo lectura

Supongamos que deseas ejecutar más de una lectura en la misma marca de tiempo. En las transacciones de solo lectura se observa un prefijo uniforme del historial de confirmación de transacción, por lo que la aplicación siempre obtiene datos uniformes. El tipo Transaction se usa para representar todo tipo de transacciones. Usa la función de fábrica MakeReadOnlyTransaction() para crear una transacción de solo lectura.

A continuación, se muestra cómo ejecutar una consulta y realizar una lectura en la misma transacción de solo lectura:

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

Ejecuta la muestra con el argumento read-only-transaction.

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

El resultado que verás debe parecerse al siguiente:

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

Limpieza

Para evitar que se apliquen cargos adicionales en tu cuenta de Facturación de Cloud por los recursos utilizados en este tutorial, elimina la base de datos y la instancia que has creado.

Eliminar la base de datos

Al eliminar una instancia, se eliminan automáticamente todas sus bases de datos. En este paso se muestra cómo eliminar una base de datos sin eliminar una instancia (se seguirían generando costes por la instancia).

En la línea de comandos

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

Usar la Google Cloud consola

  1. Ve a la página Instancias de Spanner de la Google Cloud consola.

    Ir a la página Instancias

  2. Haz clic en la instancia.

  3. Haz clic en la base de datos que quieras eliminar.

  4. En la página sobre detalles de la base de datos, haz clic en Eliminar.

  5. Confirma que deseas eliminar la base de datos y haz clic en Eliminar.

Eliminar la instancia

Al eliminar una instancia, se borran todas las bases de datos creadas en dicha instancia.

En la línea de comandos

gcloud spanner instances delete test-instance

Usar la Google Cloud consola

  1. Ve a la página Instancias de Spanner de la Google Cloud consola.

    Ir a la página Instancias

  2. Haz clic en tu instancia.

  3. Haz clic en Eliminar.

  4. Confirma que deseas eliminar la instancia y haz clic en Eliminar.

Siguientes pasos