Primeiros passos com o Cloud Spanner em Go

Objetivos:

Neste tutorial, explicaremos as etapas a seguir usando a biblioteca de cliente do Cloud Spanner para Go:

  • 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.

    1. Se ainda não tiver feito isso, instale o Go (fazer download) no seu computador.
    2. Caso ainda não tenha configurado a variável de ambiente GOPATH conforme descrito em Testar sua instalação (em inglês), faça isso agora.
    3. Faça o download das amostras para sua máquina.

      go get -u github.com/GoogleCloudPlatform/golang-samples/spanner/...
      
    4. Altere para o diretório que contém o código de amostra do Cloud Spanner:

      cd $GOPATH/src/github.com/GoogleCloudPlatform/golang-samples/spanner/spanner_snippets
      
    5. Defina a variável de ambiente GCLOUD_PROJECT como o ID do projeto do Google Cloud Platform:

      export GCLOUD_PROJECT=[MY_PROJECT_ID]
      

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.

Examinar arquivos de amostra

O repositório de amostras contém um exemplo que mostra como usar o Cloud Spanner com o Go.

Dê uma olhada no arquivo snippet.go, que mostra como usar o Cloud Spanner. O código mostra como criar e usar um novo banco de dados. Os dados usam o esquema de exemplo exibido na página Esquema e modelo de dados.

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:

go run snippet.go createdatabase projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

Você verá:

Created database [example-db]

Você acabou de criar um banco de dados do Cloud Spanner. Veja a seguir o código que criou o banco de dados.

func createDatabase(ctx context.Context, w io.Writer, adminClient *database.DatabaseAdminClient, db string) error {
	matches := regexp.MustCompile("^(.*)/databases/(.*)$").FindStringSubmatch(db)
	if matches == nil || len(matches) != 3 {
		return fmt.Errorf("Invalid database id %s", db)
	}
	op, err := adminClient.CreateDatabase(ctx, &adminpb.CreateDatabaseRequest{
		Parent:          matches[1],
		CreateStatement: "CREATE DATABASE `" + matches[2] + "`",
		ExtraStatements: []string{
			`CREATE TABLE Singers (
				SingerId   INT64 NOT NULL,
				FirstName  STRING(1024),
				LastName   STRING(1024),
				SingerInfo BYTES(MAX)
			) PRIMARY KEY (SingerId)`,
			`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`,
		},
	})
	if err != nil {
		return err
	}
	if _, err := op.Wait(ctx); err != nil {
		return err
	}
	fmt.Fprintf(w, "Created database [%s]\n", db)
	return nil
}

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

Antes de fazer leituras ou gravações, é necessário criar um Client:

func createClients(ctx context.Context, db string) (*database.DatabaseAdminClient, *spanner.Client) {
	adminClient, err := database.NewDatabaseAdminClient(ctx)
	if err != nil {
		log.Fatal(err)
	}

	dataClient, err := spanner.NewClient(ctx, db)
	if err != nil {
		log.Fatal(err)
	}

	return adminClient, dataClient
}

Pense em um Client como uma conexão com o banco de dados: todas as suas interações com o Cloud Spanner precisam passar por um Client. Normalmente, você cria um Client quando seu aplicativo é iniciado e depois o reutiliza esse Client para ler, gravar e executar transações. Como cada cliente usa recursos no Cloud Spanner, chame Client.Close() para limpá-los, incluindo as conexões de rede.

Saiba mais na referência do Client.

O código acima também mostra como criar um DatabaseAdminClient, que é usado para gerar um banco de dados.

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 Update() para executar uma instrução DML.

func writeUsingDML(ctx context.Context, w io.Writer, client *spanner.Client) error {
	_, err := client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
		stmt := spanner.Statement{
			SQL: `INSERT Singers (SingerId, FirstName, LastName) VALUES
				(12, 'Melissa', 'Garcia'),
				(13, 'Russell', 'Morales'),
				(14, 'Jacqueline', 'Long'),
				(15, 'Dylan', 'Shaw')`,
		}
		rowCount, err := txn.Update(ctx, stmt)
		if err != nil {
			return err
		}
		fmt.Fprintf(w, "%d record(s) inserted.\n", rowCount)
		return err
	})
	return err
}

Execute a amostra usando o argumento dmlwrite.

go run snippet.go dmlwrite projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

Você verá:

4 record(s) inserted.

Escrever dados com mutações

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

Um Mutation é um contêiner para operações de mutação. Um Mutation representa uma sequência de inserções, atualizações e exclusões que o Cloud Spanner aplica atomicamente a diferentes linhas e tabelas em um banco de dados do Cloud Spanner.

Use Mutation.InsertOrUpdate() para construir uma mutação INSERT_OR_UPDATE, que adiciona uma nova linha ou atualiza valores de coluna se a linha já existir. Como alternativa, use o método Mutation.Insert() para construir uma mutação INSERT, que adiciona uma nova linha.

Client.Apply() aplica mutações atomicamente a um banco de dados.

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

func write(ctx context.Context, w io.Writer, client *spanner.Client) error {
	singerColumns := []string{"SingerId", "FirstName", "LastName"}
	albumColumns := []string{"SingerId", "AlbumId", "AlbumTitle"}
	m := []*spanner.Mutation{
		spanner.InsertOrUpdate("Singers", singerColumns, []interface{}{1, "Marc", "Richards"}),
		spanner.InsertOrUpdate("Singers", singerColumns, []interface{}{2, "Catalina", "Smith"}),
		spanner.InsertOrUpdate("Singers", singerColumns, []interface{}{3, "Alice", "Trentor"}),
		spanner.InsertOrUpdate("Singers", singerColumns, []interface{}{4, "Lea", "Martin"}),
		spanner.InsertOrUpdate("Singers", singerColumns, []interface{}{5, "David", "Lomond"}),
		spanner.InsertOrUpdate("Albums", albumColumns, []interface{}{1, 1, "Total Junk"}),
		spanner.InsertOrUpdate("Albums", albumColumns, []interface{}{1, 2, "Go, Go, Go"}),
		spanner.InsertOrUpdate("Albums", albumColumns, []interface{}{2, 1, "Green"}),
		spanner.InsertOrUpdate("Albums", albumColumns, []interface{}{2, 2, "Forever Hold Your Peace"}),
		spanner.InsertOrUpdate("Albums", albumColumns, []interface{}{2, 3, "Terrified"}),
	}
	_, err := client.Apply(ctx, m)
	return err
}

Execute a amostra usando o argumento write.

go run snippet.go write projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

Você verá o comando executado com sucesso.

Consultar dados usando SQL

O Cloud Spanner oferece uma interface SQL nativa para leitura de dados, que você pode acessar na linha de comando usando a ferramenta de linha de comando gcloud ou a biblioteca de cliente do Cloud Spanner para Go de modo programático.

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

Usando a biblioteca de cliente do Cloud Spanner para Go

Além de executar uma instrução SQL na linha de comando, você pode emitir a mesma instrução SQL de forma programática usando a biblioteca de cliente do Cloud Spanner para Go.

Os métodos e tipos a seguir são usados para executar a consulta SQL:

  • Client.Single(): use esse método para ler o valor de uma ou mais colunas de uma ou mais linhas em uma tabela do Cloud Spanner. Client.Single retorna uma ReadOnlyTransaction, que é usada para executar uma instrução de leitura ou SQL.
  • ReadOnlyTransaction.Query(): use esse método para executar uma consulta em um banco de dados.
  • O tipo Statement: use-o para construir uma string SQL.
  • O tipo Row: use-o para acessar os dados retornados por uma instrução SQL ou uma chamada de leitura.

Veja como emitir a consulta e acessar os dados:

func query(ctx context.Context, w io.Writer, client *spanner.Client) error {
	stmt := spanner.Statement{SQL: `SELECT SingerId, AlbumId, AlbumTitle FROM Albums`}
	iter := client.Single().Query(ctx, stmt)
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			return nil
		}
		if err != nil {
			return err
		}
		var singerID, albumID int64
		var albumTitle string
		if err := row.Columns(&singerID, &albumID, &albumTitle); err != nil {
			return err
		}
		fmt.Fprintf(w, "%d %d %s\n", singerID, albumID, albumTitle)
	}
}

Execute a amostra usando o argumento query.

go run snippet.go query projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

Você verá o resultado a seguir:

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

Ler dados usando a API de leitura

Além da interface SQL do Cloud Spanner, o Cloud Spanner também é compatível com uma interface de leitura.

Use ReadOnlyTransaction.Read() para ler linhas do banco de dados. Aplique KeySet para definir uma coleção de chaves e intervalos de chaves para leitura.

Veja como ler os dados:

func read(ctx context.Context, w io.Writer, client *spanner.Client) error {
	iter := client.Single().Read(ctx, "Albums", spanner.AllKeys(),
		[]string{"SingerId", "AlbumId", "AlbumTitle"})
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			return nil
		}
		if err != nil {
			return err
		}
		var singerID, albumID int64
		var albumTitle string
		if err := row.Columns(&singerID, &albumID, &albumTitle); err != nil {
			return err
		}
		fmt.Fprintf(w, "%d %d %s\n", singerID, albumID, albumTitle)
	}
}

Execute a amostra usando o argumento read.

go run snippet.go read projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

Você verá uma resposta similar a:

1 1 Total Junk
1 2 Go, Go, Go
2 1 Green
2 2 Forever Hold Your Peace
2 3 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 existente, é 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

Você pode adicionar uma coluna na linha de comando usando a ferramenta de linha de comando gcloud ou a biblioteca de cliente do Cloud Spanner para Go de modo programático.

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 Go

Use DatabaseAdminClient.UpdateDatabaseDdl() para modificar o esquema:

func addNewColumn(ctx context.Context, w io.Writer, adminClient *database.DatabaseAdminClient, database string) error {
	op, err := adminClient.UpdateDatabaseDdl(ctx, &adminpb.UpdateDatabaseDdlRequest{
		Database: database,
		Statements: []string{
			"ALTER TABLE Albums ADD COLUMN MarketingBudget INT64",
		},
	})
	if err != nil {
		return err
	}
	if err := op.Wait(ctx); err != nil {
		return err
	}
	fmt.Fprintf(w, "Added MarketingBudget column\n")
	return nil
}

Execute a amostra usando o argumento addnewcolumn.

go run snippet.go addnewcolumn projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

Você verá:

Added 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 500000 na linha indexada por Albums(2, 2).

func update(ctx context.Context, w io.Writer, client *spanner.Client) error {
	cols := []string{"SingerId", "AlbumId", "MarketingBudget"}
	_, err := client.Apply(ctx, []*spanner.Mutation{
		spanner.Update("Albums", cols, []interface{}{1, 1, 100000}),
		spanner.Update("Albums", cols, []interface{}{2, 2, 500000}),
	})
	return err
}

Execute a amostra usando o argumento update.

go run snippet.go update projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

Também é possível executar uma consulta SQL ou uma chamada de leitura para coletar os valores que você acabou de gravar.

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

func queryNewColumn(ctx context.Context, w io.Writer, client *spanner.Client) error {
	stmt := spanner.Statement{SQL: `SELECT SingerId, AlbumId, MarketingBudget FROM Albums`}
	iter := client.Single().Query(ctx, stmt)
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			return nil
		}
		if err != nil {
			return err
		}
		var singerID, albumID int64
		var marketingBudget spanner.NullInt64
		if err := row.ColumnByName("SingerId", &singerID); err != nil {
			return err
		}
		if err := row.ColumnByName("AlbumId", &albumID); err != nil {
			return err
		}
		if err := row.ColumnByName("MarketingBudget", &marketingBudget); err != nil {
			return err
		}
		budget := "NULL"
		if marketingBudget.Valid {
			budget = strconv.FormatInt(marketingBudget.Int64, 10)
		}
		fmt.Fprintf(w, "%d %d %s\n", singerID, albumID, budget)
	}
}

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

go run snippet.go querynewcolumn projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

Você verá:

1 1 100000
1 2 NULL
2 1 NULL
2 2 500000
2 3 NULL

Atualizar dados

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

Use o método Update() para executar uma instrução DML.

func writeWithTransactionUsingDML(ctx context.Context, w io.Writer, client *spanner.Client) error {
	_, err := client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
		// getBudget returns the budget for a record with a given albumId and singerId.
		getBudget := func(albumID, singerID int64) (int64, error) {
			key := spanner.Key{albumID, singerID}
			row, err := txn.ReadRow(ctx, "Albums", key, []string{"MarketingBudget"})
			if err != nil {
				return 0, err
			}
			var budget int64
			if err := row.Column(0, &budget); err != nil {
				return 0, err
			}
			return budget, nil
		}
		// updateBudget updates the budget for a record with a given albumId and singerId.
		updateBudget := func(singerID, albumID, albumBudget int64) error {
			stmt := spanner.Statement{
				SQL: `UPDATE Albums
					SET MarketingBudget = @AlbumBudget
					WHERE SingerId = @SingerId and AlbumId = @AlbumId`,
				Params: map[string]interface{}{
					"SingerId":    singerID,
					"AlbumId":     albumID,
					"AlbumBudget": albumBudget,
				},
			}
			_, err := txn.Update(ctx, stmt)
			return err
		}

		// Transfer the marketing budget from one album to another. By keeping the actions
		// in a single transaction, it ensures the movement is atomic.
		const transferAmt = 200000
		var album1budget, album2budget int64
		var err error
		if album1budget, err = getBudget(1, 1); err != nil {
			return err
		}
		// The transaction will only be committed if this condition still holds at the time
		// of commit. Otherwise it will be aborted and the callable will be rerun by the
		// client library.
		if album1budget >= transferAmt {
			if album2budget, err = getBudget(2, 2); err != nil {
				return err
			}
			if err = updateBudget(1, 1, album1budget-transferAmt); err != nil {
				return err
			}
			if err = updateBudget(2, 2, album2budget+transferAmt); err != nil {
				return err
			}
			fmt.Fprintf(w, "Moved %d from Album1's MarketingBudget to Album2's.", transferAmt)
		}
		return nil
	})
	return err
}

Execute a amostra usando o argumento dmlwritetxn.

go run snippet.go dmlwritetxn projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

Você verá:

Moved 200000 from Album1's MarketingBudget to Album2's.

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

Você pode adicionar um índice na linha de comando usando a ferramenta de linha de comando gcloud ou programaticamente usando a biblioteca de cliente do Cloud Spanner para Go.

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.

Usando a biblioteca de cliente do Cloud Spanner para Go

Use UpdateDatabaseDdl() para adicionar um índice:

func addIndex(ctx context.Context, w io.Writer, adminClient *database.DatabaseAdminClient, database string) error {
	op, err := adminClient.UpdateDatabaseDdl(ctx, &adminpb.UpdateDatabaseDdlRequest{
		Database: database,
		Statements: []string{
			"CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)",
		},
	})
	if err != nil {
		return err
	}
	if err := op.Wait(ctx); err != nil {
		return err
	}
	fmt.Fprintf(w, "Added index\n")
	return nil
}

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

Added index

Consultar usando o índice

Você pode fazer 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

Usando a biblioteca de cliente do Cloud Spanner para Go

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

func queryUsingIndex(ctx context.Context, w io.Writer, client *spanner.Client) error {
	stmt := spanner.Statement{
		SQL: `SELECT AlbumId, AlbumTitle, MarketingBudget
			FROM Albums@{FORCE_INDEX=AlbumsByAlbumTitle}
			WHERE AlbumTitle >= @start_title AND AlbumTitle < @end_title`,
		Params: map[string]interface{}{
			"start_title": "Aardvark",
			"end_title":   "Goo",
		},
	}
	iter := client.Single().Query(ctx, stmt)
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			return err
		}
		var albumID int64
		var marketingBudget spanner.NullInt64
		var albumTitle string
		if err := row.ColumnByName("AlbumId", &albumID); err != nil {
			return err
		}
		if err := row.ColumnByName("AlbumTitle", &albumTitle); err != nil {
			return err
		}
		if err := row.ColumnByName("MarketingBudget", &marketingBudget); err != nil {
			return err
		}
		budget := "NULL"
		if marketingBudget.Valid {
			budget = strconv.FormatInt(marketingBudget.Int64, 10)
		}
		fmt.Fprintf(w, "%d %s %s\n", albumID, albumTitle, budget)
	}
	return nil
}

Execute a amostra usando o argumento queryindex.

go run snippet.go queryindex projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

Você verá uma resposta similar a:

2 Go, Go, Go NULL
2 Forever Hold Your Peace 300000

Para mais detalhes, consulte a referência de:

Ler usando o índice

Para fazer uma leitura com o índice, use ReadOnlyTransaction.ReadUsingIndex(), que é utilizado para ler zero ou mais linhas de um banco de dados com um índice.

O código a seguir busca todas as colunas AlbumId e AlbumTitle no índice AlbumsByAlbumTitle.

func readUsingIndex(ctx context.Context, w io.Writer, client *spanner.Client) error {
	iter := client.Single().ReadUsingIndex(ctx, "Albums", "AlbumsByAlbumTitle", spanner.AllKeys(),
		[]string{"AlbumId", "AlbumTitle"})
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			return nil
		}
		if err != nil {
			return err
		}
		var albumID int64
		var albumTitle string
		if err := row.Columns(&albumID, &albumTitle); err != nil {
			return err
		}
		fmt.Fprintf(w, "%d %s\n", albumID, albumTitle)
	}
}

Execute a amostra usando o argumento readindex.

go run snippet.go readindex projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

Você verá:

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

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 Go

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

func addStoringIndex(ctx context.Context, w io.Writer, adminClient *database.DatabaseAdminClient, database string) error {
	op, err := adminClient.UpdateDatabaseDdl(ctx, &adminpb.UpdateDatabaseDdlRequest{
		Database: database,
		Statements: []string{
			"CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget)",
		},
	})
	if err != nil {
		return err
	}
	if err := op.Wait(ctx); err != nil {
		return err
	}
	fmt.Fprintf(w, "Added storing index\n")
	return nil
}

Execute a amostra usando o argumento addstoringindex.

go run snippet.go addstoringindex projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

Pode levar alguns minutos para adicionar um índice. Depois da adição, você verá:

Added storing index

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

func readStoringIndex(ctx context.Context, w io.Writer, client *spanner.Client) error {
	iter := client.Single().ReadUsingIndex(ctx, "Albums", "AlbumsByAlbumTitle2", spanner.AllKeys(),
		[]string{"AlbumId", "AlbumTitle", "MarketingBudget"})
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			return nil
		}
		if err != nil {
			return err
		}
		var albumID int64
		var marketingBudget spanner.NullInt64
		var albumTitle string
		if err := row.Columns(&albumID, &albumTitle, &marketingBudget); err != nil {
			return err
		}
		budget := "NULL"
		if marketingBudget.Valid {
			budget = strconv.FormatInt(marketingBudget.Int64, 10)
		}
		fmt.Fprintf(w, "%d %s %s\n", albumID, albumTitle, budget)
	}
}

Execute a amostra usando o argumento readstoringindex.

go run snippet.go readstoringindex projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

Você verá uma resposta similar a:

2 Forever Hold Your Peace 300000
2 Go, Go, Go NULL
1 Green NULL
3 Terrified NULL
1 Total Junk 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 de transação. Portanto, seu aplicativo sempre recebe dados consistentes. Use o tipo ReadOnlyTransaction para executar transações somente leitura. Use Client.ReadOnlyTransaction() para conseguir um ReadOnlyTransaction.

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

func readOnlyTransaction(ctx context.Context, w io.Writer, client *spanner.Client) error {
	ro := client.ReadOnlyTransaction()
	defer ro.Close()
	stmt := spanner.Statement{SQL: `SELECT SingerId, AlbumId, AlbumTitle FROM Albums`}
	iter := ro.Query(ctx, stmt)
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			return err
		}
		var singerID int64
		var albumID int64
		var albumTitle string
		if err := row.Columns(&singerID, &albumID, &albumTitle); err != nil {
			return err
		}
		fmt.Fprintf(w, "%d %d %s\n", singerID, albumID, albumTitle)
	}

	iter = ro.Read(ctx, "Albums", spanner.AllKeys(), []string{"SingerId", "AlbumId", "AlbumTitle"})
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			return nil
		}
		if err != nil {
			return err
		}
		var singerID int64
		var albumID int64
		var albumTitle string
		if err := row.Columns(&singerID, &albumID, &albumTitle); err != nil {
			return err
		}
		fmt.Fprintf(w, "%d %d %s\n", singerID, albumID, albumTitle)
	}
}

Execute a amostra usando o argumento readonlytransaction.

go run snippet.go readonlytransaction projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

Você verá uma resposta similar a:

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

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