Primeiros passos com o Cloud Spanner em C#

Objetivos

Este tutorial apresenta as seguintes etapas usando a biblioteca de cliente do Cloud Spanner para C#:

  • Criar uma instância e um banco de dados do Cloud Spanner
  • Gravar, ler e executar consultas SQL em dados contidos no banco de dados.
  • Atualizar o esquema do banco de dados.
  • Atualizar dados usando uma transação de leitura e gravação.
  • Adicionar um índice secundário ao banco de dados.
  • Usar o índice para ler e executar consultas SQL nos dados.
  • Recuperar dados usando uma transação somente leitura.

Custos

Neste tutorial, usamos o Cloud Spanner, que é um componente faturável do Google Cloud Platform. Para informações sobre o custo de utilização do Cloud Spanner, consulte Preços.

Antes de começar

  1. Conclua as etapas descritas em Configuração, que abordam a criação e configuração de um projeto padrão do Google Cloud Platform, a ativação do faturamento, a ativação da API Cloud Spanner e a configuração do OAuth 2.0 para receber credenciais de autenticação e usar a API Cloud Spanner.
    Em especial, execute gcloud auth application-default login para configurar o ambiente local de desenvolvimento com as credenciais de autenticação.

  2. Defina a variável de ambiente GOOGLE_PROJECT_ID para o código do projeto do Google Cloud Platform.

    1. Primeiro, defina GOOGLE_PROJECT_ID para a sessão atual do PowerShell:

      $env:GOOGLE_PROJECT_ID = "MY_PROJECT_ID"
    2. Em seguida, configure GOOGLE_PROJECT_ID para todos os processos criados após este comando:

      [Environment]::SetEnvironmentVariable("GOOGLE_PROJECT_ID", "MY_PROJECT_ID", "User")
  3. Faça o download das credenciais.

    1. Visite a página APIs e serviços no Console do Google Cloud Platform e clique em Credenciais.
      Ir para a página "APIs e serviços"
    2. Clique em Criar credenciais e escolha Chave da conta de serviço.
    3. Na opção "Conta de serviço", escolha a Conta de serviço padrão do Compute Engine e selecione JSON em "Tipo de chave". Clique em Criar. Será feito o download de um arquivo JSON.
  4. Configure as credenciais Para um arquivo chamado FILENAME.json no diretório Downloads de CURRENT_USER, localizado no drive C, execute os seguintes comandos para definir o GOOGLE_APPLICATION_CREDENTIALS para apontar para a chave JSON:

    1. Primeiro, para configurar GOOGLE_APPLICATION_CREDENTIALS para esta sessão do PowerShell, execute:

      $env:GOOGLE_APPLICATION_CREDENTIALS = "C:\Users\CURRENT_USER\Downloads\FILENAME.json"
    2. Em seguida, para configurar GOOGLE_APPLICATION_CREDENTIALS para todos os processos criados após este comando, execute:

      [Environment]::SetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS", "C:\Users\CURRENT_USER\Downloads\FILENAME.json", "User")
  5. Clone o repositório do app de amostra na máquina local:

    git clone https://github.com/GoogleCloudPlatform/dotnet-docs-samples
    

    Outra alternativa é fazer o download da amostra como um arquivo ZIP e extraí-lo.

  6. Com o Visual Studio 2017 ou posterior, abra o Spanner.sln, localizado no diretório dotnet-docs-samples\spanner\api do repositório transferido por download e depois o crie.

  7. No mesmo repositório, altere para o diretório que contém o aplicativo compilado. Por exemplo:

    cd dotnet-docs-samples\spanner\api\Spanner
    

Criar uma instância

Ao usar o Cloud Spanner pela primeira vez, é necessário criar uma instância, que é uma alocação de recursos usados pelos bancos de dados do Cloud Spanner. Ao criar uma instância, escolha uma configuração para ela que determine onde os dados serão armazenados e também o número de nós a serem usados. Com isso, é definida a quantidade de recursos de serviço e de armazenamento na instância.

Execute o seguinte comando para criar uma instância do Cloud Spanner na região us-central1 com um nó:

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

A instância criada tem as seguintes características:

  • O ID dela é test-instance.
  • O nome de exibição é Test Instance.
  • A configuração da instância é regional-us-central1. Com as configurações regionais, os dados são armazenados em uma região. Já com as configurações multirregionais, os dados são distribuídos em diversas regiões. Saiba mais em Instâncias.
  • A contagem de nós é 1. node_count corresponde à quantidade de recursos de serviço e de armazenamento disponíveis para os bancos de dados na instância. Saiba mais em Contagem de nós.

Você verá:

Creating instance...done.

Consultar os arquivos de amostra

No repositório de amostras há uma que explica como usar o Cloud Spanner com C#.

Confira o arquivo spanner/api/Spanner/Program.cs, que mostra como criar um banco de dados e modificar um esquema de banco de dados. Os dados usam o esquema de exemplo mostrado na página Modelo de dados e esquema.

Criar um banco de dados

Para criar um banco de dados chamado example-db na instância chamada test-instance, execute o seguinte código na linha de comando:

dotnet run createSampleDatabase $env:GOOGLE_PROJECT_ID test-instance example-db

Você verá:

Created database example-db on instance test-instance

Você acabou de criar um banco de dados do Cloud Spanner. Veja a seguir o código usado na criação do banco de dados.

// Initialize request connection string for database creation.
string connectionString =
    $"Data Source=projects/{projectId}/instances/{instanceId}";
// Make the request.
using (var connection = new SpannerConnection(connectionString))
{
    string createStatement = $"CREATE DATABASE `{databaseId}`";
    var cmd = connection.CreateDdlCommand(createStatement);
    try
    {
        await cmd.ExecuteNonQueryAsync();
    }
    catch (SpannerException e) when (e.ErrorCode == ErrorCode.AlreadyExists)
    {
        // OK.
    }
}
// Update connection string with Database ID for table creation.
connectionString = connectionString + $"/databases/{databaseId}";
using (var connection = new SpannerConnection(connectionString))
{
    // Define create table statement for table #1.
    string createTableStatement =
   @"CREATE TABLE Singers (
         SingerId INT64 NOT NULL,
         FirstName    STRING(1024),
         LastName STRING(1024),
         ComposerInfo   BYTES(MAX)
     ) PRIMARY KEY (SingerId)";
    // Make the request.
    var cmd = connection.CreateDdlCommand(createTableStatement);
    await cmd.ExecuteNonQueryAsync();
    // Define create table statement for table #2.
    createTableStatement =
    @"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";
    // Make the request.
    cmd = connection.CreateDdlCommand(createTableStatement);
    await cmd.ExecuteNonQueryAsync();
}

O código também define duas tabelas, Singers e Albums, para um aplicativo básico de música. Essas tabelas são usadas em toda esta página. Caso ainda não tenha feito isso, confira o exemplo de esquema.

A próxima etapa é gravar dados no seu banco de dados.

Criar um cliente de banco de dados

Para fazer leituras ou gravações, crie uma Spanner​Connection:

using Google.Cloud.Spanner.Data;
using System;
using System.Threading.Tasks;

namespace GoogleCloudSamples.Spanner
{
    public class QuickStart
    {
        static async Task MainAsync()
        {
            string projectId = "YOUR-PROJECT-ID";
            string instanceId = "my-instance";
            string databaseId = "my-database";
            string connectionString =
                $"Data Source=projects/{projectId}/instances/{instanceId}/"
                + $"databases/{databaseId}";
            // Create connection to Cloud Spanner.
            using (var connection = new SpannerConnection(connectionString))
            {
                // Execute a simple SQL statement.
                var cmd = connection.CreateSelectCommand(
                    @"SELECT ""Hello World"" as test");
                using (var reader = await cmd.ExecuteReaderAsync())
                {
                    while (await reader.ReadAsync())
                    {
                        Console.WriteLine(
                            reader.GetFieldValue<string>("test"));
                    }
                }
            }
        }
        public static void Main(string[] args)
        {
            MainAsync().Wait();
        }
    }
}

Pense na Spanner​Connection como uma conexão de banco de dados: todas as interações com o Cloud Spanner precisam passar por uma Spanner​Connection.

Saiba mais na referência da Spanner​Connection.

Escrever dados com DML

É possível inserir dados usando a linguagem de manipulação de dados (DML, na sigla em inglês) em uma transação de leitura/gravação.

Use o método ExecuteNonQueryAsync() para executar uma declaração DML.

public static async Task WriteUsingDmlCoreAsync(
    string projectId,
    string instanceId,
    string databaseId)
{
    string connectionString =
        $"Data Source=projects/{projectId}/instances/{instanceId}"
        + $"/databases/{databaseId}";

    // Create connection to Cloud Spanner.
    using (var connection =
        new SpannerConnection(connectionString))
    {
        await connection.OpenAsync();

        SpannerCommand cmd = connection.CreateDmlCommand(
            "INSERT Singers (SingerId, FirstName, LastName) VALUES "
               + "(12, 'Melissa', 'Garcia'), "
               + "(13, 'Russell', 'Morales'), "
               + "(14, 'Jacqueline', 'Long'), "
               + "(15, 'Dylan', 'Shaw')");
        int rowCount = await cmd.ExecuteNonQueryAsync();
        Console.WriteLine($"{rowCount} row(s) inserted...");
    }
}

Execute a amostra usando o argumento writeusingdml.

dotnet run writeusingdml $env:GOOGLE_PROJECT_ID test-instance example-db

Você verá:

4 row(s) inserted...

Gravar dados com mutações

Também é possível inserir dados usando mutações.

É possível inserir dados usando o método connection.CreateInsertCommand(), que cria um novo SpannerCommand para inserir linhas em uma tabela. Com o método SpannerCommand.ExecuteNonQueryAsync(), novas linhas são adicionadas à tabela.

Este código mostra como inserir dados usando mutações:

public class Singer
{
    public int singerId { get; set; }
    public string firstName { get; set; }
    public string lastName { get; set; }
}

public class Album
{
    public int singerId { get; set; }
    public int albumId { get; set; }
    public string albumTitle { get; set; }
}
public static async Task InsertSampleDataAsync(
    string projectId, string instanceId, string databaseId)
{
    const int firstSingerId = 1;
    const int secondSingerId = 2;
    string connectionString =
    $"Data Source=projects/{projectId}/instances/{instanceId}"
    + $"/databases/{databaseId}";
    List<Singer> singers = new List<Singer> {
        new Singer {singerId = firstSingerId, firstName = "Marc",
            lastName = "Richards"},
        new Singer {singerId = secondSingerId, firstName = "Catalina",
            lastName = "Smith"},
        new Singer {singerId = 3, firstName = "Alice",
            lastName = "Trentor"},
        new Singer {singerId = 4, firstName = "Lea",
            lastName = "Martin"},
        new Singer {singerId = 5, firstName = "David",
            lastName = "Lomond"},
    };
    List<Album> albums = new List<Album> {
        new Album {singerId = firstSingerId, albumId = 1,
            albumTitle = "Total Junk"},
        new Album {singerId = firstSingerId, albumId = 2,
            albumTitle = "Go, Go, Go"},
        new Album {singerId = secondSingerId, albumId = 1,
            albumTitle = "Green"},
        new Album {singerId = secondSingerId, albumId = 2,
            albumTitle = "Forever Hold your Peace"},
        new Album {singerId = secondSingerId, albumId = 3,
            albumTitle = "Terrified"},
    };
    // Create connection to Cloud Spanner.
    using (var connection = new SpannerConnection(connectionString))
    {
        await connection.OpenAsync();

        // Insert rows into the Singers table.
        var cmd = connection.CreateInsertCommand("Singers",
            new SpannerParameterCollection {
                {"SingerId", SpannerDbType.Int64},
                {"FirstName", SpannerDbType.String},
                {"LastName", SpannerDbType.String}
        });
        await Task.WhenAll(singers.Select(singer =>
        {
            cmd.Parameters["SingerId"].Value = singer.singerId;
            cmd.Parameters["FirstName"].Value = singer.firstName;
            cmd.Parameters["LastName"].Value = singer.lastName;
            return cmd.ExecuteNonQueryAsync();
        }));

        // Insert rows into the Albums table.
        cmd = connection.CreateInsertCommand("Albums",
            new SpannerParameterCollection {
                {"SingerId", SpannerDbType.Int64},
                {"AlbumId", SpannerDbType.Int64},
                {"AlbumTitle", SpannerDbType.String}
        });
        await Task.WhenAll(albums.Select(album =>
        {
            cmd.Parameters["SingerId"].Value = album.singerId;
            cmd.Parameters["AlbumId"].Value = album.albumId;
            cmd.Parameters["AlbumTitle"].Value = album.albumTitle;
            return cmd.ExecuteNonQueryAsync();
        }));
        Console.WriteLine("Inserted data.");
    }
}

Execute a amostra usando o argumento insertSampleData.

dotnet run insertSampleData $env:GOOGLE_PROJECT_ID test-instance example-db

Você verá:

Inserted data.

Consultar dados usando SQL

O Cloud Spanner é compatível com uma interface SQL nativa para leitura de dados, que você acessa na linha de comando usando a ferramenta de linha de comando gcloud ou usando a biblioteca de cliente do Cloud Spanner para C#.

Na linha de comando

Execute a instrução SQL a seguir 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 será:

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

Como usar a biblioteca de cliente do Cloud Spanner para C#

Além de executar uma instrução SQL na linha de comando, é possível emiti-la de maneira programática usando a biblioteca de cliente do Cloud Spanner para C#.

Use ExecuteReaderAsync() para executar a consulta SQL.

string connectionString =
$"Data Source=projects/{projectId}/instances/"
+ $"{instanceId}/databases/{databaseId}";
// Create connection to Cloud Spanner.
using (var connection = new SpannerConnection(connectionString))
{
    var cmd = connection.CreateSelectCommand(
        "SELECT SingerId, AlbumId, AlbumTitle FROM Albums");
    using (var reader = await cmd.ExecuteReaderAsync())
    {
        while (await reader.ReadAsync())
        {
            Console.WriteLine("SingerId : "
                + reader.GetFieldValue<string>("SingerId")
                + " AlbumId : "
                + reader.GetFieldValue<string>("AlbumId")
                + " AlbumTitle : "
                + reader.GetFieldValue<string>("AlbumTitle"));
        }
    }
}

Veja como emitir a consulta e acessar os dados:

dotnet run querySampleData $env:GOOGLE_PROJECT_ID test-instance example-db

Você verá o resultado a seguir:

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

Atualizar o esquema do banco de dados

Imagine que você precise adicionar uma nova coluna chamada MarketingBudget à tabela Albums. Para adicionar uma nova coluna a uma tabela, é necessário atualizar seu esquema de banco de dados. O Cloud Spanner é compatível com atualizações de esquema em um banco de dados enquanto esse banco continua a disponibilizar o tráfego. Para fazer atualizações no esquema, não é necessário desconectar o banco de dados, nem bloquear tabelas ou colunas inteiras. É possível continuar gravando dados no banco de dados durante a atualização do esquema. Leia mais sobre as atualizações de esquemas compatíveis e o desempenho das alterações de esquemas em Atualizações de esquema.

Adicionar uma coluna

Adicione uma coluna na linha de comando usando a ferramenta de linha de comando gcloud ou usando a biblioteca de cliente do Cloud Spanner para C# de maneira programática.

Na linha de comando

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

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

Você verá:

DDL updating...done.

Como usar a biblioteca de cliente do Cloud Spanner para C#

Use CreateDdlCommand() para modificar o esquema:

// Initialize request argument(s).
string connectionString =
    $"Data Source=projects/{projectId}/instances/"
    + $"{instanceId}/databases/{databaseId}";
string alterStatement =
    "ALTER TABLE Albums ADD COLUMN MarketingBudget INT64";
// Make the request.
using (var connection = new SpannerConnection(connectionString))
{
    var updateCmd = connection.CreateDdlCommand(alterStatement);
    await updateCmd.ExecuteNonQueryAsync();
}
Console.WriteLine("Added the MarketingBudget column.");

Execute a amostra usando o comando addColumn.

dotnet run addColumn $env:GOOGLE_PROJECT_ID test-instance example-db

Você verá:

Added the MarketingBudget column.

Gravar dados na nova coluna

O código a seguir grava dados na nova coluna. Ele define MarketingBudget como 100000 na linha indexada por Albums(1, 1) e como 500000 na linha indexada por Albums(2, 2).

string connectionString =
$"Data Source=projects/{projectId}/instances/{instanceId}"
+ $"/databases/{databaseId}";
// Create connection to Cloud Spanner.
using (var connection = new SpannerConnection(connectionString))
{
    var cmd = connection.CreateUpdateCommand("Albums",
        new SpannerParameterCollection {
            {"SingerId", SpannerDbType.Int64},
            {"AlbumId", SpannerDbType.Int64},
            {"MarketingBudget", SpannerDbType.Int64},
        });
    var cmdLookup =
        connection.CreateSelectCommand("SELECT * FROM Albums");
    using (var reader = await cmdLookup.ExecuteReaderAsync())
    {
        while (await reader.ReadAsync())
        {
            if (reader.GetFieldValue<int>("SingerId") == 1
                && reader.GetFieldValue<int>("AlbumId") == 1)
            {
                cmd.Parameters["SingerId"].Value =
                    reader.GetFieldValue<int>("SingerId");
                cmd.Parameters["AlbumId"].Value =
                    reader.GetFieldValue<int>("AlbumId");
                cmd.Parameters["MarketingBudget"].Value = 100000;
                await cmd.ExecuteNonQueryAsync();
            }
            if (reader.GetInt64(0) == 2 && reader.GetInt64(1) == 2)
            {
                cmd.Parameters["SingerId"].Value =
                    reader.GetFieldValue<int>("SingerId");
                cmd.Parameters["AlbumId"].Value =
                    reader.GetFieldValue<int>("AlbumId");
                cmd.Parameters["MarketingBudget"].Value = 500000;
                await cmd.ExecuteNonQueryAsync();
            }
        }
    }
}
Console.WriteLine("Updated data.");

Execute a amostra usando o comando writeDataToNewColumn.

dotnet run writeDataToNewColumn $env:GOOGLE_PROJECT_ID test-instance example-db

Você verá:

Updated data.

Você também pode executar uma consulta SQL para buscar os valores que acabou de gravar.

Veja a seguir o código para executar a consulta:

string connectionString =
$"Data Source=projects/{projectId}/instances/{instanceId}"
+ $"/databases/{databaseId}";
// Create connection to Cloud Spanner.
using (var connection = new SpannerConnection(connectionString))
{
    var cmd =
        connection.CreateSelectCommand("SELECT * FROM Albums");
    using (var reader = await cmd.ExecuteReaderAsync())
    {
        while (await reader.ReadAsync())
        {
            string budget = string.Empty;
            if (reader["MarketingBudget"] != DBNull.Value)
            {
                budget = reader.GetFieldValue<string>("MarketingBudget");
            }
            Console.WriteLine("SingerId : "
            + reader.GetFieldValue<string>("SingerId")
            + " AlbumId : "
            + reader.GetFieldValue<string>("AlbumId")
            + $" MarketingBudget : {budget}");
        }
    }
}

Para realizar esta consulta, execute a amostra usando o argumento queryNewColumn.

dotnet run queryNewColumn $env:GOOGLE_PROJECT_ID test-instance example-db

Você verá:

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

Atualizar dados

É possível atualizar dados usando DML em uma transação de leitura/gravação.

Use o método ExecuteNonQueryAsync() para executar uma declaração DML.

public static async Task WriteWithTransactionUsingDmlCoreAsync(
    string projectId,
    string instanceId,
    string databaseId)
{
    // This sample transfers 200,000 from the MarketingBudget
    // field of the first Album to the second Album. Make sure to run
    // the addColumn and writeDataToNewColumn samples first,
    // in that order.
    string connectionString =
        $"Data Source=projects/{projectId}/instances/{instanceId}"
        + $"/databases/{databaseId}";

    decimal transferAmount = 200000;
    decimal minimumAmountToTransfer = 300000;
    decimal firstBudget = 0;
    decimal secondBudget = 0;

    // Create connection to Cloud Spanner.
    using (var connection =
        new SpannerConnection(connectionString))
    {
        await connection.OpenAsync();

        // Create a readwrite transaction that we'll assign
        // to each SpannerCommand.
        using (var transaction =
                await connection.BeginTransactionAsync())
        {
            // Create statement to select the first album's data.
            var cmdLookup = connection.CreateSelectCommand(
             "SELECT * FROM Albums WHERE SingerId = 1 AND AlbumId = 1");
            cmdLookup.Transaction = transaction;
            // Excecute the select query.
            using (var reader = await cmdLookup.ExecuteReaderAsync())
            {
                while (await reader.ReadAsync())
                {
                    // Read the first album's budget.
                    firstBudget =
                       reader.GetFieldValue<decimal>("MarketingBudget");
                    // Confirm first Album's budget is sufficient and
                    // if not raise an exception. Raising an exception
                    // will automatically roll back the transaction.
                    if (firstBudget < minimumAmountToTransfer)
                    {
                        throw new Exception("The first album's "
                                + $"budget {firstBudget} "
                                + "is less than the minimum required "
                                + "amount to transfer.");
                    }
                }
            }
            // Read the second album's budget.
            cmdLookup = connection.CreateSelectCommand(
             "SELECT * FROM Albums WHERE SingerId = 2 and AlbumId = 2");
            cmdLookup.Transaction = transaction;
            using (var reader = await cmdLookup.ExecuteReaderAsync())
            {
                while (await reader.ReadAsync())
                {
                    secondBudget =
                      reader.GetFieldValue<decimal>("MarketingBudget");
                }
            }

            // Update first album to remove the transfer amount.
            firstBudget -= transferAmount;
            SpannerCommand cmd = connection.CreateDmlCommand(
                "UPDATE Albums SET MarketingBudget = @MarketingBudget "
                + "WHERE SingerId = 1 and AlbumId = 1");
            cmd.Parameters.Add("MarketingBudget", SpannerDbType.Int64, firstBudget);
            cmd.Transaction = transaction;
            await cmd.ExecuteNonQueryAsync();
            // Update second album to add the transfer amount.
            secondBudget += transferAmount;
            cmd = connection.CreateDmlCommand(
                "UPDATE Albums SET MarketingBudget = @MarketingBudget "
                + "WHERE SingerId = 2 and AlbumId = 2");
            cmd.Parameters.Add("MarketingBudget", SpannerDbType.Int64, secondBudget);
            cmd.Transaction = transaction;
            await cmd.ExecuteNonQueryAsync();

            await transaction.CommitAsync();
        }
        Console.WriteLine("Transaction complete.");
    }
}

Execute a amostra usando o argumento writeWithTransactionUsingDml.

dotnet run writeWithTransactionUsingDml $env:GOOGLE_PROJECT_ID test-instance example-db

Você verá:

Transaction complete.

Como executar novas tentativas em transações de leitura e gravação

O Cloud Spanner executa novas tentativas para cada chamada de rede e é resistente a falhas de rede. No entanto, quando há uma carga elevada, pode ocorrer um impasse. Isso faz com que uma transação do Cloud Spanner emita uma SpannerException "Cancelada". Para lidar com essa exceção, use uma abordagem de "novas tentativas", conforme mostrado abaixo, para repetir toda a transação.

Primeiro, defina um método que será chamado quando a transação precisar ser repetida. O exemplo a seguir define um método chamado RetryRobot.

public class RetryRobot
{
    public TimeSpan FirstRetryDelay { get; set; } = TimeSpan.FromSeconds(1000);
    public float DelayMultiplier { get; set; } = 2;
    public int MaxTryCount { get; set; } = 7;
    public Func<Exception, bool> ShouldRetry { get; set; }

    /// <summary>
    /// Retry action when assertion fails.
    /// </summary>
    /// <param name="func"></param>
    public T Eventually<T>(Func<T> func)
    {
        TimeSpan delay = FirstRetryDelay;
        for (int i = 0; ; ++i)
        {
            try
            {
                return func();
            }
            catch (Exception e)
            when (ShouldCatch(e) && i < MaxTryCount)
            {
                Thread.Sleep(delay);
                delay *= (int)DelayMultiplier;
            }
        }
    }

    private bool ShouldCatch(Exception e)
    {
        return ShouldRetry != null && ShouldRetry(e);
    }
}

Em seguida, crie uma instância, denominada retryRobot, do método RetryRobot, especificando IsTransientSpannerFault() como a condição de erro que será repetida. Depois, execute a transação inteira usando retryRobot.Eventually.

Veja a seguir o código para executar uma nova tentativa:

var retryRobot = new RetryRobot
{
    MaxTryCount = 3,
    DelayMultiplier = 2,
    ShouldRetry = (e) => e.IsTransientSpannerFault()
};

await retryRobot.Eventually(() =>
    ReadWriteWithTransactionAsync(
        projectId, instanceId, databaseId));

Usar um índice secundário

Suponha que você queira buscar todas as linhas de Albums que tenham valores de AlbumTitle em um determinado intervalo. É possível ler todos os valores da coluna AlbumTitle usando uma instrução SQL ou uma chamada de leitura e, em seguida, descartar as linhas que não atendam aos critérios. Porém, fazer essa varredura em uma tabela completa é um processo caro, especialmente em tabelas com muitas linhas. Em vez disso, acelere a recuperação de linhas ao pesquisar por colunas de chaves não primárias por meio da criação de um índice secundário na tabela.

Adicionar um índice secundário a uma tabela requer uma atualização de esquema. Como outras atualizações de esquema, o Cloud Spanner é compatível com a adição de um índice enquanto o banco de dados continua a disponibilizar o tráfego. O Cloud Spanner preenche o índice com dados (processo conhecido como "preenchimento") em segundo plano. Pode levar alguns minutos para que os preenchimentos sejam concluídos, mas não é necessário deixar o banco de dados off-line ou evitar a gravação em determinadas tabelas ou colunas durante esse processo. Para mais detalhes, consulte o preenchimento de índices.

Adicionar um índice secundário

Adicione um índice na linha de comando usando a ferramenta de linha de comando gcloud ou usando a biblioteca de cliente do Cloud Spanner para C# de maneira programática.

Na linha de comando

Use o comando CREATE INDEX a seguir para adicionar um índice ao banco de dados:

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

Você verá:

DDL updating...done.

Como usar a biblioteca de cliente do Cloud Spanner para C#

Use CreateDdlCommand() para adicionar um índice:

// Initialize request argument(s).
string connectionString =
    $"Data Source=projects/{projectId}/instances/"
    + $"{instanceId}/databases/{databaseId}";
string createStatement =
    "CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)";
// Make the request.
using (var connection = new SpannerConnection(connectionString))
{
    var createCmd = connection.CreateDdlCommand(createStatement);
    await createCmd.ExecuteNonQueryAsync();
}
Console.WriteLine("Added the AlbumsByAlbumTitle index.");

Execute a amostra usando o comando addIndex.

  dotnet run addIndex $env:GOOGLE_PROJECT_ID test-instance example-db

A adição do índice pode levar alguns minutos. Depois da adição, você verá:

  Added the AlbumsByAlbumTitle index.

Fazer uma consulta usando o índice

Faça uma consulta usando o novo índice na linha de comando ou na biblioteca de cliente.

Na linha de comando

Execute uma instrução SQL usando a ferramenta de linha de comando gcloud para buscar AlbumId, AlbumTitle e MarketingBudget em Albums usando o índice AlbumsByAlbumTitle para o intervalo de AlbumsTitle em ["Aardvark", "Goo").

gcloud spanner databases execute-sql example-db --instance=test-instance --sql='SELECT AlbumId, AlbumTitle, MarketingBudget FROM Albums@{FORCE_INDEX=AlbumsByAlbumTitle} WHERE AlbumTitle >= "Aardvark" AND AlbumTitle < "Goo"'

O resultado será:

AlbumId  AlbumTitle               MarketingBudget
2        Go, Go, Go
2        Forever Hold Your Peace  300000

Como usar a biblioteca de cliente do Cloud Spanner para C#

O código para usar o índice de maneira programática é semelhante ao código de consulta usado anteriormente.

string connectionString =
$"Data Source=projects/{projectId}/instances/{instanceId}"
+ $"/databases/{databaseId}";
// Create connection to Cloud Spanner.
using (var connection = new SpannerConnection(connectionString))
{
    var cmd = connection.CreateSelectCommand(
        "SELECT AlbumId, AlbumTitle, MarketingBudget FROM Albums@ "
        + "{FORCE_INDEX=AlbumsByAlbumTitle} "
        + $"WHERE AlbumTitle >= @startTitle "
        + $"AND AlbumTitle < @endTitle",
        new SpannerParameterCollection {
            {"startTitle", SpannerDbType.String},
            {"endTitle", SpannerDbType.String} });
    cmd.Parameters["startTitle"].Value = startTitle;
    cmd.Parameters["endTitle"].Value = endTitle;
    using (var reader = await cmd.ExecuteReaderAsync())
    {
        while (await reader.ReadAsync())
        {
            Console.WriteLine("AlbumId : "
            + reader.GetFieldValue<string>("AlbumId")
            + " AlbumTitle : "
            + reader.GetFieldValue<string>("AlbumTitle")
            + " MarketingBudget : "
            + reader.GetFieldValue<string>("MarketingBudget"));
        }
    }
}

Execute a amostra usando o comando queryDataWithIndex.

dotnet run queryDataWithIndex $env:GOOGLE_PROJECT_ID test-instance example-db

Você verá um resultado similar a:

AlbumId : 1 AlbumTitle : Go, Go, Go MarketingBudget : 300000
AlbumId : 2 AlbumTitle : Forever Hold your Peace MarketingBudget : 300000

Para mais detalhes, consulte a referência de:

Adicionar um índice com uma cláusula STORING

Talvez você tenha percebido que o exemplo de leitura acima não inclui a leitura da coluna MarketingBudget. Isso ocorre porque a interface de leitura do Cloud Spanner não é compatível com a capacidade de fazer a junção de um índice a uma tabela de dados para pesquisar 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 comando

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

A adição do índice pode levar alguns minutos. Depois da adição, você verá:

DDL updating...done.

Como usar a biblioteca de cliente do Cloud Spanner para C#

Use CreateDdlCommand() para adicionar um índice com uma cláusula STORING:

// Initialize request argument(s).
string connectionString =
    $"Data Source=projects/{projectId}/instances/"
    + $"{instanceId}/databases/{databaseId}";
string createStatement =
    "CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) "
    + "STORING (MarketingBudget)";
// Make the request.
using (var connection = new SpannerConnection(connectionString))
{
    var createCmd = connection.CreateDdlCommand(createStatement);
    await createCmd.ExecuteNonQueryAsync();
}
Console.WriteLine("Added the AlbumsByAlbumTitle2 index.");

Execute a amostra usando o comando addStoringIndex.

dotnet run addStoringIndex $env:GOOGLE_PROJECT_ID test-instance example-db

Você verá:

Added the AlbumsByAlbumTitle2 index.

Agora, é possível executar uma leitura que busque todas as colunas AlbumId, AlbumTitle e MarketingBudget do índice AlbumsByAlbumTitle2:

Leia os dados usando o índice de armazenamento criado, executando uma consulta que especifique de forma explícita o índice:

string connectionString =
$"Data Source=projects/{projectId}/instances/{instanceId}"
+ $"/databases/{databaseId}";
// Create connection to Cloud Spanner.
using (var connection = new SpannerConnection(connectionString))
{
    var cmd = connection.CreateSelectCommand(
        "SELECT AlbumId, AlbumTitle, MarketingBudget FROM Albums@ "
        + "{FORCE_INDEX=AlbumsByAlbumTitle2} "
        + $"WHERE AlbumTitle >= @startTitle "
        + $"AND AlbumTitle < @endTitle",
        new SpannerParameterCollection {
            {"startTitle", SpannerDbType.String},
            {"endTitle", SpannerDbType.String} });
    cmd.Parameters["startTitle"].Value = startTitle;
    cmd.Parameters["endTitle"].Value = endTitle;
    using (var reader = await cmd.ExecuteReaderAsync())
    {
        while (await reader.ReadAsync())
        {
            Console.WriteLine("AlbumId : "
            + reader.GetFieldValue<string>("AlbumId")
            + " AlbumTitle : "
            + reader.GetFieldValue<string>("AlbumTitle")
            + " MarketingBudget : "
            + reader.GetFieldValue<string>("MarketingBudget"));
        }
    }
}

Execute a amostra usando o comando queryDataWithStoringIndex.

dotnet run queryDataWithStoringIndex $env:GOOGLE_PROJECT_ID test-instance example-db

Você verá um resultado similar a:

AlbumId : 2 AlbumTitle : Forever Hold your Peace MarketingBudget : 300000
AlbumId : 1 AlbumTitle : Go, Go, Go MarketingBudget : 300000

Recuperar dados usando transações somente leitura

Suponha que você queira executar mais de uma leitura no mesmo carimbo de data/hora. As transações somente leitura observam um prefixo consistente do histórico de confirmações da transação. Portanto, o aplicativo sempre recebe dados consistentes. Use TransactionScope() do .NET framework com OpenAsReadOnlyAsync() para executar transações somente leitura.

Veja a seguir como executar uma consulta e fazer uma leitura na mesma transação somente leitura.

.NET Standard 2.0

string connectionString =
$"Data Source=projects/{projectId}/instances/{instanceId}"
+ $"/databases/{databaseId}";
// Gets a transaction object that captures the database state
// at a specific point in time.
using (TransactionScope scope = new TransactionScope(
    TransactionScopeAsyncFlowOption.Enabled))
{
    // Create connection to Cloud Spanner.
    using (var connection = new SpannerConnection(connectionString))
    {
        // Open the connection, making the implicitly created
        // transaction read only when it connects to the outer
        // transaction scope.
        await connection.OpenAsReadOnlyAsync()
            .ConfigureAwait(false);
        var cmd = connection.CreateSelectCommand(
            "SELECT SingerId, AlbumId, AlbumTitle FROM Albums");
        // Read #1.
        using (var reader = await cmd.ExecuteReaderAsync())
        {
            while (await reader.ReadAsync())
            {
                Console.WriteLine("SingerId : "
                    + reader.GetFieldValue<string>("SingerId")
                    + " AlbumId : "
                    + reader.GetFieldValue<string>("AlbumId")
                    + " AlbumTitle : "
                    + reader.GetFieldValue<string>("AlbumTitle"));
            }
        }
        // Read #2. Even if changes occur in-between the reads,
        // the transaction ensures that Read #1 and Read #2
        // return the same data.
        using (var reader = await cmd.ExecuteReaderAsync())
        {
            while (await reader.ReadAsync())
            {
                Console.WriteLine("SingerId : "
                    + reader.GetFieldValue<string>("SingerId")
                    + " AlbumId : "
                    + reader.GetFieldValue<string>("AlbumId")
                    + " AlbumTitle : "
                    + reader.GetFieldValue<string>("AlbumTitle"));
            }
        }
    }
    scope.Complete();
    Console.WriteLine("Transaction complete.");
}

.NET Standard 1.5

string connectionString =
    $"Data Source=projects/{projectId}/instances/{instanceId}"
    + $"/databases/{databaseId}";

// Create connection to Cloud Spanner.
using (var connection = new SpannerConnection(connectionString))
{
    await connection.OpenAsync();

    // Open a new read only transaction.
    using (var transaction =
        await connection.BeginReadOnlyTransactionAsync())
    {
        var cmd = connection.CreateSelectCommand(
            "SELECT SingerId, AlbumId, AlbumTitle FROM Albums");
        cmd.Transaction = transaction;

        // Read #1.
        using (var reader = await cmd.ExecuteReaderAsync())
        {
            while (await reader.ReadAsync())
            {
                Console.WriteLine("SingerId : "
                    + reader.GetFieldValue<string>("SingerId")
                    + " AlbumId : "
                    + reader.GetFieldValue<string>("AlbumId")
                    + " AlbumTitle : "
                    + reader.GetFieldValue<string>("AlbumTitle"));
            }
        }
        // Read #2. Even if changes occur in-between the reads,
        // the transaction ensures that Read #1 and Read #2
        // return the same data.
        using (var reader = await cmd.ExecuteReaderAsync())
        {
            while (await reader.ReadAsync())
            {
                Console.WriteLine("SingerId : "
                    + reader.GetFieldValue<string>("SingerId")
                    + " AlbumId : "
                    + reader.GetFieldValue<string>("AlbumId")
                    + " AlbumTitle : "
                    + reader.GetFieldValue<string>("AlbumTitle"));
            }
        }
    }
}
Console.WriteLine("Transaction complete.");

Execute a amostra usando o comando queryDataWithTransaction.

dotnet run queryDataWithTransaction $env:GOOGLE_PROJECT_ID test-instance example-db

Você verá um resultado similar a:

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

Limpeza

Para evitar cobranças adicionais na sua conta do Google Cloud Platform pelos recursos utilizados neste tutorial, descarte o banco de dados e exclua a instância que você criou.

Excluir o banco de dados

Se você excluir uma instância, todos os bancos de dados nela serão excluídos automaticamente. Nesta etapa, mostramos como excluir um banco de dados sem remover a instância. Ainda pode haver cobrança em relação à instância.

Na linha de comando

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

No Console do GCP

  1. Acesse a página Instâncias do Cloud Spanner no Console do Google Cloud Platform.
    Ir para a página "Instâncias" do Cloud Spanner
  2. Clique na instância.
  3. Clique no banco de dados que você quer excluir.
  4. Na página Detalhes do banco de dados, clique em Excluir.
  5. Confirme se quer excluir o banco de dados e clique em Excluir.

Excluir a instância

A exclusão de uma instância descarta automaticamente todos os bancos de dados criados nela.

Na linha de comando

gcloud spanner instances delete test-instance

No Console do GCP

  1. Acesse a página Instâncias do Cloud Spanner no Console do Google Cloud Platform.
    Ir para a página "Instâncias" do Cloud Spanner
  2. Clique na sua instância.
  3. Clique em Excluir.
  4. Confirme se quer excluir a instância e clique em Excluir.

A seguir

Esta página foi útil? Conte sua opinião sobre:

Enviar comentários sobre…

Documentação do Cloud Spanner