Índices secundarios

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

También puedes crear índices secundarios para otras columnas. Agregar un índice secundario a una columna hace que la búsqueda de datos en esa columna sea más eficiente. Para Por ejemplo, si necesitas buscar rápidamente un álbum por título, deberías crea un índice secundario en AlbumTitle para que Spanner no necesite analizar toda la tabla.

Si la búsqueda del ejemplo anterior se realiza dentro de 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 inserciones y actualizaciones simultáneas de la tabla en las filas que se encuentran fuera del Rango de búsqueda de AlbumTitle.

Además de los beneficios que brindan a las búsquedas, los índices secundarios pueden también ayudan a que Spanner ejecute análisis de forma más eficiente, lo que permite análisis del índice, en lugar de análisis completos de la tabla.

Spanner almacena los siguientes datos en cada índice secundario:

Con el tiempo, Spanner analiza las tablas para garantizar que las se usan para las consultas correspondientes.

Agrega un índice secundario

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

En Spanner, también puedes agregar un nuevo índice secundario a un mientras la base de datos sigue entregando tráfico. Como cualquier otro esquema cambios en Spanner, agregar un índice a una base de datos existente no requieren que la base de datos esté sin conexión y no bloquee columnas ni tablas enteras.

Cada vez que se agrega un índice nuevo a una tabla existente, Spanner reabastece o propaga automáticamente el índice para reflejar una vista actualizada. de los datos que se indexan. Spanner administra este proceso de reabastecimiento automáticamente, y el proceso se ejecuta en segundo plano usando recursos de nodos en prioridad. En la mayoría de los casos, no es posible acelerar el proceso (p.ej., mediante agregar más nodos), y el reabastecimiento no afecta de forma significativa al el rendimiento de la base de datos.

La creación del índice puede tardar desde varios minutos hasta muchas horas. Debido a que 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 procesamiento de la instancia
  • La carga de la instancia

Para ver el progreso del proceso de reabastecimiento de índices, consulta la sección de progreso.

Ten en cuenta que usar la columna confirmar marca de tiempo como la primera parte del índice secundario puede crear hotspots y reducir el rendimiento de la operación de escritura.

Usa la sentencia CREATE INDEX para definir un índice secundario en tu esquema. Estos son algunos ejemplos:

Para indexar todos los Singers en la base de datos por su nombre y apellido, ejecuta el comando siguiente:

GoogleSQL

CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName);

PostgreSQL

CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName);

Para crear un índice de todas las Songs en la base de datos por el valor de SongName, ejecuta el comando siguiente:

GoogleSQL

CREATE INDEX SongsBySongName ON Songs(SongName);

PostgreSQL

CREATE INDEX SongsBySongName ON Songs(SongName);

Si deseas indexar solo las canciones de un cantante en particular, 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 en particular, ejecuta lo siguiente:

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, ejecuta 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 indexarlos por orden descendente de otras claves de índice, anótalas con DESC y SingerId DESC, AlbumId DESC.

También ten en cuenta que PRIMARY_KEY es una palabra reservada y no se puede usar como el nombre de un índice. Es el nombre asignado al seudo-índice que se crea cuando se crea una tabla con especificaciones de CLAVE PRIMARIA

Para obtener más detalles y prácticas recomendadas sobre cómo elegir índices no intercalados e índices intercalados, consulta Opciones de índice y Usa un índice intercalado en una columna cuyo valor aumenta o disminuye de forma monotónica.

Verifica el progreso del reabastecimiento de índices

Console

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

  2. Busca la operación de reabastecimiento en la lista. Si aún se está ejecutando, el el indicador de progreso en la columna Hora de finalización muestra el porcentaje de la operación que se complete, como se muestra en la siguiente imagen:

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

gcloud

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

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

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

    Reemplaza lo siguiente:

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

    Notas de uso:

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

      • --filter="metadata.name:example-db" solo enumera las operaciones. en una base de datos específica.
      • --filter="error:*" solo enumera las operaciones de copia de seguridad que fallaron.

      Para obtener información sobre la sintaxis de los filtros, consulta los filtros de tema 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 mayúsculas de 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
    

    Reemplaza lo siguiente:

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

    En la sección progress del resultado, se muestra el porcentaje de la operación. que está completo. El resultado es 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 operación:

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

Reemplaza lo siguiente:

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

Antes de usar cualquiera de los datos de solicitud a continuación, realiza los siguientes reemplazos:

  • PROJECT-ID: El ID del proyecto.
  • INSTANCE-ID: El ID de la instancia.
  • DATABASE-ID: Es el ID de la base de datos.
  • OPERATION-ID: Es 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, expande una de estas opciones:

Deberías recibir una respuesta JSON similar a la que se muestra a continuación:

{
...
    "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"
  }
}

Para gcloud y REST, puedes encontrar el progreso de cada reabastecimiento de índices en la sección progress. Para cada instrucción en la matriz de instrucciones, hay un campo correspondiente en la matriz de progreso. El orden de este array de progreso corresponde al orden del array de las sentencias. Una vez que están disponibles, Los campos startTime, progressPercent y endTime se propagan según corresponda. Ten en cuenta que el resultado no muestra un tiempo estimado para que el reabastecimiento el progreso se completará.

Si la operación tarda demasiado, puedes cancelarla. Para obtener más información, consulta Cancela la creación del índice.

Situaciones en las que se visualiza el progreso del reabastecimiento de índices

Hay diferentes situaciones que pueden ocurrir al intentar verificar el progreso de un reabastecimiento de índices. Declaraciones de creación de índices que requieren un índice el reabastecimiento son parte de las operaciones de actualización del esquema, y puede haber que forman parte de una operación de actualización de esquema.

La primera situación es la más sencilla, que ocurre cuando la declaración de creación del índice es la primera sentencia en la operación de actualización del esquema. Desde que se creó el índice es la primera, es la primera que se procesa y ejecuta debido a al orden de ejecución. De inmediato, el campo startTime de la declaración de creación del índice se propaga con la hora de inicio de la operación de actualización del esquema. A continuación, el índice el campo progressPercent de la sentencia de creación se completa cuando el progreso de el reabastecimiento del índice supera el 0%. Por último, el campo endTime se propaga una vez se confirma la instrucción.

La segunda situación es cuando la declaración de creación del índice no es la primera en la operación de actualización del esquema. No hay campos relacionados con el índice la declaración de creación se propagará hasta que se hayan completado las declaraciones anteriores comprometidos debido a la orden de ejecución. Al igual que en el escenario anterior, una vez que se confirman las declaraciones anteriores, El campo startTime de la declaración de creación de índices se propaga primero y, luego, el campo progressPercent. Por último, el campo endTime se completa una vez que finaliza la confirmación.

Cancelar la creación del índice

Puedes usar Google Cloud CLI para cancelar la creación de índices. Para recuperar una lista de para una base de datos de Spanner, 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 deseas cancelar y, luego, usa el comando gcloud spanner operations cancel con el fin de cancelarla:

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

Ver los índices existentes

Para ver información sobre índices existentes en una base de datos, puedes usar el La consola de Google Cloud o Google Cloud CLI:

Console

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

    Ir a la página Instancias

  2. Haz clic en el nombre de la instancia que deseas ver.

  3. En el panel izquierdo, haz clic en la base de datos que desea ver y, luego, en la tabla que deseas visualizar.

  4. Haz clic en la pestaña Índices. En la consola de Google Cloud, se muestra una lista los índices.

  5. De manera opcional, para obtener detalles sobre un índice, como las columnas que incluye, haz clic en el nombre del índice.

gcloud

Usa el comando gcloud spanner databases ddl describe:

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

Gcloud CLI imprime el lenguaje de definición de datos (DDL). para crear las tablas y los índices de la base de datos. Las sentencias CREATE INDEX describen los índices existentes. 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)

Consulta con un índice específico

En las siguientes secciones, se explica cómo especificar un índice en una instrucción de SQL y con la interfaz de lectura para Spanner. En los ejemplos de estas secciones, se supone que agregaste una columna MarketingBudget a la tabla Albums y que creaste 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);

Especifica un índice en una instrucción de SQL

Cuando usas SQL para consultar una tabla de Spanner, este servicio y usa los índices que podrían hacer que la consulta sea más eficaz. Como resultado, no necesitas especificar un índice para las consultas en SQL. Sin embargo, para consultas que son fundamentales para tu carga de trabajo, Google te recomienda usar FORCE_INDEX en tus instrucciones de SQL para lograr un rendimiento más coherente.

En algunos casos, Spanner puede elegir un índice que genere consultas aumentar la latencia. Si seguiste los pasos para solucionar problemas de las regresiones de rendimiento y confirmaste que tiene sentido probar otro índice para la consulta, puedes especificarlo como parte de tu consulta.

Para especificar un índice en una instrucción de SQL, usa FORCE_INDEX Sugerencia para proporcionar una directiva de índice. Las directivas de índice usan la sintaxis que se menciona a continuación:

GoogleSQL

FROM MyTable@{FORCE_INDEX=MyTableIndex}

PostgreSQL

FROM MyTable /*@ FORCE_INDEX = MyTableIndex */

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

GoogleSQL

FROM MyTable@{FORCE_INDEX=_BASE_TABLE}

PostgreSQL

FROM MyTable /*@ FORCE_INDEX = _BASE_TABLE */

En el siguiente ejemplo, se muestra una consulta de 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 podría forzar la lectura del procesador de consultas de Spanner columnas adicionales requeridas por la consulta, pero que no se almacenan en el índice. El procesador de consultas recupera estas columnas mediante la unión del índice y la tabla base. Para evitar esta unión adicional, usa un Cláusula STORING (bases de datos del dialecto GoogleSQL) o INCLUDE (bases de datos del dialecto PostgreSQL) para almacenar las columnas adicionales en el índice.

En el ejemplo anterior, la columna MarketingBudget no está almacenados en el índice, pero la consulta en SQL selecciona esta columna. Como resultado, Spanner debe buscar la columna MarketingBudget en la tabla base. y luego unirla con los datos del índice para devolver los resultados de la consulta.

Spanner genera un error si la directiva de índice tiene alguna de las siguientes opciones problemas:

En los siguientes ejemplos, se muestra cómo escribir y ejecutar consultas que recuperan 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

Especifica un índice en la interfaz de lectura

Cuando usas la interfaz de lectura de Spanner y quieres para usar un índice, debes especificarlo. La interfaz de lectura no selecciona el índice de forma automática.

Además, tu índice debe contener todos los datos que aparecen en los resultados de la consulta, sin incluir las columnas que forman parte de la clave primaria. Esta restricción existe porque la interfaz de lectura no es compatible con combinaciones entre el índice y la tabla base. Si necesitas incluir otras columnas en los resultados de la consulta, puedes elegir entre las siguientes opciones:

  • Usa una cláusula STORING o INCLUDE para almacenar las columnas adicionales en el índice.
  • Realiza consultas sin incluir las columnas adicionales y usa las claves primarias para enviar otra consulta que lea las columnas adicionales.

Spanner muestra los valores del índice en orden ascendente por í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 según todas las claves, incluye una anotación DESC para cada clave.

  • Si la lectura especifica un rango de clave, asegúrate de que el rango de clave también esté en orden descendente. En otras palabras, el valor de la clave de inicio debe ser mayor que el valor de la clave de cierre.

En el siguiente ejemplo, se muestra cómo recuperar los valores de AlbumId y AlbumTitle con 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

Crea un índice para análisis solo de índices

De forma opcional, puedes usar la cláusula STORING (para bases de datos del dialecto GoogleSQL) o INCLUDE (para bases de datos de dialectos de PostgreSQL) para almacenar una copia de una columna en la índice. Este tipo de índice proporciona ventajas para las consultas y las llamadas de operación lectura que usan el índice, a costa de usar almacenamiento adicional:

  • Consultas en SQL que usan el índice y seleccionan las columnas almacenadas en el 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 el STORING/INCLUDE.

Por ejemplo, supongamos que creaste una versión alternativa de AlbumsByAlbumTitle que almacena una copia de la columna MarketingBudget en el índice (ten en cuenta el Cláusulas 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 anterior, Spanner debe unirse al índice. con la tabla base y luego recupera la columna de la tabla base. Con la nueva AlbumsByAlbumTitle2, Spanner lee la columna directamente del índice, que es más eficiente.

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

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 sentencia ALTER INDEX para agregar columnas adicionales en un índice existente o descartar columnas. Esta Puede actualizar la lista de columnas definida por la cláusula STORING. (bases de datos del dialecto GoogleSQL) o la cláusula INCLUDE (bases de datos del dialecto PostgreSQL) cuando creas la índice. No puedes usar esta declaración para agregar o quitar columnas columnas de la clave de índice. Por ejemplo, en lugar de crear una nueva índice AlbumsByAlbumTitle2, puedes usar ALTER INDEX para agregar en 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 agregas una columna nueva a un índice existente, Spanner usa un proceso de reabastecimiento en segundo plano. Mientras el reabastecimiento está en curso, la columna del índice no es legible, así que es posible que no un aumento del rendimiento. 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 Describir operación.

También puedes usar Cancelar operación para cancelar una operación en ejecución.

Cuando se completa el reabastecimiento, Spanner agrega la columna al índice. Como índice crece y puede ralentizar las consultas que usan el índice.

En el siguiente ejemplo, se muestra cómo descartar 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, recupera la definición del índice SingersByFirstLastName en la tabla Singers:

CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName);

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

En un diagrama, se muestran las filas que se omiten en un índice filtrado NULL.

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

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;

Ordena por valores NULL

Spanner ordena NULL como el valor más pequeño para cualquier tipo determinado. Para una columna en orden ascendente (ASC), los valores NULL se ordenan primero. Para una columna en orden descendente (DESC), los valores NULL se ordenan por último.

Inhabilitar la indexación de los valores NULL

GoogleSQL

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

Esta es una definición alternativa de SingersByFirstLastName que no indexa valores 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 puedes especificar el filtro NULL por columna.

PostgreSQL

Para filtrar filas con valores nulos en una o más columnas indexadas, usa el Predicado WHERE COLUMN IS NOT NULL. Los índices con filtro nulo son particularmente útiles para indexar columnas, en las que la mayoría de las filas contienen un valor NULL. En estos casos, el un índice con filtro nulo puede ser considerablemente más pequeño y más eficiente de mantener que un índice normal que incluya valores NULL.

Esta es una definición alternativa de SingersByFirstLastName que no indexa valores NULL:

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

Filtrar valores de NULL evita que Spanner los use para algunos para tus consultas. Por ejemplo, Spanner no usa el índice para esta consulta, porque el índice omite cualquier fila de Singers en la que LastName sea NULL; como resultado, usar el índice impediría que la consulta mostrara las filas correctas:

GoogleSQL

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

PostgreSQL

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

Para permitir que Spanner use el índice, debes volver a escribir la consulta de modo que Excluye 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 proto de índice

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

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

El siguiente es un ejemplo de la tabla Singers con un proto SingerInfo mensaje. Para definir un índice en el campo nationality de PROTO, haz lo siguiente: 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 de 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;
  }
}

Luego, define un índice en el campo nationality del proto:

GoogleSQL

CREATE INDEX SingersByNationality ON Singers(SingerNationality);

La siguiente consulta en SQL lee los datos con 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 en la campos de columnas del búfer de protocolo.
  • No puedes crear un índice en campos de búfer de protocolo repetidos.

Actualizaciones de los esquemas que contienen un índice en campos proto

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

Índices únicos

Los índices se pueden declarar como UNIQUE. Los índices UNIQUE agregan una restricción a los datos que se indexan y que prohíben las entradas duplicadas para una clave de índice determinada. Spanner aplica esta restricción en el momento de la confirmación de la transacción. En particular, no se confirmarán las transacciones que harían que existan varias entradas de índice para la misma clave.

Si una tabla contiene datos que no son UNIQUE en ella, cuando intentes crear un índice UNIQUE en ella, fallará.

Una nota sobre los índices UNIQUE NULL_FILTERED

Un índice UNIQUE NULL_FILTERED no fuerza la unicidad de la clave de índice cuando una de las partes clave del índice, como mínimo, es NULL.

Por ejemplo, supongamos que creaste el siguiente índice y tabla:

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 siguientes dos filas en ExampleTable tienen los mismos valores para las claves de índice secundarias Key1, Key2 y Col1:

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

Debido a que Key2 es NULL y el índice se filtra por valores nulos, las filas no se presente en el índice ExampleIndex. Debido a que no se insertan en la índice, este no los rechazará por incumplir la unicidad en (Key1, Key2, Col1).

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

Cómo descartar un índice

Usa la sentencia DROP INDEX desde la que se descartará un índice secundario tu esquema.

Para descartar el índice llamado SingersByFirstLastName, usa la siguiente declaración:

DROP INDEX SingersByFirstLastName;

Índice para un análisis más rápido

Cuando Spanner necesita realizar un análisis de tabla (en lugar de un búsqueda) para recuperar valores de una o más columnas, puedes recibir notificaciones los resultados si existe un índice para esas columnas, y en el orden que se especifica en la consulta. Si realizas con frecuencia consultas que requieran análisis, crea índices secundarios que te ayuden para que estos análisis se realicen de forma más eficiente.

En particular, si necesitas que Spanner analice con frecuencia la clave primaria u otro índice en orden inverso, puedes aumentar su de eficiencia a través de un índice secundario que realice el orden elegido explícito.

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

SELECT SongId FROM Songs LIMIT 1;

SongId es la clave primaria de la tabla, almacenada (como con todas las claves primarias) 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 volver tan rápido, en especial si Songs contiene muchos datos:

SELECT SongId FROM Songs ORDER BY SongId DESC LIMIT 1;

Aunque SongId es la clave primaria de la tabla, Spanner no tiene de obtener el valor más alto de la columna sin recurrir a un valor analizar la tabla.

Agregar el siguiente índice permitiría que esta consulta muestre más rápidamente:

CREATE INDEX SongIdDesc On Songs(SongId DESC);

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

¿Qué sigue?