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

  1. Completa los pasos descritos en la página sobre configuración, que abarcan crear y configurar un proyecto de Google Cloud predeterminado, habilitar la facturación, habilitar la API de Cloud Spanner y configurar OAuth 2.0 para obtener credenciales de autenticación a fin de 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.

    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 almacenarán tus datos y cuántos nodos se usarán. Esto definirá la cantidad de recursos de almacenamiento y entrega en 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 creará una instancia con las siguientes características:

  • ID de instancia test-instance
  • Nombre visible Test Instance
  • Configuración de la instancia regional-us-central1 (las opciones de configuración regionales almacenan datos en una región, mientras que las opciones de configuración multirregionales distribuyen datos en varias regiones. Consulta Instancias para obtener más información).
  • Recuento de nodos de 1 (node_count corresponde a la cantidad de recursos de almacenamiento y entrega disponibles para las bases de datos en la instancia. Consulta Recuento de nodos para obtener más información).

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.

Observa el archivo snippet.go, en el que se muestra cómo usar Cloud Spanner. En el código, se muestra cómo crear y usar una base de datos nueva. Los datos usan el esquema de ejemplo que se muestra en 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. A continuación, se incluye el código que creó la base de datos.


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
}

El código también define dos tablas, Singers y Albums, para una aplicación de música básica. Estas tablas se usan en toda esta página. Observa el esquema de ejemplo si aún no lo hiciste.

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:


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
}

Un Client es como una conexión de base de datos: todas tus interacciones con Cloud Spanner deben pasar por un . 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.

Debes usar el método Update() para ejecutar una declaración 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
}

Ejecuta la muestra con el argumento dmlwrite:

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

Deberías 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:


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
}

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.

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

A continuación, se muestra cómo enviar la consulta y acceder a los datos:


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

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 una consulta con un parámetro de SQL

Puedes incluir valores personalizados en las instrucciones de SQL mediante parámetros. Este es un ejemplo del uso de @lastName como parámetro en la cláusula WHERE a fin de consultar registros que contienen un valor específico para LastName.


func queryWithParameter(ctx context.Context, w io.Writer, client *spanner.Client) error {
	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.

A continuación, te indicamos cómo leer los datos:


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

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 al siguiente:

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 permite realizar actualizaciones de esquema en 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 de esquema compatibles 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:


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
}

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.

Escribir 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).


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
}

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 con el que se ejecuta la 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 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 el DML en una transacción de lectura y escritura.

Debes usar el método Update() para ejecutar una declaración 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
		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, realizar un análisis completo de una tabla es costoso, en especial en el caso de las tablas con muchas filas. En su lugar, puedes acelerar la recuperación de filas si realizas búsquedas por columnas de clave no primaria mediante la creación de un índice secundario en la tabla.

Para agregar un índice secundario a una tabla existente, es necesario actualizar el esquema. Al igual que otras actualizaciones de esquema, Cloud Spanner admite el agregado de un índice mientras la base de datos continúa entregando tráfico. Cloud Spanner reabastece el índice de forma automática 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 o que evites escribir en la tabla indexada durante este proceso. Para obtener más detalles, consulta la página sobre el reabastecimiento de índices.

Después de agregar un índice secundario, Cloud Spanner lo usa de forma automática para las consultas de SQL que pueden ejecutarse 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:


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
}

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

Added index

Lee con el índice

Para las consultas de SQL, Cloud Spanner usa un índice adecuado de forma automática. 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.


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

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 capacidad 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:


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
}

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:


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

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 al siguiente:

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:


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

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 al siguiente:

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

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, todas las bases de datos que contiene se borran de manera automática. En este paso, se muestra cómo borrar una base de datos sin borrar una instancia (de todos modos, se generarían 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 descartan de manera automática todas las bases de datos creadas en esa instancia.

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 la instancia.

  3. Haz clic en Borrar.

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

Próximos pasos

  • Usar 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 Comenzar a usar la autenticación.
  • Obtén más información sobre los conceptos de Cloud Spanner.