Comienza a usar Cloud Spanner en Go

Objetivos

En este instructivo, se explican los siguientes pasos a fin de usar la biblioteca cliente de Cloud Spanner para Go:

  • Crea una base de datos y una instancia de Cloud Spanner.
  • Escribe, lee y ejecuta consultas de SQL sobre datos en la base de datos.
  • Actualiza el esquema de la base de datos.
  • Actualiza los datos mediante una transacción de lectura y escritura.
  • Agrega un índice secundario a la base de datos.
  • Usa el índice para leer y ejecutar consultas de SQL sobre datos.
  • Recupera datos con una transacción de solo lectura.

Costos

En este instructivo, se usa Cloud Spanner, que es un componente facturable de Google Cloud. Para obtener información sobre el costo de usar Cloud Spanner, consulta Precios.

Antes de comenzar

Completa los pasos descritos en Configuración, que abarcan la creación y configuración de un proyecto de Google Cloud predeterminado, la facturación, la habilitación de la API de Cloud Spanner y la configuración de OAuth 2.0 para obtener credenciales de autenticación para usar la API de Cloud Spanner.

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

Prepara tu entorno local de Go

  1. Instala Go (descargar) en tu máquina de desarrollo si aún no lo hiciste.

  2. Configura la variable de entorno GOPATH si aún no está configurada, como se describe en la sección sobre probar la instalación.

  3. Descarga las muestras en tu máquina.

    go get -u github.com/GoogleCloudPlatform/golang-samples/spanner/...
    
  4. Ve al directorio que contiene el código de muestra de Cloud Spanner:

    cd $GOPATH/src/github.com/GoogleCloudPlatform/golang-samples/spanner/spanner_snippets
    
  5. Establece la variable de entorno GCLOUD_PROJECT en tu ID del proyecto de Google Cloud:

    export GCLOUD_PROJECT=[MY_PROJECT_ID]
    

Crea una instancia

Cuando usas Cloud Spanner por primera vez, debes crear una instancia, que es una asignación de recursos que usan las bases de datos de Cloud Spanner. Cuando creas una instancia, debes elegir una configuración de instancia, que determina dónde se almacenan tus datos y también la cantidad de nodos que se usarán, lo que, a su vez, determina la cantidad de recursos de entrega y almacenamiento de tu instancia.

Ejecuta el siguiente comando para crear una instancia de Cloud Spanner en la región us-central1 con 1 nodo:

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

Ten en cuenta que esto crea una instancia con las siguientes características:

  • ID de instancia: test-instance
  • Nombre visible: Test Instance
  • Configuración de la instancia: regional-us-central1 (la configuración regional almacena datos en una región, mientras que la multirregional distribuye datos en varias regiones; obtén más información en Instancias)
  • Recuento de nodos: 1 (node_count corresponde a la cantidad de recursos de procesamiento y almacenamiento disponibles para las bases de datos de la instancia; obtén más información en Recuento de nodos)

Deberías ver lo siguiente:

Creating instance...done.

Examina los archivos de muestra

El repositorio de muestras contiene una muestra que indica cómo usar Cloud Spanner con Go.

Consulta el archivo snippet.go, en el que se muestra cómo usar Cloud Spanner. El código es un instructivo sobre cómo crear y usar una base de datos nueva. Los datos utilizan el esquema de ejemplo de la página Esquema y modelo de datos.

Crea una base de datos

Para crear una base de datos llamada example-db en la instancia test-instance, ejecuta lo siguiente en la línea de comandos.

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

Deberías ver lo siguiente:

Created database [example-db]

Acabas de crear una base de datos de Cloud Spanner. El siguiente es el código que creó la base de datos:

import (
	"context"
	"fmt"
	"io"
	"regexp"

	database "cloud.google.com/go/spanner/admin/database/apiv1"
	adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)

func createDatabase(w io.Writer, db string) error {
	matches := regexp.MustCompile("^(.*)/databases/(.*)$").FindStringSubmatch(db)
	if matches == nil || len(matches) != 3 {
		return fmt.Errorf("Invalid database id %s", db)
	}

	ctx := context.Background()
	adminClient, err := database.NewDatabaseAdminClient(ctx)
	if err != nil {
		return err
	}
	defer adminClient.Close()

	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
}

El código también define dos tablas, Singers y Albums, para una aplicación básica de música. Ambas tablas se usan en toda la página. Si aún no lo has hecho, consulta el esquema de ejemplo.

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

Crea un cliente de base de datos

Para poder realizar operaciones de lectura o escritura, debes crear un Client:


import (
	"context"
	"io"

	"cloud.google.com/go/spanner"
	database "cloud.google.com/go/spanner/admin/database/apiv1"
)

func createClients(w io.Writer, db string) error {
	ctx := context.Background()

	adminClient, err := database.NewDatabaseAdminClient(ctx)
	if err != nil {
		return err
	}

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

	_ = adminClient
	_ = dataClient

	return nil
}

Un Client es como una conexión de base de datos: todas tus interacciones con Cloud Spanner deben pasar por un Client. Lo usual es que cuando se inicia la aplicación, se crea un Client y se vuelve a usar ese Client para leer, escribir y ejecutar transacciones. Cada cliente usa recursos en Cloud Spanner, por lo que debes llamar a Client.Close() para limpiar los recursos del cliente, incluidas las conexiones de red.

Obtén más información en la referencia de Client.

En el código anterior, también se muestra cómo crear un DatabaseAdminClient, que se usa para crear una base de datos.

Escribe datos con DML

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

Usa el método Update() para ejecutar una declaración DML.


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
)

func writeUsingDML(w io.Writer, db string) error {
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	_, 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
}

Ejecuta la muestra con el argumento dmlwrite:

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

Debería ver lo siguiente:

4 record(s) inserted.

Escribe datos con mutaciones

También puedes insertar datos mediante mutaciones.

Una Mutation es un contenedor de operaciones de mutación. Una Mutation representa una secuencia de inserciones, actualizaciones y eliminaciones que Cloud Spanner aplica de forma atómica a diferentes filas y tablas en una base de datos de Cloud Spanner.

Usa Mutation.InsertOrUpdate() para construir una mutación INSERT_OR_UPDATE, que agrega una fila nueva o actualiza los valores de la columna si la fila ya existe. También puedes usar el método Mutation.Insert() para construir una mutación INSERT, que agrega una fila nueva.

Client.Apply() aplica mutaciones de forma atómica a una base de datos.

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


import (
	"context"
	"io"

	"cloud.google.com/go/spanner"
)

func write(w io.Writer, db string) error {
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	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
}

Ejecuta la muestra con el argumento write:

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

Deberías ver que el comando se ejecuta con éxito.

Consulta datos mediante SQL

Cloud Spanner admite una interfaz nativa de SQL para leer datos, a la que puedes acceder desde la línea de comandos mediante la herramienta de línea de comandos de gcloud o de manera programática con la biblioteca cliente de Cloud Spanner para Go.

En la línea de comandos

Ejecuta la siguiente instrucción de 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 debería ser el 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

Usa la biblioteca cliente de Cloud Spanner para Go

Además de ejecutar una instrucción de SQL en la línea de comandos, puedes emitir la misma instrucción de SQL de manera programática con la biblioteca cliente de Cloud Spanner para Go.

Los siguientes métodos y tipos se usan para ejecutar la consulta de SQL:

  • Client.Single(): Usa esto para leer el valor de una o más columnas de una o más filas en una tabla de Cloud Spanner. Client.Single muestra una ReadOnlyTransaction, que se usa para ejecutar una operación de lectura o una instrucción de SQL.
  • ReadOnlyTransaction.Query(): Usa este método para ejecutar una consulta en una base de datos.
  • El tipo Statement: Úsalo para construir una string de SQL.
  • El tipo Row: Úsalo para acceder a los datos que muestra una instrucción de SQL o una llamada de lectura.

Aquí se muestra cómo enviar la consulta y acceder a los datos:


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
	"google.golang.org/api/iterator"
)

func query(w io.Writer, db string) error {
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

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

Ejecuta la muestra con el argumento query.

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

Deberías ver el siguiente resultado:

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

Realiza consultas con un parámetro de SQL

Puedes incluir valores personalizados en las instrucciones de SQL mediante parámetros. En este ejemplo, se usa @lastName como parámetro en la cláusula WHERE a fin de consultar los registros que contienen un valor específico de LastName.


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
	"google.golang.org/api/iterator"
)

func queryWithParameter(w io.Writer, db string) error {
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	stmt := spanner.Statement{
		SQL: `SELECT SingerId, FirstName, LastName FROM Singers
			WHERE LastName = @lastName`,
		Params: map[string]interface{}{
			"lastName": "Garcia",
		},
	}
	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 int64
		var firstName, lastName string
		if err := row.Columns(&singerID, &firstName, &lastName); err != nil {
			return err
		}
		fmt.Fprintf(w, "%d %s %s\n", singerID, firstName, lastName)
	}
}

Ejecuta la muestra con el argumento querywithparameter.

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

Deberías ver un resultado similar al siguiente:

12 Melissa Garcia

Lee datos mediante la API de lectura

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

Usa ReadOnlyTransaction.Read() para leer filas de la base de datos. Usa KeySet para definir el conjunto de claves y los rangos de claves que desees leer.

Aquí te indicamos cómo leer los datos:


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
	"google.golang.org/api/iterator"
)

func read(w io.Writer, db string) error {
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

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

Ejecuta la muestra con el argumento read.

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

Deberías ver un resultado similar a este:

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

Actualiza el esquema de la base de datos

Supongamos que necesitas agregar una columna nueva llamada MarketingBudget a la tabla Albums. Para agregar una columna nueva a una tabla existente, es necesario actualizar el esquema de la base de datos. Cloud Spanner admite actualizaciones del esquema de una base de datos mientras esta sigue entregando tráfico. Las actualizaciones de esquema no requieren que la base de datos esté sin conexión y no bloquean tablas ni columnas completas. Puedes continuar escribiendo datos en la base de datos durante la actualización del esquema. Obtén más información sobre las actualizaciones admitidas del esquema y el rendimiento de los cambios de esquema en la página sobre actualizaciones de esquema.

Agrega una columna

Puedes agregar una columna en la línea de comandos con la herramienta de línea de comandos de gcloud o de manera programática mediante la biblioteca cliente de Cloud Spanner para Go.

En la línea de comandos

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

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

Deberías ver lo siguiente:

Schema updating...done.

Usa la biblioteca cliente de Cloud Spanner para Go

Usa DatabaseAdminClient.UpdateDatabaseDdl() para modificar el esquema:


import (
	"context"
	"fmt"
	"io"

	database "cloud.google.com/go/spanner/admin/database/apiv1"
	adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)

func addNewColumn(w io.Writer, db string) error {
	ctx := context.Background()
	adminClient, err := database.NewDatabaseAdminClient(ctx)
	if err != nil {
		return err
	}
	defer adminClient.Close()

	op, err := adminClient.UpdateDatabaseDdl(ctx, &adminpb.UpdateDatabaseDdlRequest{
		Database: db,
		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
}

Ejecuta la muestra con el argumento addnewcolumn.

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

Deberías ver lo siguiente:

Added MarketingBudget column.

Escribe datos en la columna nueva

Con el siguiente código, se escriben datos en la columna nueva. Establece MarketingBudget en 100000 para la fila marcada por Albums(1, 1) y en 500000 para la fila marcada por Albums(2, 2).

import (
	"context"
	"io"

	"cloud.google.com/go/spanner"
)

func update(w io.Writer, db string) error {
	ctx := context.Background()

	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	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
}

Ejecuta la muestra con el argumento update.

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

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

Este es el código para ejecutar la consulta:


import (
	"context"
	"fmt"
	"io"
	"strconv"

	"cloud.google.com/go/spanner"
	"google.golang.org/api/iterator"
)

func queryNewColumn(w io.Writer, db string) error {
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	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 ejecutar esta consulta, ejecuta la muestra con el argumento querynewcolumn.

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

Deberías ver lo siguiente:

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

Actualiza datos

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

Usa el método Update() para ejecutar una declaración DML.


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
)

func writeWithTransactionUsingDML(w io.Writer, db string) error {
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	_, 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
		album2Budget, err := getBudget(2, 2)
		if 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 album2Budget >= transferAmt {
			album1Budget, err := getBudget(1, 1)
			if 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 Album2's MarketingBudget to Album1's.", transferAmt)
		}
		return nil
	})
	return err
}

Ejecuta la muestra con el argumento dmlwritetxn.

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

Deberías ver lo siguiente:

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

Usa un índice secundario

Supongamos que deseas recuperar todas las filas de Albums que tienen valores AlbumTitle en un rango determinado. Puedes leer todos los valores de la columna AlbumTitle con una instrucción de SQL o una llamada de lectura y, luego, descartar las filas que no cumplan con los criterios. Sin embargo, analizar tablas enteras es costoso, especialmente si tienen muchas filas. En su lugar, crea un índice secundario en la tabla para acelerar la recuperación de filas cuando realizas búsquedas por columnas sin claves primarias.

Para agregar un índice secundario a una tabla existente, es necesario actualizar el esquema. Al igual que otras actualizaciones de esquema, Cloud Spanner admite que se agregue un índice mientras la base de datos continúa entregando tráfico. Cloud Spanner reabastece de manera automática el índice con tus datos existentes. Los reabastecimientos pueden tardar unos minutos en completarse, pero no es necesario que uses la base de datos sin conexión ni que evites escribir en la tabla indexada durante este proceso. Para obtener más detalles, consulta la sección sobre el reabastecimiento de índices.

Después de agregar un índice secundario, Cloud Spanner lo usa de forma automática 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 deseas usar.

Agrega un índice secundario

Puedes agregar un índice en la línea de comandos con la herramienta de línea de comandos de gcloud o de manera programática mediante la biblioteca cliente de Cloud Spanner para Go.

En la línea de comandos

Usa el siguiente comando CREATE INDEX para agregar 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.

Usa la biblioteca cliente de Cloud Spanner para Go

Usa UpdateDatabaseDdl() para agregar un índice:


import (
	"context"
	"fmt"
	"io"

	database "cloud.google.com/go/spanner/admin/database/apiv1"
	adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)

func addIndex(w io.Writer, db string) error {
	ctx := context.Background()
	adminClient, err := database.NewDatabaseAdminClient(ctx)
	if err != nil {
		return err
	}
	defer adminClient.Close()

	op, err := adminClient.UpdateDatabaseDdl(ctx, &adminpb.UpdateDatabaseDdlRequest{
		Database: db,
		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
}

Agregar un índice puede tardar unos minutos. Después de agregar el índice, deberías ver lo siguiente:

Added index

Lee datos con el índice

Para las consultas de SQL, Cloud Spanner usa de manera automática un índice adecuado. En la interfaz de lectura, debes especificar el índice en tu solicitud.

Para usar el índice en la interfaz de lectura, usa ReadOnlyTransaction.ReadUsingIndex(), que lee cero o más filas de una base de datos mediante un índice.

Con el siguiente código, se recuperan todas las columnas AlbumId y AlbumTitle del índice AlbumsByAlbumTitle.


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
	"google.golang.org/api/iterator"
)

func readUsingIndex(w io.Writer, db string) error {
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

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

Ejecuta la muestra con el argumento readindex.

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

Deberías ver lo siguiente:

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

Agrega un índice con una cláusula STORING

Quizás notaste 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 Cloud Spanner no admite la posibilidad de unir 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

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

Agregar un índice puede tardar unos minutos. Después de agregar el índice, deberías ver lo siguiente:

Schema updating...done.

Usa la biblioteca cliente de Cloud Spanner para Go

Usa UpdateDatabaseDdl() para agregar un índice con una cláusula STORING:


import (
	"context"
	"fmt"
	"io"

	database "cloud.google.com/go/spanner/admin/database/apiv1"
	adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)

func addStoringIndex(w io.Writer, db string) error {
	ctx := context.Background()
	adminClient, err := database.NewDatabaseAdminClient(ctx)
	if err != nil {
		return err
	}
	defer adminClient.Close()

	op, err := adminClient.UpdateDatabaseDdl(ctx, &adminpb.UpdateDatabaseDdlRequest{
		Database: db,
		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
}

Ejecuta la muestra con el argumento addstoringindex.

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

Agregar un índice puede tardar unos minutos. Después de agregar el índice, deberías ver lo siguiente:

Added storing index

Ahora puedes ejecutar una operación de lectura que recupere todas las columnas AlbumId, AlbumTitle y MarketingBudget del índice AlbumsByAlbumTitle2:


import (
	"context"
	"fmt"
	"io"
	"strconv"

	"cloud.google.com/go/spanner"
	"google.golang.org/api/iterator"
)

func readStoringIndex(w io.Writer, db string) error {
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

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

Ejecuta la muestra con el argumento readstoringindex.

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

Deberías ver un resultado similar a este:

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

Recupera datos mediante transacciones de solo lectura

Supongamos que quieres ejecutar más de una lectura en la misma marca de tiempo. En las transacciones de solo lectura, se observa un prefijo coherente del historial de confirmaciones de transacciones, por lo que la aplicación siempre obtiene datos coherentes. Usa el tipo ReadOnlyTransaction para ejecutar transacciones de solo lectura. Usa Client.ReadOnlyTransaction() para obtener una ReadOnlyTransaction.

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


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
	"google.golang.org/api/iterator"
)

func readOnlyTransaction(w io.Writer, db string) error {
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

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

Ejecuta la muestra con el argumento readonlytransaction.

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

Deberías ver un resultado similar a este:

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

Realiza una limpieza

Para evitar que se apliquen cargos adicionales a tu cuenta de Google Cloud por los recursos que se usaron en este instructivo, descarta la base de datos y borra la instancia que creaste.

Borra la base de datos

Si borras una instancia, se borrarán de forma automática todas las bases de datos que contiene. En este paso, se muestra cómo borrar una base de datos sin borrar una instancia (ten en cuenta que se seguirán generando cargos por la instancia).

En la línea de comandos

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

Usa Cloud Console.

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

    Ir a la página Instancias

  2. Haz clic en la instancia.

  3. Haz clic en la base de datos que deseas borrar.

  4. En la página Detalles de la base de datos, haz clic en Borrar.

  5. Confirma que deseas borrar la base de datos y haz clic en Borrar.

Borra la instancia

Si borras una instancia, se descartarán de forma automática todas las bases de datos creadas en ella.

En la línea de comandos

gcloud spanner instances delete test-instance

Usa Cloud Console.

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

    Ir a la página Instancias

  2. Haz clic en tu instancia.

  3. Haz clic en Borrar.

  4. Confirma que deseas borrar la instancia y haz clic en Borrar.

Próximos pasos

  • Accede a Cloud Spanner en una instancia de máquina virtual: crea una instancia de máquina virtual con acceso a tu base de datos de Cloud Spanner.
  • Obtén más información sobre las credenciales de autorización y autenticación en Cómo comenzar a usar la autenticación.
  • Obtén más información sobre los conceptos de Cloud Spanner.