Índices secundarios

En una base de datos de Spanner, Spanner crea automáticamente un índice para la clave principal de cada tabla. Por ejemplo, no tienes que hacer nada para indexar la clave principal de Singers, ya que se indexa automáticamente.

También puedes crear índices secundarios para otras columnas. Si añades un índice secundario a una columna, será más eficiente buscar datos en esa columna. Por ejemplo, si necesitas buscar rápidamente un álbum por título, debes crear un índice secundario en AlbumTitle para que Spanner no tenga que analizar toda la tabla.

Si la búsqueda del ejemplo anterior se realiza en una transacción de lectura y escritura, la búsqueda más eficiente también evita mantener bloqueos en toda la tabla, lo que permite insertar y actualizar simultáneamente filas de la tabla que no estén en el AlbumTitle intervalo de búsqueda.

Además de las ventajas que aportan a las búsquedas, los índices secundarios también pueden ayudar a Spanner a ejecutar búsquedas de forma más eficiente, lo que permite realizar búsquedas de índice en lugar de búsquedas de tabla completas.

Spanner almacena los siguientes datos en cada índice secundario:

Con el tiempo, Spanner analiza tus tablas para asegurarse de que tus índices secundarios se utilizan en las consultas adecuadas.

Añadir un índice secundario

El momento más eficiente para añadir un índice secundario es cuando creas la tabla. Para crear una tabla y sus índices al mismo tiempo, envía las instrucciones DDL de la nueva tabla y los nuevos índices en una sola solicitud a Spanner.

En Spanner, también puedes añadir un nuevo índice secundario a una tabla mientras la base de datos sigue sirviendo tráfico. Al igual que con cualquier otro cambio de esquema en Spanner, añadir un índice a una base de datos ya creada no requiere que la base de datos esté sin conexión y no bloquea columnas ni tablas completas.

Cada vez que se añade un nuevo índice a una tabla, Spanner rellena automáticamente el índice para reflejar una vista actualizada de los datos que se están indexando. Spanner gestiona este proceso de relleno para ti y se ejecuta en segundo plano con recursos de nodo de baja prioridad. La velocidad de relleno de índices se adapta a los cambios en los recursos de los nodos durante la creación de índices, y el relleno no afecta significativamente al rendimiento de la base de datos.

La creación de índices puede tardar desde varios minutos hasta muchas horas. Como la creación de índices es una actualización de esquema, está sujeta a las mismas restricciones de rendimiento que cualquier otra actualización de esquema. El tiempo necesario para crear un índice secundario depende de varios factores:

  • El tamaño del conjunto de datos
  • La capacidad de computación de la instancia
  • La carga de la instancia

Para ver el progreso de un proceso de relleno de índice, consulta la sección de progreso.

Ten en cuenta que usar la columna Marca de tiempo de la confirmación como primera parte del índice secundario puede crear puntos de acceso y reducir el rendimiento de escritura.

Usa la instrucción CREATE INDEX para definir un índice secundario en tu esquema. A continuación, se incluyen algunos ejemplos:

Para indexar todos los Singers de la base de datos por su nombre y apellidos, haz lo siguiente:

GoogleSQL

CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName);

PostgreSQL

CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName);

Para crear un índice de todos los Songs de la base de datos por el valor de SongName, sigue estos pasos:

GoogleSQL

CREATE INDEX SongsBySongName ON Songs(SongName);

PostgreSQL

CREATE INDEX SongsBySongName ON Songs(SongName);

Para indexar solo las canciones de un cantante concreto, usa la cláusula INTERLEAVE IN para intercalar el índice en la tabla Singers:

GoogleSQL

CREATE INDEX SongsBySingerSongName ON Songs(SingerId, SongName),
    INTERLEAVE IN Singers;

PostgreSQL

CREATE INDEX SongsBySingerSongName ON Songs(SingerId, SongName)
    INTERLEAVE IN Singers;

Para indexar solo las canciones de un álbum concreto, sigue estos pasos:

GoogleSQL

CREATE INDEX SongsBySingerAlbumSongName ON Songs(SingerId, AlbumId, SongName),
    INTERLEAVE IN Albums;

PostgreSQL

CREATE INDEX SongsBySingerAlbumSongName ON Songs(SingerId, AlbumId, SongName)
    INTERLEAVE IN Albums;

Para indexar por orden descendente de SongName, haz lo siguiente:

GoogleSQL

CREATE INDEX SongsBySingerAlbumSongNameDesc ON Songs(SingerId, AlbumId, SongName DESC),
    INTERLEAVE IN Albums;

PostgreSQL

CREATE INDEX SongsBySingerAlbumSongNameDesc ON Songs(SingerId, AlbumId, SongName DESC)
    INTERLEAVE IN Albums;

Ten en cuenta que la anotación DESC anterior solo se aplica a SongName. Para indexar por orden descendente de otras claves de índice, anótalas también con DESC: SingerId DESC, AlbumId DESC.

También debes tener en cuenta que PRIMARY_KEY es una palabra reservada y no se puede usar como nombre de un índice. Es el nombre que se le da al pseudoíndice que se crea cuando se crea una tabla con la especificación PRIMARY KEY.

Para obtener más información y consultar las prácticas recomendadas para elegir entre índices no intercalados e índices intercalados, consulta Opciones de índice y Usar un índice intercalado en una columna cuyo valor aumente o disminuya de forma monótona.

Índices e intercalación

Los índices de Spanner se pueden intercalar con otras tablas para colocar las filas de índice junto a las de otra tabla. Al igual que en el intercalado de tablas de Spanner, las columnas de clave principal del elemento superior del índice deben ser un prefijo de las columnas indexadas, con el mismo tipo y orden de clasificación. A diferencia de las tablas intercaladas, no es necesario que los nombres de las columnas coincidan. Cada fila de un índice intercalado se almacena físicamente junto con la fila principal asociada.

Por ejemplo, supongamos que se da el siguiente esquema:

CREATE TABLE Singers (
  SingerId   INT64 NOT NULL,
  FirstName  STRING(1024),
  LastName   STRING(1024),
  SingerInfo PROTO<Singer>(MAX)
) PRIMARY KEY (SingerId);

CREATE TABLE Albums (
  SingerId     INT64 NOT NULL,
  AlbumId      INT64 NOT NULL,
  AlbumTitle   STRING(MAX),
  PublisherId  INT64 NOT NULL
) PRIMARY KEY (SingerId, AlbumId),
  INTERLEAVE IN PARENT Singers ON DELETE CASCADE;

CREATE TABLE Songs (
  SingerId     INT64 NOT NULL,
  AlbumId      INT64 NOT NULL,
  TrackId      INT64 NOT NULL,
  PublisherId  INT64 NOT NULL,
  SongName     STRING(MAX)
) PRIMARY KEY (SingerId, AlbumId, TrackId),
  INTERLEAVE IN PARENT Albums ON DELETE CASCADE;

CREATE TABLE Publishers (
  Id            INT64 NOT NULL,
  PublisherName STRING(MAX)
) PRIMARY KEY (Id);

Para indexar todos los Singers de la base de datos por su nombre y apellidos, debes crear un índice. A continuación, te indicamos cómo definir el índice SingersByFirstLastName:

CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName);

Si quieres crear un índice de Songs en (SingerId, AlbumId, SongName), puedes hacer lo siguiente:

CREATE INDEX SongsBySingerAlbumSongName
    ON Songs(SingerId, AlbumId, SongName);

También puedes crear un índice intercalado con un ancestro de Songs, como el siguiente:

CREATE INDEX SongsBySingerAlbumSongName
    ON Songs(SingerId, AlbumId, SongName),
    INTERLEAVE IN Albums;

Además, también puedes crear un índice de Songs en (PublisherId, SingerId, AlbumId, SongName) que esté intercalado con una tabla que no sea un elemento antecesor de Songs, como Publishers. Ten en cuenta que la clave principal de la tabla Publishers (id) no es un prefijo de las columnas indexadas del siguiente ejemplo. Esto sigue estando permitido porque Publishers.Id y Songs.PublisherId comparten el mismo tipo, orden y nulidad.

CREATE INDEX SongsByPublisherSingerAlbumSongName
    ON Songs(PublisherId, SingerId, AlbumId, SongName),
    INTERLEAVE IN Publishers;

Comprobar el progreso del relleno de índice

Consola

  1. En el menú de navegación de Spanner, haga clic en la pestaña Operaciones. En la página Operaciones se muestra una lista de las operaciones en curso.

  2. Busca la operación de relleno en la lista. Si sigue en curso, el indicador de progreso de la columna Hora de finalización muestra el porcentaje de la operación que se ha completado, como se muestra en la siguiente imagen:

    Captura de pantalla del indicador de progreso que muestra un 98%

gcloud

Usa gcloud spanner operations describe para comprobar el progreso de una operación.

  1. Obtén el ID de la operación:

    gcloud spanner operations list --instance=INSTANCE-NAME \
    --database=DATABASE-NAME --type=DATABASE_UPDATE_DDL

    Haz los cambios siguientes:

    • INSTANCE-NAME con el nombre de la instancia de Spanner.
    • DATABASE-NAME con el nombre de la base de datos.

    Notas sobre el uso:

    • Para limitar la lista, especifica la marca --filter. Por ejemplo:

      • --filter="metadata.name:example-db" solo muestra las operaciones de una base de datos específica.
      • --filter="error:*" solo muestra las operaciones de copia de seguridad que no se han realizado.

      Para obtener información sobre la sintaxis de los filtros, consulta Filtros de temas de gcloud. Para obtener información sobre cómo filtrar operaciones de copia de seguridad, consulta el campo filter en ListBackupOperationsRequest.

    • La marca --type no distingue entre mayúsculas y minúsculas.

    El resultado es similar al siguiente:

    OPERATION_ID     STATEMENTS                                                                                          DONE   @TYPE
    _auto_op_123456  CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName)                                 False  UpdateDatabaseDdlMetadata
                    CREATE INDEX SongsBySingerAlbumSongName ON Songs(SingerId, AlbumId, SongName), INTERLEAVE IN Albums
    _auto_op_234567                                                                                                      True   CreateDatabaseMetadata
    
  2. Ejecuta gcloud spanner operations describe:

    gcloud spanner operations describe \
    --instance=INSTANCE-NAME \
    --database=DATABASE-NAME \
    projects/PROJECT-NAME/instances/INSTANCE-NAME/databases/DATABASE-NAME/operations/OPERATION_ID

    Haz los cambios siguientes:

    • INSTANCE-NAME: nombre de la instancia de Spanner.
    • DATABASE-NAME: nombre de la base de datos de Spanner.
    • PROJECT-NAME: nombre del proyecto.
    • OPERATION-ID: el ID de la operación que quieres comprobar.

    La sección progress de la salida muestra el porcentaje de la operación que se ha completado. La salida tiene un aspecto similar al siguiente:

    done: true
    ...
      progress:
      - endTime: '2021-01-22T21:58:42.912540Z'
        progressPercent: 100
        startTime: '2021-01-22T21:58:11.053996Z'
      - progressPercent: 67
        startTime: '2021-01-22T21:58:11.053996Z'
    ...
    

REST v1

Obtén el ID de la operación:

  gcloud spanner operations list --instance=INSTANCE-NAME 
--database=DATABASE-NAME --type=DATABASE_UPDATE_DDL

Haz los cambios siguientes:

  • INSTANCE-NAME con el nombre de la instancia de Spanner.
  • DATABASE-NAME con el nombre de la base de datos.

Antes de usar los datos de la solicitud, haz las siguientes sustituciones:

  • PROJECT-ID: el ID del proyecto.
  • INSTANCE-ID: el ID de instancia.
  • DATABASE-ID: el ID de la base de datos.
  • OPERATION-ID: el ID de la operación.

Método HTTP y URL:

GET https://spanner.googleapis.com/v1/projects/PROJECT-ID/instances/INSTANCE-ID/databases/DATABASE-ID/operations/OPERATION-ID

Para enviar tu solicitud, despliega una de estas opciones:

Deberías recibir una respuesta JSON similar a la siguiente:

{
...
    "progress": [
      {
        "progressPercent": 100,
        "startTime": "2023-05-27T00:52:27.366688Z",
        "endTime": "2023-05-27T00:52:30.184845Z"
      },
      {
        "progressPercent": 100,
        "startTime": "2023-05-27T00:52:30.184845Z",
        "endTime": "2023-05-27T00:52:40.750959Z"
      }
    ],
...
  "done": true,
  "response": {
    "@type": "type.googleapis.com/google.protobuf.Empty"
  }
}

En gcloud y REST, puedes consultar el progreso de cada instrucción de relleno de índice en la sección progress. Por cada instrucción de la matriz de instrucciones, hay un campo correspondiente en la matriz de progreso. El orden de esta matriz de progreso se corresponde con el de la matriz de instrucciones. Cuando estén disponibles, los campos startTime, progressPercent y endTime se rellenarán en consecuencia. Ten en cuenta que el resultado no muestra una hora estimada de cuándo se completará el proceso de relleno.

Si la operación tarda demasiado, puedes cancelarla. Para obtener más información, consulta Cancelar la creación de índices.

Situaciones al ver el progreso del relleno de índice

Hay diferentes situaciones que pueden darse al intentar comprobar el progreso de un relleno de índice. Las instrucciones de creación de índices que requieren un relleno de índice forman parte de las operaciones de actualización de esquemas, y puede haber varias instrucciones que formen parte de una operación de actualización de esquemas.

El primer caso es el más sencillo, que se da cuando la instrucción de creación de índice es la primera instrucción de la operación de actualización del esquema. Como la instrucción de creación de índices es la primera, es la primera que se procesa y se ejecuta debido al orden de ejecución. Inmediatamente, el campo startTime de la instrucción de creación del índice se rellenará con la hora de inicio de la operación de actualización del esquema. A continuación, el campo progressPercent de la instrucción de creación del índice se rellena cuando el progreso del relleno inicial del índice supera el 0%. Por último, el campo endTime se rellena una vez que se confirma la instrucción.

El segundo caso se da cuando la instrucción de creación de índice no es la primera instrucción de la operación de actualización del esquema. Ningún campo relacionado con la instrucción de creación de índices se rellenará hasta que se hayan confirmado las instrucciones anteriores debido al orden de ejecución. Al igual que en el caso anterior, una vez que se confirman las instrucciones anteriores, primero se rellena el campo startTime de la instrucción de creación de índice y, después, el campo progressPercent. Por último, el campo endTime se rellena cuando se completa la confirmación de la declaración.

Cancelar la creación del índice

Puedes usar la CLI de Google Cloud para cancelar la creación de índices. Para obtener una lista de operaciones de actualización de esquema de una base de datos de Spanner, usa el comando gcloud spanner operations list e incluye la opción --filter:

gcloud spanner operations list \
    --instance=INSTANCE \
    --database=DATABASE \
    --filter="@TYPE:UpdateDatabaseDdlMetadata"

Busca el OPERATION_ID de la operación que quieras cancelar y, a continuación, usa el comando gcloud spanner operations cancel para cancelarla:

gcloud spanner operations cancel OPERATION_ID \
    --instance=INSTANCE \
    --database=DATABASE

Ver los índices

Para ver información sobre los índices de una base de datos, puedes usar laGoogle Cloud consola o la interfaz de línea de comandos de Google Cloud:

Consola

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

    Ir a la página Instancias

  2. Haga clic en el nombre de la instancia que quiera ver.

  3. En el panel de la izquierda, haz clic en la base de datos que quieras ver y, a continuación, en la tabla que quieras ver.

  4. Haz clic en la pestaña Índices. La consola Google Cloud muestra una lista de índices.

  5. Opcional: Para obtener detalles sobre un índice, como las columnas que incluye, haz clic en su nombre.

gcloud

Usa el comando gcloud spanner databases ddl describe:

    gcloud spanner databases ddl describe DATABASE \
        --instance=INSTANCE

La CLI de gcloud imprime las instrucciones del lenguaje de definición de datos (DDL) para crear las tablas y los índices de la base de datos. Las instrucciones CREATE INDEX describen los índices que ya existen. Por ejemplo:

    --- |-
  CREATE TABLE Singers (
    SingerId INT64 NOT NULL,
    FirstName STRING(1024),
    LastName STRING(1024),
    SingerInfo BYTES(MAX),
  ) PRIMARY KEY(SingerId)
---
  CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName)

Consultar con un índice específico

En las siguientes secciones se explica cómo especificar un índice en una instrucción SQL y con la interfaz de lectura de Spanner. En los ejemplos de estas secciones se da por hecho que ha añadido una columna MarketingBudget a la tabla Albums y que ha creado un índice llamado AlbumsByAlbumTitle:

GoogleSQL

CREATE TABLE Albums (
  SingerId         INT64 NOT NULL,
  AlbumId          INT64 NOT NULL,
  AlbumTitle       STRING(MAX),
  MarketingBudget  INT64,
) PRIMARY KEY (SingerId, AlbumId),
  INTERLEAVE IN PARENT Singers ON DELETE CASCADE;

CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle);

PostgreSQL

CREATE TABLE Albums (
  SingerId         BIGINT NOT NULL,
  AlbumId          BIGINT NOT NULL,
  AlbumTitle       VARCHAR,
  MarketingBudget  BIGINT,
  PRIMARY KEY (SingerId, AlbumId)
) INTERLEAVE IN PARENT Singers ON DELETE CASCADE;

CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle);

Especificar un índice en una instrucción SQL

Cuando usas SQL para consultar una tabla de Spanner, Spanner usa automáticamente los índices que probablemente hagan que la consulta sea más eficiente. Por lo tanto, no es necesario especificar un índice para las consultas SQL. Sin embargo, para las consultas que sean críticas para tu carga de trabajo, Google te recomienda que uses directivas FORCE_INDEX en tus instrucciones SQL para obtener un rendimiento más constante.

En algunos casos, Spanner puede elegir un índice que provoque un aumento de la latencia de las consultas. Si has seguido los pasos para solucionar problemas de regresiones de rendimiento y has confirmado que es conveniente probar con otro índice para la consulta, puedes especificar el índice como parte de la consulta.

Para especificar un índice en una instrucción SQL, usa la sugerencia FORCE_INDEX para proporcionar una directiva de índice. Las directivas de índice usan la siguiente sintaxis:

GoogleSQL

FROM MyTable@{FORCE_INDEX=MyTableIndex}

PostgreSQL

FROM MyTable /*@ FORCE_INDEX = MyTableIndex */

También puedes usar una directiva de índice para indicar a Spanner que analice la tabla base en lugar de usar un índice:

GoogleSQL

FROM MyTable@{FORCE_INDEX=_BASE_TABLE}

PostgreSQL

FROM MyTable /*@ FORCE_INDEX = _BASE_TABLE */

Puedes usar una directiva de índice para indicar a Spanner que analice un índice de una tabla con esquemas con nombre:

GoogleSQL

FROM MyNamedSchema.MyTable@{FORCE_INDEX="MyNamedSchema.MyTableIndex"}

PostgreSQL

FROM MyTable /*@ FORCE_INDEX = MyTableIndex */

En el siguiente ejemplo se muestra una consulta SQL que especifica un índice:

GoogleSQL

SELECT AlbumId, AlbumTitle, MarketingBudget
    FROM Albums@{FORCE_INDEX=AlbumsByAlbumTitle}
    WHERE AlbumTitle >= "Aardvark" AND AlbumTitle < "Goo";

PostgreSQL

SELECT AlbumId, AlbumTitle, MarketingBudget
    FROM Albums /*@ FORCE_INDEX = AlbumsByAlbumTitle */
    WHERE AlbumTitle >= 'Aardvark' AND AlbumTitle < 'Goo';

Una directiva de índice puede obligar al procesador de consultas de Spanner a leer columnas adicionales que la consulta necesita, pero que no están almacenadas en el índice. El procesador de consultas obtiene estas columnas combinando el índice y la tabla base. Para evitar esta unión adicional, usa una cláusula STORING (bases de datos con dialecto de GoogleSQL) o INCLUDE (bases de datos con dialecto de PostgreSQL) para almacenar las columnas adicionales en el índice.

En el ejemplo anterior, la columna MarketingBudget no se almacena en el índice, pero la consulta de SQL selecciona esta columna. Por lo tanto, Spanner debe buscar la columna MarketingBudget en la tabla base y, a continuación, combinarla con los datos del índice para devolver los resultados de la consulta.

Spanner genera un error si la directiva de índice tiene alguno de los siguientes problemas:

En los siguientes ejemplos se muestra cómo escribir y ejecutar consultas que obtengan los valores de AlbumId, AlbumTitle y MarketingBudget mediante el índice AlbumsByAlbumTitle:

C++

void QueryUsingIndex(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;

  spanner::SqlStatement select(
      "SELECT AlbumId, AlbumTitle, MarketingBudget"
      " FROM Albums@{FORCE_INDEX=AlbumsByAlbumTitle}"
      " WHERE AlbumTitle >= @start_title AND AlbumTitle < @end_title",
      {{"start_title", spanner::Value("Aardvark")},
       {"end_title", spanner::Value("Goo")}});
  using RowType =
      std::tuple<std::int64_t, std::string, absl::optional<std::int64_t>>;
  auto rows = client.ExecuteQuery(std::move(select));
  for (auto& row : spanner::StreamOf<RowType>(rows)) {
    if (!row) throw std::move(row).status();
    std::cout << "AlbumId: " << std::get<0>(*row) << "\t";
    std::cout << "AlbumTitle: " << std::get<1>(*row) << "\t";
    auto marketing_budget = std::get<2>(*row);
    if (marketing_budget) {
      std::cout << "MarketingBudget: " << *marketing_budget << "\n";
    } else {
      std::cout << "MarketingBudget: NULL\n";
    }
  }
  std::cout << "Read completed for [spanner_query_data_with_index]\n";
}

C#


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

public class QueryDataWithIndexAsyncSample
{
    public class Album
    {
        public int AlbumId { get; set; }
        public string AlbumTitle { get; set; }
        public long MarketingBudget { get; set; }
    }

    public async Task<List<Album>> QueryDataWithIndexAsync(string projectId, string instanceId, string databaseId,
        string startTitle, string endTitle)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";
        using var connection = new SpannerConnection(connectionString);
        using var cmd = connection.CreateSelectCommand(
            "SELECT AlbumId, AlbumTitle, MarketingBudget FROM Albums@ "
            + "{FORCE_INDEX=AlbumsByAlbumTitle} "
            + $"WHERE AlbumTitle >= @startTitle "
            + $"AND AlbumTitle < @endTitle",
            new SpannerParameterCollection
            {
                { "startTitle", SpannerDbType.String, startTitle },
                { "endTitle", SpannerDbType.String, endTitle }
            });

        var albums = new List<Album>();
        using var reader = await cmd.ExecuteReaderAsync();
        while (await reader.ReadAsync())
        {
            albums.Add(new Album
            {
                AlbumId = reader.GetFieldValue<int>("AlbumId"),
                AlbumTitle = reader.GetFieldValue<string>("AlbumTitle"),
                MarketingBudget = reader.IsDBNull(reader.GetOrdinal("MarketingBudget")) ? 0 : reader.GetFieldValue<long>("MarketingBudget")
            });
        }
        return albums;
    }
}

Go


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

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

func queryUsingIndex(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 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
}

Java

static void queryUsingIndex(DatabaseClient dbClient) {
  Statement statement =
      Statement
          // We use FORCE_INDEX hint to specify which index to use. For more details see
          // https://cloud.google.com/spanner/docs/query-syntax#from-clause
          .newBuilder(
              "SELECT AlbumId, AlbumTitle, MarketingBudget "
                  + "FROM Albums@{FORCE_INDEX=AlbumsByAlbumTitle} "
                  + "WHERE AlbumTitle >= @StartTitle AND AlbumTitle < @EndTitle")
          // We use @BoundParameters to help speed up frequently executed queries.
          //  For more details see https://cloud.google.com/spanner/docs/sql-best-practices
          .bind("StartTitle")
          .to("Aardvark")
          .bind("EndTitle")
          .to("Goo")
          .build();
  try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
    while (resultSet.next()) {
      System.out.printf(
          "%d %s %s\n",
          resultSet.getLong("AlbumId"),
          resultSet.getString("AlbumTitle"),
          resultSet.isNull("MarketingBudget") ? "NULL" : resultSet.getLong("MarketingBudget"));
    }
  }
}

Node.js

/**
 * TODO(developer): Uncomment these variables before running the sample.
 */
// const instanceId = 'my-instance';
// const databaseId = 'my-database';
// const projectId = 'my-project-id';
// const startTitle = 'Ardvark';
// const endTitle = 'Goo';

// Imports the Google Cloud Spanner client library
const {Spanner} = require('@google-cloud/spanner');

// Instantiates a client
const spanner = new Spanner({
  projectId: projectId,
});

async function queryDataWithIndex() {
  // Gets a reference to a Cloud Spanner instance and database
  const instance = spanner.instance(instanceId);
  const database = instance.database(databaseId);

  const query = {
    sql: `SELECT AlbumId, AlbumTitle, MarketingBudget
                FROM Albums@{FORCE_INDEX=AlbumsByAlbumTitle}
                WHERE AlbumTitle >= @startTitle AND AlbumTitle <= @endTitle`,
    params: {
      startTitle: startTitle,
      endTitle: endTitle,
    },
  };

  // Queries rows from the Albums table
  try {
    const [rows] = await database.run(query);

    rows.forEach(row => {
      const json = row.toJSON();
      const marketingBudget = json.MarketingBudget
        ? json.MarketingBudget
        : null; // This value is nullable
      console.log(
        `AlbumId: ${json.AlbumId}, AlbumTitle: ${json.AlbumTitle}, MarketingBudget: ${marketingBudget}`,
      );
    });
  } catch (err) {
    console.error('ERROR:', err);
  } finally {
    // Close the database when finished.
    database.close();
  }
}
queryDataWithIndex();

PHP

use Google\Cloud\Spanner\SpannerClient;

/**
 * Queries sample data from the database using SQL and an index.
 *
 * The index must exist before running this sample. You can add the index
 * by running the `add_index` sample or by running this DDL statement against
 * your database:
 *
 *     CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)
 *
 * Example:
 * ```
 * query_data_with_index($instanceId, $databaseId);
 * ```
 *
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 * @param string $startTitle The start of the title index.
 * @param string $endTitle   The end of the title index.
 */
function query_data_with_index(
    string $instanceId,
    string $databaseId,
    string $startTitle = 'Aardvark',
    string $endTitle = 'Goo'
): void {
    $spanner = new SpannerClient();
    $instance = $spanner->instance($instanceId);
    $database = $instance->database($databaseId);

    $parameters = [
        'startTitle' => $startTitle,
        'endTitle' => $endTitle
    ];

    $results = $database->execute(
        'SELECT AlbumId, AlbumTitle, MarketingBudget ' .
        'FROM Albums@{FORCE_INDEX=AlbumsByAlbumTitle} ' .
        'WHERE AlbumTitle >= @startTitle AND AlbumTitle < @endTitle',
        ['parameters' => $parameters]
    );

    foreach ($results as $row) {
        printf('AlbumId: %s, AlbumTitle: %s, MarketingBudget: %d' . PHP_EOL,
            $row['AlbumId'], $row['AlbumTitle'], $row['MarketingBudget']);
    }
}

Python

def query_data_with_index(
    instance_id, database_id, start_title="Aardvark", end_title="Goo"
):
    """Queries sample data from the database using SQL and an index.

    The index must exist before running this sample. You can add the index
    by running the `add_index` sample or by running this DDL statement against
    your database:

        CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)

    This sample also uses the `MarketingBudget` column. You can add the column
    by running the `add_column` sample or by running this DDL statement against
    your database:

        ALTER TABLE Albums ADD COLUMN MarketingBudget INT64

    """
    spanner_client = spanner.Client()
    instance = spanner_client.instance(instance_id)
    database = instance.database(database_id)

    params = {"start_title": start_title, "end_title": end_title}
    param_types = {
        "start_title": spanner.param_types.STRING,
        "end_title": spanner.param_types.STRING,
    }

    with database.snapshot() as snapshot:
        results = snapshot.execute_sql(
            "SELECT AlbumId, AlbumTitle, MarketingBudget "
            "FROM Albums@{FORCE_INDEX=AlbumsByAlbumTitle} "
            "WHERE AlbumTitle >= @start_title AND AlbumTitle < @end_title",
            params=params,
            param_types=param_types,
        )

        for row in results:
            print("AlbumId: {}, AlbumTitle: {}, " "MarketingBudget: {}".format(*row))

Ruby

# project_id  = "Your Google Cloud project ID"
# instance_id = "Your Spanner instance ID"
# database_id = "Your Spanner database ID"
# start_title = "An album title to start with such as 'Ardvark'"
# end_title   = "An album title to end with such as 'Goo'"

require "google/cloud/spanner"

spanner = Google::Cloud::Spanner.new project: project_id
client  = spanner.client instance_id, database_id

sql_query = "SELECT AlbumId, AlbumTitle, MarketingBudget
             FROM Albums@{FORCE_INDEX=AlbumsByAlbumTitle}
             WHERE AlbumTitle >= @start_title AND AlbumTitle < @end_title"

params      = { start_title: start_title, end_title: end_title }
param_types = { start_title: :STRING,     end_title: :STRING }

client.execute(sql_query, params: params, types: param_types).rows.each do |row|
  puts "#{row[:AlbumId]} #{row[:AlbumTitle]} #{row[:MarketingBudget]}"
end

Especificar un índice en la interfaz de lectura

Cuando usas la interfaz de lectura de Spanner y quieres que Spanner use un índice, debes especificarlo. La interfaz de lectura no selecciona el índice automáticamente.

Además, el índice debe contener todos los datos que aparecen en los resultados de la consulta, excepto las columnas que forman parte de la clave principal. Esta restricción se debe a que la interfaz de lectura no admite combinaciones entre el índice y la tabla base. Si necesitas incluir otras columnas en los resultados de la consulta, tienes varias opciones:

  • Usa una cláusula STORING o INCLUDE para almacenar las columnas adicionales en el índice.
  • Consulta sin incluir las columnas adicionales y, a continuación, usa las claves principales para enviar otra consulta que lea las columnas adicionales.

Spanner devuelve los valores del índice en orden ascendente por clave de índice. Para recuperar los valores en orden descendente, sigue estos pasos:

  • Anota la clave de índice con DESC. Por ejemplo:

    CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle DESC);
    

    La anotación DESC se aplica a una sola clave de índice. Si el índice incluye más de una clave y quieres que los resultados de la consulta aparezcan en orden descendente en función de todas las claves, incluye una anotación DESC para cada clave.

  • Si la lectura especifica un intervalo de claves, asegúrate de que el intervalo de claves también esté en orden descendente. Es decir, el valor de la clave de inicio debe ser mayor que el valor de la clave de finalización.

En el siguiente ejemplo se muestra cómo obtener los valores de AlbumId y AlbumTitle mediante el índice AlbumsByAlbumTitle:

C++

void ReadDataWithIndex(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;

  auto rows =
      client.Read("Albums", google::cloud::spanner::KeySet::All(),
                  {"AlbumId", "AlbumTitle"},
                  google::cloud::Options{}.set<spanner::ReadIndexNameOption>(
                      "AlbumsByAlbumTitle"));
  using RowType = std::tuple<std::int64_t, std::string>;
  for (auto& row : spanner::StreamOf<RowType>(rows)) {
    if (!row) throw std::move(row).status();
    std::cout << "AlbumId: " << std::get<0>(*row) << "\t";
    std::cout << "AlbumTitle: " << std::get<1>(*row) << "\n";
  }
  std::cout << "Read completed for [spanner_read_data_with_index]\n";
}

C#


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

public class QueryDataWithIndexAsyncSample
{
    public class Album
    {
        public int AlbumId { get; set; }
        public string AlbumTitle { get; set; }
        public long MarketingBudget { get; set; }
    }

    public async Task<List<Album>> QueryDataWithIndexAsync(string projectId, string instanceId, string databaseId,
        string startTitle, string endTitle)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";
        using var connection = new SpannerConnection(connectionString);
        using var cmd = connection.CreateSelectCommand(
            "SELECT AlbumId, AlbumTitle, MarketingBudget FROM Albums@ "
            + "{FORCE_INDEX=AlbumsByAlbumTitle} "
            + $"WHERE AlbumTitle >= @startTitle "
            + $"AND AlbumTitle < @endTitle",
            new SpannerParameterCollection
            {
                { "startTitle", SpannerDbType.String, startTitle },
                { "endTitle", SpannerDbType.String, endTitle }
            });

        var albums = new List<Album>();
        using var reader = await cmd.ExecuteReaderAsync();
        while (await reader.ReadAsync())
        {
            albums.Add(new Album
            {
                AlbumId = reader.GetFieldValue<int>("AlbumId"),
                AlbumTitle = reader.GetFieldValue<string>("AlbumTitle"),
                MarketingBudget = reader.IsDBNull(reader.GetOrdinal("MarketingBudget")) ? 0 : reader.GetFieldValue<long>("MarketingBudget")
            });
        }
        return albums;
    }
}

Go


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

Java

static void readUsingIndex(DatabaseClient dbClient) {
  try (ResultSet resultSet =
      dbClient
          .singleUse()
          .readUsingIndex(
              "Albums",
              "AlbumsByAlbumTitle",
              KeySet.all(),
              Arrays.asList("AlbumId", "AlbumTitle"))) {
    while (resultSet.next()) {
      System.out.printf("%d %s\n", resultSet.getLong(0), resultSet.getString(1));
    }
  }
}

Node.js

/**
 * TODO(developer): Uncomment these variables before running the sample.
 */
// const instanceId = 'my-instance';
// const databaseId = 'my-database';
// const projectId = 'my-project-id';

// Imports the Google Cloud Spanner client library
const {Spanner} = require('@google-cloud/spanner');

// Instantiates a client
const spanner = new Spanner({
  projectId: projectId,
});

async function readDataWithIndex() {
  // Gets a reference to a Cloud Spanner instance and database
  const instance = spanner.instance(instanceId);
  const database = instance.database(databaseId);

  const albumsTable = database.table('Albums');

  const query = {
    columns: ['AlbumId', 'AlbumTitle'],
    keySet: {
      all: true,
    },
    index: 'AlbumsByAlbumTitle',
  };

  // Reads the Albums table using an index
  try {
    const [rows] = await albumsTable.read(query);

    rows.forEach(row => {
      const json = row.toJSON();
      console.log(`AlbumId: ${json.AlbumId}, AlbumTitle: ${json.AlbumTitle}`);
    });
  } catch (err) {
    console.error('ERROR:', err);
  } finally {
    // Close the database when finished.
    database.close();
  }
}
readDataWithIndex();

PHP

use Google\Cloud\Spanner\SpannerClient;

/**
 * Reads sample data from the database using an index.
 *
 * The index must exist before running this sample. You can add the index
 * by running the `add_index` sample or by running this DDL statement against
 * your database:
 *
 *     CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)
 *
 * Example:
 * ```
 * read_data_with_index($instanceId, $databaseId);
 * ```
 *
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 */
function read_data_with_index(string $instanceId, string $databaseId): void
{
    $spanner = new SpannerClient();
    $instance = $spanner->instance($instanceId);
    $database = $instance->database($databaseId);

    $keySet = $spanner->keySet(['all' => true]);
    $results = $database->read(
        'Albums',
        $keySet,
        ['AlbumId', 'AlbumTitle'],
        ['index' => 'AlbumsByAlbumTitle']
    );

    foreach ($results->rows() as $row) {
        printf('AlbumId: %s, AlbumTitle: %s' . PHP_EOL,
            $row['AlbumId'], $row['AlbumTitle']);
    }
}

Python

def read_data_with_index(instance_id, database_id):
    """Reads sample data from the database using an index.

    The index must exist before running this sample. You can add the index
    by running the `add_index` sample or by running this DDL statement against
    your database:

        CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)

    """
    spanner_client = spanner.Client()
    instance = spanner_client.instance(instance_id)
    database = instance.database(database_id)

    with database.snapshot() as snapshot:
        keyset = spanner.KeySet(all_=True)
        results = snapshot.read(
            table="Albums",
            columns=("AlbumId", "AlbumTitle"),
            keyset=keyset,
            index="AlbumsByAlbumTitle",
        )

        for row in results:
            print("AlbumId: {}, AlbumTitle: {}".format(*row))

Ruby

# project_id  = "Your Google Cloud project ID"
# instance_id = "Your Spanner instance ID"
# database_id = "Your Spanner database ID"

require "google/cloud/spanner"

spanner = Google::Cloud::Spanner.new project: project_id
client  = spanner.client instance_id, database_id

result = client.read "Albums", [:AlbumId, :AlbumTitle],
                     index: "AlbumsByAlbumTitle"

result.rows.each do |row|
  puts "#{row[:AlbumId]} #{row[:AlbumTitle]}"
end

Crear un índice para análisis solo de índice

También puedes usar la cláusula STORING (para bases de datos con dialecto de GoogleSQL) o la cláusula INCLUDE (para bases de datos con dialecto de PostgreSQL) para almacenar una copia de una columna en el índice. Este tipo de índice ofrece ventajas para las consultas y las llamadas de lectura que lo usan, pero a costa de usar almacenamiento adicional:

  • Las consultas de SQL que usan el índice y seleccionan columnas almacenadas en la cláusula STORING o INCLUDE no requieren una unión adicional a la tabla base.
  • Las llamadas a read() que usan el índice pueden leer las columnas almacenadas por la cláusula STORING/INCLUDE.

Por ejemplo, supongamos que has creado una versión alternativa de AlbumsByAlbumTitle que almacena una copia de la columna MarketingBudget en el índice (fíjate en la cláusula STORING o INCLUDE en negrita):

GoogleSQL

CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget);

PostgreSQL

CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) INCLUDE (MarketingBudget);

Con el índice AlbumsByAlbumTitle antiguo, Spanner debe combinar el índice con la tabla base y, a continuación, recuperar la columna de la tabla base. Con el nuevo índice AlbumsByAlbumTitle2, Spanner lee la columna directamente del índice, lo que resulta más eficiente.

Si usas la interfaz de lectura en lugar de SQL, el nuevo índice AlbumsByAlbumTitle2 también te permite leer la columna MarketingBudget directamente:

C++

void ReadDataWithStoringIndex(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;

  auto rows =
      client.Read("Albums", google::cloud::spanner::KeySet::All(),
                  {"AlbumId", "AlbumTitle", "MarketingBudget"},
                  google::cloud::Options{}.set<spanner::ReadIndexNameOption>(
                      "AlbumsByAlbumTitle2"));
  using RowType =
      std::tuple<std::int64_t, std::string, absl::optional<std::int64_t>>;
  for (auto& row : spanner::StreamOf<RowType>(rows)) {
    if (!row) throw std::move(row).status();
    std::cout << "AlbumId: " << std::get<0>(*row) << "\t";
    std::cout << "AlbumTitle: " << std::get<1>(*row) << "\t";
    auto marketing_budget = std::get<2>(*row);
    if (marketing_budget) {
      std::cout << "MarketingBudget: " << *marketing_budget << "\n";
    } else {
      std::cout << "MarketingBudget: NULL\n";
    }
  }
  std::cout << "Read completed for [spanner_read_data_with_storing_index]\n";
}

C#


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

public class QueryDataWithStoringIndexAsyncSample
{
    public class Album
    {
        public int AlbumId { get; set; }
        public string AlbumTitle { get; set; }
        public long? MarketingBudget { get; set; }
    }

    public async Task<List<Album>> QueryDataWithStoringIndexAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";

        using var connection = new SpannerConnection(connectionString);
        var cmd = connection.CreateSelectCommand(
            "SELECT AlbumId, AlbumTitle, MarketingBudget FROM Albums@ "
            + "{FORCE_INDEX=AlbumsByAlbumTitle2}");

        var albums = new List<Album>();
        using var reader = await cmd.ExecuteReaderAsync();
        while (await reader.ReadAsync())
        {
            albums.Add(new Album
            {
                AlbumId = reader.GetFieldValue<int>("AlbumId"),
                AlbumTitle = reader.GetFieldValue<string>("AlbumTitle"),
                MarketingBudget = reader.IsDBNull(reader.GetOrdinal("MarketingBudget")) ? 0 : reader.GetFieldValue<long>("MarketingBudget")
            });
        }
        return albums;
    }
}

Go


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

Java

static void readStoringIndex(DatabaseClient dbClient) {
  // We can read MarketingBudget also from the index since it stores a copy of MarketingBudget.
  try (ResultSet resultSet =
      dbClient
          .singleUse()
          .readUsingIndex(
              "Albums",
              "AlbumsByAlbumTitle2",
              KeySet.all(),
              Arrays.asList("AlbumId", "AlbumTitle", "MarketingBudget"))) {
    while (resultSet.next()) {
      System.out.printf(
          "%d %s %s\n",
          resultSet.getLong(0),
          resultSet.getString(1),
          resultSet.isNull("MarketingBudget") ? "NULL" : resultSet.getLong("MarketingBudget"));
    }
  }
}

Node.js

/**
 * TODO(developer): Uncomment these variables before running the sample.
 */
// const instanceId = 'my-instance';
// const databaseId = 'my-database';
// const projectId = 'my-project-id';

// Imports the Google Cloud Spanner client library
const {Spanner} = require('@google-cloud/spanner');

// Instantiates a client
const spanner = new Spanner({
  projectId: projectId,
});

// "Storing" indexes store copies of the columns they index
// This speeds up queries, but takes more space compared to normal indexes
// See the link below for more information:
// https://cloud.google.com/spanner/docs/secondary-indexes#storing_clause
async function readDataWithStoringIndex() {
  // Gets a reference to a Cloud Spanner instance and database
  const instance = spanner.instance(instanceId);
  const database = instance.database(databaseId);

  const albumsTable = database.table('Albums');

  const query = {
    columns: ['AlbumId', 'AlbumTitle', 'MarketingBudget'],
    keySet: {
      all: true,
    },
    index: 'AlbumsByAlbumTitle2',
  };

  // Reads the Albums table using a storing index
  try {
    const [rows] = await albumsTable.read(query);

    rows.forEach(row => {
      const json = row.toJSON();
      let rowString = `AlbumId: ${json.AlbumId}`;
      rowString += `, AlbumTitle: ${json.AlbumTitle}`;
      if (json.MarketingBudget) {
        rowString += `, MarketingBudget: ${json.MarketingBudget}`;
      }
      console.log(rowString);
    });
  } catch (err) {
    console.error('ERROR:', err);
  } finally {
    // Close the database when finished.
    database.close();
  }
}
readDataWithStoringIndex();

PHP

use Google\Cloud\Spanner\SpannerClient;

/**
 * Reads sample data from the database using an index with a storing
 * clause.
 *
 * The index must exist before running this sample. You can add the index
 * by running the `add_storing_index` sample or by running this DDL statement
 * against your database:
 *
 *     CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle)
 *     STORING (MarketingBudget)
 *
 * Example:
 * ```
 * read_data_with_storing_index($instanceId, $databaseId);
 * ```
 *
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 */
function read_data_with_storing_index(string $instanceId, string $databaseId): void
{
    $spanner = new SpannerClient();
    $instance = $spanner->instance($instanceId);
    $database = $instance->database($databaseId);

    $keySet = $spanner->keySet(['all' => true]);
    $results = $database->read(
        'Albums',
        $keySet,
        ['AlbumId', 'AlbumTitle', 'MarketingBudget'],
        ['index' => 'AlbumsByAlbumTitle2']
    );

    foreach ($results->rows() as $row) {
        printf('AlbumId: %s, AlbumTitle: %s, MarketingBudget: %d' . PHP_EOL,
            $row['AlbumId'], $row['AlbumTitle'], $row['MarketingBudget']);
    }
}

Python

def read_data_with_storing_index(instance_id, database_id):
    """Reads sample data from the database using an index with a storing
    clause.

    The index must exist before running this sample. You can add the index
    by running the `add_scoring_index` sample or by running this DDL statement
    against your database:

        CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle)
        STORING (MarketingBudget)

    """
    spanner_client = spanner.Client()
    instance = spanner_client.instance(instance_id)
    database = instance.database(database_id)

    with database.snapshot() as snapshot:
        keyset = spanner.KeySet(all_=True)
        results = snapshot.read(
            table="Albums",
            columns=("AlbumId", "AlbumTitle", "MarketingBudget"),
            keyset=keyset,
            index="AlbumsByAlbumTitle2",
        )

        for row in results:
            print("AlbumId: {}, AlbumTitle: {}, " "MarketingBudget: {}".format(*row))

Ruby

# project_id  = "Your Google Cloud project ID"
# instance_id = "Your Spanner instance ID"
# database_id = "Your Spanner database ID"

require "google/cloud/spanner"

spanner = Google::Cloud::Spanner.new project: project_id
client  = spanner.client instance_id, database_id

result = client.read "Albums", [:AlbumId, :AlbumTitle, :MarketingBudget],
                     index: "AlbumsByAlbumTitle2"

result.rows.each do |row|
  puts "#{row[:AlbumId]} #{row[:AlbumTitle]} #{row[:MarketingBudget]}"
end

Modificar un índice

Puedes usar la instrucción ALTER INDEX para añadir columnas adicionales a un índice o eliminar columnas. De esta forma, se puede actualizar la lista de columnas definida por la cláusula STORING (bases de datos del dialecto de GoogleSQL) o la cláusula INCLUDE (bases de datos del dialecto de PostgreSQL) al crear el índice. No puede usar esta instrucción para añadir o eliminar columnas de la clave de índice. Por ejemplo, en lugar de crear un nuevo índice AlbumsByAlbumTitle2, puedes usar ALTER INDEX para añadir una columna a AlbumsByAlbumTitle, como se muestra en el siguiente ejemplo:

GoogleSQL

ALTER INDEX AlbumsByAlbumTitle ADD STORED COLUMN MarketingBudget

PostgreSQL

ALTER INDEX AlbumsByAlbumTitle ADD INCLUDE COLUMN MarketingBudget

Cuando añades una columna a un índice, Spanner usa un proceso de relleno en segundo plano. Mientras se realiza el relleno, la columna del índice no se puede leer, por lo que es posible que no obtengas la mejora del rendimiento esperada. Puedes usar el comando gcloud spanner operations para enumerar la operación de larga duración y ver su estado. Para obtener más información, consulta describe operation.

También puedes usar cancel operation para cancelar una operación en curso.

Una vez completado el relleno, Spanner añade la columna al índice. A medida que el índice aumenta, las consultas que lo usan pueden ralentizarse.

En el siguiente ejemplo se muestra cómo eliminar una columna de un índice:

GoogleSQL

ALTER INDEX AlbumsByAlbumTitle DROP STORED COLUMN MarketingBudget

PostgreSQL

ALTER INDEX AlbumsByAlbumTitle DROP INCLUDE COLUMN MarketingBudget

Índice de valores NULL

De forma predeterminada, Spanner indexa los valores NULL. Por ejemplo, recuerda la definición del índice SingersByFirstLastName en la tabla Singers:

CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName);

Todas las filas de Singers se indexan aunque FirstName o LastName, o ambos, sean NULL.

Un diagrama muestra las filas que se omiten de un índice filtrado por NULL.

Cuando se indexan los valores de NULL, puedes realizar consultas y lecturas SQL eficientes sobre los datos que incluyen valores de NULL. Por ejemplo, usa esta instrucción de consulta SQL para buscar todos los Singers con un NULL FirstName:

GoogleSQL

SELECT s.SingerId, s.FirstName, s.LastName
    FROM Singers@{FORCE_INDEX=SingersByFirstLastName} AS s
    WHERE s.FirstName IS NULL;

PostgreSQL

SELECT s.SingerId, s.FirstName, s.LastName
    FROM Singers /* @ FORCE_INDEX = SingersByFirstLastName */ AS s
    WHERE s.FirstName IS NULL;

Orden de clasificación de los valores NULL

Spanner ordena NULL como el valor más pequeño de cualquier tipo. En el caso de una columna ordenada de forma ascendente (ASC), los valores NULL se ordenan primero. En una columna ordenada de forma descendente (DESC), los valores NULL se ordenan al final.

Inhabilitar la indexación de valores NULL

GoogleSQL

Para inhabilitar la indexación de valores nulos, añade la palabra clave NULL_FILTERED a la definición del índice. Los índices NULL_FILTERED son especialmente útiles para indexar columnas dispersas, en las que la mayoría de las filas contienen un valor NULL. En estos casos, el índice NULL_FILTERED puede ser considerablemente más pequeño y eficiente de mantener que un índice normal que incluya valores NULL.

Aquí tienes una definición alternativa de SingersByFirstLastName que no indexa los valores de NULL:

CREATE NULL_FILTERED INDEX SingersByFirstLastNameNoNulls
    ON Singers(FirstName, LastName);

La palabra clave NULL_FILTERED se aplica a todas las columnas de clave de índice. No puede especificar el filtrado NULL por columnas.

PostgreSQL

Para excluir las filas con valores nulos en una o varias columnas indexadas, usa el predicado WHERE COLUMN IS NOT NULL. Los índices filtrados por valores NULL son especialmente útiles para indexar columnas dispersas, donde la mayoría de las filas contienen un valor NULL. En estos casos, el índice filtrado por nulos puede ser considerablemente más pequeño y eficiente de mantener que un índice normal que incluya valores NULL.

Aquí tienes una definición alternativa de SingersByFirstLastName que no indexa los valores de NULL:

CREATE INDEX SingersByFirstLastNameNoNulls
    ON Singers(FirstName, LastName)
    WHERE FirstName IS NOT NULL
    AND LastName IS NOT NULL;

Si se excluyen los valores NULL, se impide que Spanner los utilice en algunas consultas. Por ejemplo, Spanner no usa el índice en esta consulta porque omite las filas de Singers en las que LastName es NULL. Por lo tanto, si se usara el índice, la consulta no devolvería las filas correctas:

GoogleSQL

FROM Singers@{FORCE_INDEX=SingersByFirstLastNameNoNulls}
    WHERE FirstName = "John";

PostgreSQL

FROM Singers /*@ FORCE_INDEX = SingersByFirstLastNameNoNulls */
    WHERE FirstName = 'John';

Para que Spanner pueda usar el índice, debes reescribir la consulta de forma que excluya las filas que también se excluyen del índice:

GoogleSQL

SELECT FirstName, LastName
    FROM Singers@{FORCE_INDEX=SingersByFirstLastNameNoNulls}
    WHERE FirstName = 'John' AND LastName IS NOT NULL;

PostgreSQL

SELECT FirstName, LastName
    FROM Singers /*@ FORCE_INDEX = SingersByFirstLastNameNoNulls */
    WHERE FirstName = 'John' AND LastName IS NOT NULL;

Campos de proto de índice

Usa columnas generadas para indexar campos en búferes de protocolo almacenados en columnas PROTO, siempre que los campos que se indexen usen los tipos de datos primitivos o ENUM.

Si defines un índice en un campo de mensaje de protocolo, no puedes modificar ni eliminar ese campo del esquema proto. Para obtener más información, consulta Actualizaciones de esquemas que contienen un índice en campos proto.

A continuación, se muestra un ejemplo de la tabla Singers con una columna de mensaje proto SingerInfo. Para definir un índice en el campo nationality de PROTO, debes crear una columna generada almacenada:

GoogleSQL

CREATE PROTO BUNDLE (googlesql.example.SingerInfo, googlesql.example.SingerInfo.Residence);

CREATE TABLE Singers (
  SingerId INT64 NOT NULL,
  ...
  SingerInfo googlesql.example.SingerInfo,
  SingerNationality STRING(MAX) AS (SingerInfo.nationality) STORED
) PRIMARY KEY (SingerId);

Tiene la siguiente definición del tipo proto googlesql.example.SingerInfo:

GoogleSQL

package googlesql.example;

message SingerInfo {
optional string    nationality = 1;
repeated Residence residence   = 2;

  message Residence {
    required int64  start_year   = 1;
    optional int64  end_year     = 2;
    optional string city         = 3;
    optional string country      = 4;
  }
}

A continuación, define un índice en el campo nationality del proto:

GoogleSQL

CREATE INDEX SingersByNationality ON Singers(SingerNationality);

La siguiente consulta de SQL lee datos mediante el índice anterior:

GoogleSQL

SELECT s.SingerId, s.FirstName
FROM Singers AS s
WHERE s.SingerNationality = "English";

Notas:

  • Usa una directiva de índice para acceder a los índices de los campos de las columnas de búfer de protocolo.
  • No puedes crear un índice en campos de búfer de protocolo repetidos.

Actualizaciones de esquemas que contienen un índice en campos proto

Si defines un índice en un campo de mensaje de protocolo, no puedes modificar ni eliminar ese campo del esquema proto. Esto se debe a que, después de definir el índice, se realiza una comprobación de tipos cada vez que se actualiza el esquema. Spanner captura la información de tipo de todos los campos de la ruta que se utilizan en la definición del índice.

Índices únicos

Los índices se pueden declarar UNIQUE. Los índices UNIQUE añaden una restricción a los datos que se indexan, lo que impide que haya entradas duplicadas para una clave de índice determinada. Spanner aplica esta restricción en el momento de confirmar la transacción. En concreto, no se podrá confirmar ninguna transacción que provoque que existan varias entradas de índice para la misma clave.

Si una tabla contiene datos que no son UNIQUE desde el principio, no se podrá crear un índice UNIQUE en ella.

Nota sobre los índices UNIQUE NULL_FILTERED

Un índice UNIQUE NULL_FILTERED no exige la unicidad de la clave de índice cuando al menos una de las partes de la clave del índice es NULL.

Por ejemplo, supongamos que ha creado la siguiente tabla e índice:

GoogleSQL

CREATE TABLE ExampleTable (
  Key1 INT64 NOT NULL,
  Key2 INT64,
  Key3 INT64,
  Col1 INT64,
) PRIMARY KEY (Key1, Key2, Key3);

CREATE UNIQUE NULL_FILTERED INDEX ExampleIndex ON ExampleTable (Key1, Key2, Col1);

PostgreSQL

CREATE TABLE ExampleTable (
  Key1 BIGINT NOT NULL,
  Key2 BIGINT,
  Key3 BIGINT,
  Col1 BIGINT,
  PRIMARY KEY (Key1, Key2, Key3)
);

CREATE UNIQUE INDEX ExampleIndex ON ExampleTable (Key1, Key2, Col1)
    WHERE Key1 IS NOT NULL
    AND Key2 IS NOT NULL
    AND Col1 IS NOT NULL;

Las dos filas siguientes de ExampleTable tienen los mismos valores para las claves de índice secundario Key1, Key2 y Col1:

1, NULL, 1, 1
1, NULL, 2, 1

Como Key2 es NULL y el índice se ha filtrado por nulos, las filas no estarán presentes en el índice ExampleIndex. Como no se insertan en el índice, este no los rechazará por infringir la unicidad en (Key1, Key2, Col1).

Si quieres que el índice aplique la unicidad de los valores de la tupla (Key1, Key2, Col1), debes anotar Key2 con NOT NULL en la definición de la tabla o crear el índice sin filtrar los valores nulos.

Eliminar un índice

Usa la instrucción DROP INDEX para eliminar un índice secundario de tu esquema.

Para eliminar el índice llamado SingersByFirstLastName, haz lo siguiente:

DROP INDEX SingersByFirstLastName;

Índice para acelerar el análisis

Cuando Spanner necesita realizar un análisis de tabla (en lugar de una búsqueda indexada) para obtener valores de una o varias columnas, puedes recibir resultados más rápidos si existe un índice para esas columnas y en el orden especificado por la consulta. Si realizas con frecuencia consultas que requieren análisis, considera la posibilidad de crear índices secundarios para que estos análisis se realicen de forma más eficiente.

En concreto, si necesitas que Spanner analice con frecuencia la clave principal u otro índice de una tabla en orden inverso, puedes aumentar su eficiencia mediante un índice secundario que haga explícito el orden elegido.

Por ejemplo, la siguiente consulta siempre devuelve un resultado rápido, aunque Spanner tenga que analizar Songs para encontrar el valor más bajo de SongId:

SELECT SongId FROM Songs LIMIT 1;

SongId es la clave principal de la tabla, que se almacena (como todas las claves principales) en orden ascendente. Spanner puede analizar el índice de esa clave y encontrar el primer resultado rápidamente.

Sin embargo, sin la ayuda de un índice secundario, la siguiente consulta no se devolvería tan rápido, sobre todo si Songs contiene muchos datos:

SELECT SongId FROM Songs ORDER BY SongId DESC LIMIT 1;

Aunque SongId es la clave principal de la tabla, Spanner no tiene forma de obtener el valor más alto de la columna sin recurrir a un análisis completo de la tabla.

Si añades el siguiente índice, esta consulta se podrá completar más rápido:

CREATE INDEX SongIdDesc On Songs(SongId DESC);

Con este índice, Spanner lo usaría para devolver un resultado para la segunda consulta mucho más rápido.

Siguientes pasos