Índices secundarios

En una base de datos de Cloud Spanner, este crea un índice de forma automática para la columna de clave primaria de cada tabla. Por ejemplo, no es necesario realizar ninguna acción para indexar la columna de clave primaria de Singers, ya que se indexa de forma automática.

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. Por ejemplo, si necesitas buscar con rapidez un conjunto de valores SingerId para un rango determinado de valores LastName, deberías crear un índice secundario en LastName, así Cloud Spanner no necesita analizar toda la tabla.

Cloud Spanner almacena los siguientes datos en cada índice secundario:

  • Todas las columnas de clave de la tabla base
  • Todas las columnas que se incluyen en el índice
  • Todas las columnas especificadas en la cláusula STORING opcional de la definición del índice

Con el tiempo, Cloud Spanner analiza tus tablas con el fin de asegurarse de que tus índices secundarios se usen para las consultas adecuadas.

Agrega un índice secundario

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

En Cloud Spanner, también puedes agregar un nuevo índice secundario a una tabla existente mientras la base de datos continúa entregando tráfico. Al igual que cualquier otro cambio de esquema en Cloud Spanner, agregar un índice a una base de datos existente no requiere que la base de datos esté sin conexión ni bloquea columnas o tablas completas.

Cada vez que se agrega un índice nuevo a una tabla existente, Cloud Spanner reabastece o propaga de forma automática el índice para reflejar una vista actualizada de los datos que se indexan. Cloud Spanner administra este proceso de reabastecimiento por ti y usa recursos adicionales durante el reabastecimiento de los índices.

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 cantidad de nodos en la instancia
  • La carga de la instancia

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 declaración 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:

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:

CREATE INDEX SongsBySongName ON Songs(SongName)

Para indexar solo las canciones de un cantante en particular, intercala el índice en la tabla Singers:

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

Para indexar solo las canciones de un álbum en particular, ejecuta lo siguiente:

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

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

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.

Cancela la creación del índice

Puedes usar el SDK de Cloud para cancelar la creación del índice. Si deseas recuperar una lista de operaciones de actualización de esquema para una base de datos de Cloud Spanner, usa el comando gcloud spanner operations list y, luego, 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

Visualiza los índices existentes

Para ver información sobre los índices existentes en una base de datos, puedes usar Google Cloud Console o la herramienta de línea de comandos de gcloud:

Console

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

    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 Cloud Console, se muestra una lista de í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

La herramienta de gcloud imprime las declaraciones del Lenguaje de definición de datos (DDL) para crear los índices y las tablas de la base de datos. Las declaraciones 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 Cloud 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:

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

Especifica un índice en una instrucción de SQL

Cuando usas SQL para consultar una tabla de Cloud Spanner, este usa de forma automática cualquier índice que pueda hacer que la consulta sea más eficaz. Como resultado, en general, no es necesario especificar un índice para las consultas de SQL.

Sin embargo, en algunos casos, Cloud Spanner podría elegir un índice que aumente la latencia de las consultas. 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.

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

FROM MyTable@{FORCE_INDEX=MyTableIndex}

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

FROM MyTable@{FORCE_INDEX=_BASE_TABLE}

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

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 Cloud Spanner a leer columnas adicionales que la consulta requiere, 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. Si deseas evitar esta unión adicional, usa una cláusula STORING para almacenar las columnas adicionales en el índice.

Por ejemplo, en la muestra anterior, la columna MarketingBudget no se almacena en el índice, pero la consulta de SQL selecciona esta columna. Como resultado, Cloud Spanner debe buscar la columna MarketingBudget en la tabla base y, luego, unirla a los datos del índice para mostrar los resultados de la consulta.

Cloud 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 recuperan los valores de AlbumId, AlbumTitle y MarketingBudget mediante el índice AlbumsByAlbumTitle:

C#

string connectionString =
$"Data Source=projects/{projectId}/instances/{instanceId}"
+ $"/databases/{databaseId}";
// Create connection to Cloud Spanner.
using (var connection = new SpannerConnection(connectionString))
{
    var cmd = connection.CreateSelectCommand(
        "SELECT AlbumId, AlbumTitle, MarketingBudget FROM Albums@ "
        + "{FORCE_INDEX=AlbumsByAlbumTitle} "
        + $"WHERE AlbumTitle >= @startTitle "
        + $"AND AlbumTitle < @endTitle",
        new SpannerParameterCollection {
            {"startTitle", SpannerDbType.String},
            {"endTitle", SpannerDbType.String} });
    cmd.Parameters["startTitle"].Value = startTitle;
    cmd.Parameters["endTitle"].Value = endTitle;
    using (var reader = await cmd.ExecuteReaderAsync())
    {
        while (await reader.ReadAsync())
        {
            var marketingBudget = reader.IsDBNull(
                reader.GetOrdinal("MarketingBudget")) ?
                "" :
                reader.GetFieldValue<string>("MarketingBudget");
            Console.WriteLine("AlbumId : "
            + reader.GetFieldValue<string>("AlbumId")
            + " AlbumTitle : "
            + reader.GetFieldValue<string>("AlbumTitle")
            + " MarketingBudget : "
            + marketingBudget);
        }
    }
}

Go


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

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

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

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

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

// 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();
}

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($instanceId, $databaseId, $startTitle, $endTitle)
{
    $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

    """
    from google.cloud.spanner_v1.proto import type_pb2

    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': type_pb2.Type(code=type_pb2.STRING),
        'end_title': type_pb2.Type(code=type_pb2.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(
                u'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 en Cloud Spanner y deseas que este use un índice, debes especificar el índice. 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 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.

Cloud Spanner muestra 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 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#

La interfaz de lectura no está disponible en C#.

Go


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

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

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

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

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

// 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();
}

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($instanceId, $databaseId)
{
    $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

Cláusula STORING

De forma opcional, puedes usar la cláusula STORING para almacenar una copia de una columna en el í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:

  • Las consultas de SQL que usan el índice y seleccionan las columnas almacenadas en la cláusula STORING no requieren una unión adicional para la tabla base.
  • Las llamadas de operación de lectura que usan el índice pueden leer las columnas almacenadas en la cláusula STORING.

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 la cláusula STORING en negrita):

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

Con el índice AlbumsByAlbumTitle anterior, Cloud Spanner debe unir el índice con la tabla base y, luego, recuperar la columna de la tabla base. Con el nuevo índice AlbumsByAlbumTitle2, Cloud Spanner lee la columna directamente desde el í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#

La interfaz de lectura no está disponible en C#.

Go


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

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

// "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

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

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

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

// 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();
}

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($instanceId, $databaseId)
{
    $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_soring_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(
                u'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

Indexa valores NULL

Según la configuración predeterminada, Cloud 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 . Por ejemplo, usa esta declaración de consulta de SQL para buscar todos los Singers con un FirstName NULL:

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

Ordena por valores NULL

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

Inhabilita la indexación de valores NULL

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.

Crear un índice NULL_FILTERED evita que Cloud Spanner lo use para algunas consultas. Por ejemplo, Cloud Spanner no usa el índice con esta consulta, porque el índice omite cualquiera de las filas Singers para las que LastName es NULL. Como resultado, el uso del índice evitaría que la consulta mostrara las filas correctas:

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

Con el fin de permitir que Cloud Spanner use el índice, debes reescribir la consulta para que excluya las filas que también se excluyen del índice:

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

Í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. Cloud Spanner aplica esta restricción durante 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 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:

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

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

Dado que Key2 es NULL, y el índice es NULL_FILTERED, las filas no estarán presentes en el índice ExampleIndex. Debido a que no se insertan en el índice, este no los rechazará por violar la unicidad en (Key1, Key2, Col1).

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

Quita un índice

Usa la declaración DROP INDEX para descartar un índice secundario de tu esquema.

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

DROP INDEX SingersByFirstLastName;

Próximos pasos