Indeks sekunder

Dalam database Spanner, Spanner otomatis membuat indeks untuk setiap kunci utama tabel. Misalnya, Anda tidak perlu melakukan apa pun untuk mengindeks kunci utama Singers, karena kunci tersebut diindeks secara otomatis untuk Anda.

Anda juga dapat membuat indeks sekunder untuk kolom lain. Menambahkan indeks sekunder pada kolom akan membuat pencarian data di kolom tersebut menjadi lebih efisien. Misalnya, jika Anda perlu mencari album dengan cepat berdasarkan judul, Anda harus membuat indeks sekunder di AlbumTitle, sehingga Spanner tidak perlu memindai seluruh tabel.

Jika pencarian dalam contoh sebelumnya dilakukan dalam transaksi baca-tulis, pencarian yang lebih efisien juga akan menghindari penahanan kunci di seluruh tabel, yang memungkinkan penyisipan dan pembaruan serentak ke tabel untuk baris di luar rentang pencarian AlbumTitle.

Selain manfaat yang diberikannya untuk pencarian, indeks sekunder juga dapat membantu Spanner menjalankan pemindaian secara lebih efisien, sehingga memungkinkan pemindaian indeks, bukan pemindaian tabel penuh.

Spanner menyimpan data berikut di setiap indeks sekunder:

Seiring waktu, Spanner akan menganalisis tabel Anda untuk memastikan bahwa indeks sekunder Anda digunakan untuk kueri yang sesuai.

Menambahkan indeks sekunder

Waktu yang paling efisien untuk menambahkan indeks sekunder adalah saat Anda membuat tabel. Untuk membuat tabel dan indeksnya secara bersamaan, kirim pernyataan DDL untuk tabel baru dan indeks baru dalam satu permintaan ke Spanner.

Di Spanner, Anda juga dapat menambahkan indeks sekunder baru ke tabel yang ada saat database terus menyalurkan traffic. Seperti perubahan skema lainnya di Spanner, menambahkan indeks ke database yang ada tidak mengharuskan database offline dan tidak mengunci seluruh kolom atau tabel.

Setiap kali indeks baru ditambahkan ke tabel yang ada, Spanner akan otomatis mengisi ulang, atau mengisi, indeks untuk mencerminkan tampilan terbaru data yang diindeks. Spanner mengelola proses pengisian ulang ini untuk Anda, dan proses ini berjalan di latar belakang menggunakan resource node dengan prioritas rendah. Dalam sebagian besar kasus, proses ini tidak dapat dipercepat (misalnya, dengan menambahkan lebih banyak node), dan pengisian ulang tidak memengaruhi performa database secara signifikan.

Pembuatan indeks dapat memerlukan waktu dari beberapa menit hingga beberapa jam. Karena pembuatan indeks adalah update skema, pembuatan indeks terikat oleh batasan performa yang sama seperti update skema lainnya. Waktu yang diperlukan untuk membuat indeks sekunder bergantung pada beberapa faktor:

  • Ukuran set data
  • Kapasitas komputasi instance
  • Beban pada instance

Untuk melihat progres yang dibuat untuk proses pengisian ulang indeks, lihat bagian progres.

Perhatikan bahwa penggunaan kolom stempel waktu commit sebagai bagian pertama dari indeks sekunder dapat membuat hotspot dan mengurangi performa operasi tulis.

Gunakan pernyataan CREATE INDEX untuk menentukan indeks sekunder dalam skema Anda. Berikut beberapa contohnya:

Untuk mengindeks semua Singers dalam database berdasarkan nama depan dan nama belakangnya:

GoogleSQL

CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName);

PostgreSQL

CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName);

Untuk membuat indeks semua Songs dalam database berdasarkan nilai SongName:

GoogleSQL

CREATE INDEX SongsBySongName ON Songs(SongName);

PostgreSQL

CREATE INDEX SongsBySongName ON Songs(SongName);

Untuk mengindeks hanya lagu untuk penyanyi tertentu, gunakan klausa INTERLEAVE IN untuk menyisipkan indeks dalam tabel Singers:

GoogleSQL

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

PostgreSQL

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

Untuk mengindeks hanya lagu di album tertentu:

GoogleSQL

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

PostgreSQL

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

Untuk mengindeks menurut urutan menurun SongName:

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;

Perhatikan bahwa anotasi DESC sebelumnya hanya berlaku untuk SongName. Untuk mengindeks menurut urutan menurun kunci indeks lainnya, beri anotasi dengan DESC juga: SingerId DESC, AlbumId DESC.

Perhatikan juga bahwa PRIMARY_KEY adalah kata khusus untuk sistem dan tidak dapat digunakan sebagai nama indeks. Ini adalah nama yang diberikan ke pseudo-indeks yang dibuat saat tabel dengan spesifikasi PRIMARY KEY dibuat

Untuk mengetahui detail selengkapnya dan praktik terbaik dalam memilih indeks non-sisipan dan indeks sisipan, lihat Opsi indeks dan Menggunakan indeks sisipan pada kolom yang nilainya meningkat atau menurun secara monoton.

Memeriksa progres pengisian ulang indeks

Konsol

  1. Di menu navigasi Spanner, klik tab Operations. Halaman Operasi menampilkan daftar operasi yang sedang berjalan.

  2. Temukan operasi pengisian ulang dalam daftar. Jika masih berjalan, indikator progres di kolom Waktu berakhir akan menampilkan persentase operasi yang sudah selesai, seperti yang ditunjukkan pada gambar berikut:

    Screenshot indikator progres yang menunjukkan 98%

gcloud

Gunakan gcloud spanner operations describe untuk memeriksa progres operasi.

  1. Mendapatkan ID operasi:

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

    Ganti kode berikut:

    • INSTANCE-NAME dengan nama instance Spanner.
    • DATABASE-NAME dengan nama database.

    Catatan penggunaan:

    • Untuk membatasi daftar, tentukan flag --filter. Contoh:

      • --filter="metadata.name:example-db" hanya mencantumkan operasi di database tertentu.
      • --filter="error:*" hanya mencantumkan operasi pencadangan yang gagal.

      Untuk informasi tentang sintaksis filter, lihat filter topik gcloud. Untuk informasi tentang cara memfilter operasi pencadangan, lihat kolom filter di ListBackupOperationsRequest.

    • Flag --type tidak peka huruf besar/kecil.

    Outputnya terlihat mirip dengan yang berikut ini:

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

    Ganti kode berikut:

    • INSTANCE-NAME: Nama instance Spanner.
    • DATABASE-NAME: Nama database Spanner.
    • PROJECT-NAME: Nama project.
    • OPERATION-ID: ID operasi dari operasi yang ingin Anda periksa.

    Bagian progress dalam output menunjukkan persentase operasi yang telah selesai. Output-nya terlihat mirip dengan berikut ini:

    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

Mendapatkan ID operasi:

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

Ganti kode berikut:

  • INSTANCE-NAME dengan nama instance Spanner.
  • DATABASE-NAME dengan nama database.

Sebelum menggunakan salah satu data permintaan, lakukan penggantian berikut:

  • PROJECT-ID: project ID.
  • INSTANCE-ID: ID instance.
  • DATABASE-ID: ID database.
  • OPERATION-ID: ID operasi.

Metode HTTP dan URL:

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

Untuk mengirim permintaan, perluas salah satu opsi berikut:

Anda akan melihat respons JSON seperti berikut:

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

Untuk gcloud dan REST, Anda dapat menemukan progres setiap pernyataan pengisian ulang indeks di bagian progress. Untuk setiap pernyataan dalam array pernyataan, ada kolom yang sesuai dalam array progres. Urutan array progres ini sesuai dengan urutan array pernyataan. Setelah tersedia, kolom startTime, progressPercent, dan endTime akan diisi sebagaimana mestinya. Perhatikan bahwa output tidak menampilkan perkiraan waktu saat progres pengisian ulang akan selesai.

Jika operasi membutuhkan waktu terlalu lama, Anda dapat membatalkannya. Untuk mengetahui informasi selengkapnya, lihat Membatalkan pembuatan indeks.

Skenario saat melihat progres pengisian ulang indeks

Ada berbagai skenario yang dapat Anda temui saat mencoba memeriksa progres pengisian ulang indeks. Pernyataan pembuatan indeks yang memerlukan pengisian ulang indeks adalah bagian dari operasi pembaruan skema, dan mungkin ada beberapa pernyataan yang merupakan bagian dari operasi pembaruan skema.

Skenario pertama adalah yang paling sederhana, yaitu saat pernyataan pembuatan indeks adalah pernyataan pertama dalam operasi pembaruan skema. Karena pernyataan pembuatan indeks adalah pernyataan pertama, pernyataan ini adalah yang pertama diproses dan dijalankan karena urutan eksekusi. Kolom startTime dari pernyataan pembuatan indeks akan langsung diisi dengan waktu mulai operasi pembaruan skema. Selanjutnya, kolom progressPercent pernyataan pembuatan indeks diisi saat progres pengisian ulang indeks lebih dari 0%. Terakhir, kolom endTime diisi setelah pernyataan di-commit.

Skenario kedua adalah saat pernyataan pembuatan indeks bukan pernyataan pertama dalam operasi pembaruan skema. Tidak ada kolom yang terkait dengan pernyataan pembuatan indeks yang akan diisi hingga pernyataan sebelumnya telah di-commit karena urutan eksekusi. Serupa dengan skenario sebelumnya, setelah pernyataan sebelumnya di-commit, kolom startTime dari pernyataan pembuatan indeks akan diisi terlebih dahulu, diikuti dengan kolom progressPercent. Terakhir, kolom endTime akan terisi setelah pernyataan selesai di-commit.

Membatalkan pembuatan indeks

Anda dapat menggunakan Google Cloud CLI untuk membatalkan pembuatan indeks. Untuk mengambil daftar operasi pembaruan skema untuk database Spanner, gunakan perintah gcloud spanner operations list, dan sertakan opsi --filter:

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

Temukan OPERATION_ID untuk operasi yang ingin Anda batalkan, lalu gunakan perintah gcloud spanner operations cancel untuk membatalkannya:

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

Melihat indeks yang ada

Untuk melihat informasi tentang indeks yang ada dalam database, Anda dapat menggunakan konsol Google Cloud atau Google Cloud CLI:

Konsol

  1. Buka halaman Instance Spanner di konsol Google Cloud.

    Buka halaman Instances

  2. Klik nama instance yang ingin Anda lihat.

  3. Di panel kiri, klik database yang ingin Anda lihat, lalu klik tabel yang ingin Anda lihat.

  4. Klik tab Indeks. Konsol Google Cloud menampilkan daftar indeks.

  5. Opsional: Untuk mendapatkan detail tentang indeks, seperti kolom yang disertakan, klik nama indeks.

gcloud

Gunakan perintah gcloud spanner databases ddl describe:

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

CLI gcloud mencetak pernyataan Data Definition Language (DDL) untuk membuat tabel dan indeks database. Pernyataan CREATE INDEX menjelaskan indeks yang ada. Misalnya:

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

Membuat kueri dengan indeks tertentu

Bagian berikut menjelaskan cara menentukan indeks dalam pernyataan SQL dan dengan antarmuka baca untuk Spanner. Contoh di bagian ini mengasumsikan bahwa Anda menambahkan kolom MarketingBudget ke tabel Albums dan membuat indeks bernama 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);

Menentukan indeks dalam pernyataan SQL

Saat Anda menggunakan SQL untuk membuat kueri tabel Spanner, Spanner akan otomatis menggunakan indeks yang cenderung membuat kueri lebih efisien. Akibatnya, Anda tidak perlu menentukan indeks untuk kueri SQL. Namun, untuk kueri yang penting bagi beban kerja Anda, Google menyarankan Anda untuk menggunakan perintah FORCE_INDEX dalam pernyataan SQL untuk performa yang lebih konsisten.

Dalam beberapa kasus, Spanner mungkin memilih indeks yang menyebabkan latensi kueri meningkat. Jika telah mengikuti langkah-langkah pemecahan masalah untuk regresi performa dan mengonfirmasi bahwa sebaiknya coba indeks lain untuk kueri, Anda dapat menentukan indeks sebagai bagian dari kueri.

Untuk menentukan indeks dalam pernyataan SQL, gunakan petunjuk FORCE_INDEX untuk memberikan perintah indeks. Direktif indeks menggunakan sintaksis berikut:

GoogleSQL

FROM MyTable@{FORCE_INDEX=MyTableIndex}

PostgreSQL

FROM MyTable /*@ FORCE_INDEX = MyTableIndex */

Anda juga dapat menggunakan perintah indeks untuk memberi tahu Spanner agar memindai tabel dasar, bukan menggunakan indeks:

GoogleSQL

FROM MyTable@{FORCE_INDEX=_BASE_TABLE}

PostgreSQL

FROM MyTable /*@ FORCE_INDEX = _BASE_TABLE */

Contoh berikut menunjukkan kueri SQL yang menentukan indeks:

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

Perintah indeks dapat memaksa pemroses kueri Spanner untuk membaca kolom tambahan yang diperlukan oleh kueri, tetapi tidak disimpan dalam indeks. Pemroses kueri mengambil kolom ini dengan menggabungkan indeks dan tabel dasar. Untuk menghindari join tambahan ini, gunakan klausa STORING (database dialek GoogleSQL) atau klausa INCLUDE (database dialek PostgreSQL) untuk menyimpan kolom tambahan dalam indeks.

Pada contoh sebelumnya, kolom MarketingBudget tidak disimpan dalam indeks, tetapi kueri SQL memilih kolom ini. Akibatnya, Spanner harus mencari kolom MarketingBudget di tabel dasar, lalu menggabungkannya dengan data dari indeks, untuk menampilkan hasil kueri.

Spanner akan memunculkan error jika perintah indeks memiliki salah satu masalah berikut:

Contoh berikut menunjukkan cara menulis dan menjalankan kueri yang mengambil nilai AlbumId, AlbumTitle, dan MarketingBudget menggunakan indeks 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

Menentukan indeks di antarmuka baca

Saat menggunakan antarmuka baca ke Spanner, dan Anda ingin Spanner menggunakan indeks, Anda harus menentukan indeks. Antarmuka baca tidak memilih indeks secara otomatis.

Selain itu, indeks Anda harus berisi semua data yang muncul dalam hasil kueri, tidak termasuk kolom yang merupakan bagian dari kunci utama. Batasan ini ada karena antarmuka baca tidak mendukung join antara indeks dan tabel dasar. Jika perlu menyertakan kolom lain dalam hasil kueri, Anda memiliki beberapa opsi:

  • Gunakan klausa STORING atau INCLUDE untuk menyimpan kolom tambahan dalam indeks.
  • Buat kueri tanpa menyertakan kolom tambahan, lalu gunakan kunci utama untuk mengirim kueri lain yang membaca kolom tambahan.

Spanner menampilkan nilai dari indeks dalam urutan pengurutan menaik menurut kunci indeks. Untuk mengambil nilai dalam urutan menurun, selesaikan langkah-langkah berikut:

  • Anotasikan kunci indeks dengan DESC. Contoh:

    CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle DESC);
    

    Anotasi DESC berlaku untuk satu kunci indeks. Jika indeks menyertakan lebih dari satu kunci, dan Anda ingin hasil kueri muncul dalam urutan menurun berdasarkan semua kunci, sertakan anotasi DESC untuk setiap kunci.

  • Jika pembacaan menentukan rentang kunci, pastikan rentang kunci juga dalam urutan menurun. Dengan kata lain, nilai kunci awal harus lebih besar dari nilai kunci akhir.

Contoh berikut menunjukkan cara mengambil nilai AlbumId dan AlbumTitle menggunakan indeks 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

Membuat indeks untuk pemindaian khusus indeks

Secara opsional, Anda dapat menggunakan klausa STORING (untuk database dialek GoogleSQL) atau klausa INCLUDE (untuk database dialek PostgreSQL) untuk menyimpan salinan kolom dalam indeks. Jenis indeks ini memberikan keuntungan untuk kueri dan panggilan baca yang menggunakan indeks, dengan biaya penggunaan penyimpanan tambahan:

  • Kueri SQL yang menggunakan indeks dan memilih kolom yang disimpan dalam klausa STORING atau INCLUDE tidak memerlukan join tambahan ke tabel dasar.
  • Panggilan read() yang menggunakan indeks dapat membaca kolom yang disimpan oleh klausa STORING/INCLUDE.

Misalnya, Anda membuat versi alternatif AlbumsByAlbumTitle yang menyimpan salinan kolom MarketingBudget dalam indeks (perhatikan klausa STORING atau INCLUDE yang dicetak tebal):

GoogleSQL

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

PostgreSQL

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

Dengan indeks AlbumsByAlbumTitle lama, Spanner harus menggabungkan indeks dengan tabel dasar, lalu mengambil kolom dari tabel dasar. Dengan indeks AlbumsByAlbumTitle2 baru, Spanner membaca kolom langsung dari indeks, yang lebih efisien.

Jika Anda menggunakan antarmuka baca, bukan SQL, indeks AlbumsByAlbumTitle2 baru juga memungkinkan Anda membaca kolom MarketingBudget secara langsung:

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

Mengubah indeks

Anda dapat menggunakan pernyataan ALTER INDEX untuk menambahkan kolom tambahan ke indeks yang ada atau menghapus kolom. Tindakan ini dapat memperbarui daftar kolom yang ditentukan oleh klausa STORING (database dialek GoogleSQL) atau klausa INCLUDE (database dialek PostgreSQL) saat Anda membuat indeks. Anda tidak dapat menggunakan pernyataan ini untuk menambahkan kolom ke atau menghapus kolom dari kunci indeks. Misalnya, daripada membuat indeks AlbumsByAlbumTitle2 baru, Anda dapat menggunakan ALTER INDEX untuk menambahkan kolom ke AlbumsByAlbumTitle, seperti yang ditunjukkan dalam contoh berikut:

GoogleSQL

ALTER INDEX AlbumsByAlbumTitle ADD STORED COLUMN MarketingBudget

PostgreSQL

ALTER INDEX AlbumsByAlbumTitle ADD INCLUDE COLUMN MarketingBudget

Saat Anda menambahkan kolom baru ke indeks yang ada, Spanner akan menggunakan proses pengisian ulang latar belakang. Saat pengisian ulang sedang berlangsung, kolom dalam indeks tidak dapat dibaca, sehingga Anda mungkin tidak mendapatkan peningkatan performa yang diharapkan. Anda dapat menggunakan perintah gcloud spanner operations untuk mencantumkan operasi yang berjalan lama dan melihat statusnya. Untuk informasi selengkapnya, lihat menjelaskan operasi.

Anda juga dapat menggunakan cancel operation untuk membatalkan operasi yang sedang berjalan.

Setelah pengisian ulang selesai, Spanner akan menambahkan kolom ke indeks. Seiring Indeks menjadi lebih besar, hal ini dapat memperlambat kueri yang menggunakan indeks.

Contoh berikut menunjukkan cara menghapus kolom dari indeks:

GoogleSQL

ALTER INDEX AlbumsByAlbumTitle DROP STORED COLUMN MarketingBudget

PostgreSQL

ALTER INDEX AlbumsByAlbumTitle DROP INCLUDE COLUMN MarketingBudget

Indeks nilai NULL

Secara default, Spanner mengindeks nilai NULL. Misalnya, ingat kembali definisi indeks SingersByFirstLastName pada tabel Singers:

CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName);

Semua baris Singers diindeks meskipun FirstName atau LastName, atau keduanya, adalah NULL.

Diagram menunjukkan baris yang dihilangkan dari indeks yang difilter NULL.

Saat nilai NULL diindeks, Anda dapat menjalankan kueri SQL yang efisien dan membaca data yang menyertakan nilai NULL. Misalnya, gunakan pernyataan kueri SQL ini untuk menemukan semua Singers dengan NULL FirstName:

GoogleSQL

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

PostgreSQL

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

Urutan pengurutan untuk nilai NULL

Spanner mengurutkan NULL sebagai nilai terkecil untuk jenis tertentu. Untuk kolom dalam urutan menaik (ASC), nilai NULL diurutkan terlebih dahulu. Untuk kolom dalam urutan menurun (DESC), nilai NULL diurutkan terakhir.

Menonaktifkan pengindeksan nilai NULL

GoogleSQL

Untuk menonaktifkan pengindeksan null, tambahkan kata kunci NULL_FILTERED ke definisi indeks. Indeks NULL_FILTERED sangat berguna untuk mengindeks kolom yang jarang, dengan sebagian besar baris berisi nilai NULL. Dalam hal ini, indeks NULL_FILTERED dapat jauh lebih kecil dan lebih efisien untuk dikelola daripada indeks normal yang menyertakan nilai NULL.

Berikut adalah definisi alternatif SingersByFirstLastName yang tidak mengalihkan nilai NULL:

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

Kata kunci NULL_FILTERED berlaku untuk semua kolom kunci indeks. Anda tidak dapat menentukan pemfilteran NULL per kolom.

PostgreSQL

Untuk memfilter baris dengan nilai null di satu atau beberapa kolom yang diindeks, gunakan predikat WHERE COLUMN IS NOT NULL. Indeks yang difilter null sangat berguna untuk mengindeks kolom yang jarang, dengan sebagian besar baris berisi nilai NULL. Dalam hal ini, indeks yang difilter null dapat jauh lebih kecil dan lebih efisien untuk dikelola daripada indeks normal yang menyertakan nilai NULL.

Berikut adalah definisi alternatif SingersByFirstLastName yang tidak mengalihkan nilai NULL:

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

Memfilter nilai NULL akan mencegah Spanner menggunakannya untuk beberapa kueri. Misalnya, Spanner tidak menggunakan indeks untuk kueri ini, karena indeks menghilangkan baris Singers yang LastName-nya adalah NULL; sebagai akibatnya, penggunaan indeks akan mencegah kueri menampilkan baris yang benar:

GoogleSQL

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

PostgreSQL

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

Agar Spanner dapat menggunakan indeks, Anda harus menulis ulang kueri sehingga mengecualikan baris yang juga dikecualikan dari indeks:

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;

Mengindeks kolom proto

Gunakan kolom yang dihasilkan untuk mengindeks kolom dalam buffering protokol yang disimpan di kolom PROTO, selama kolom yang diindeks menggunakan jenis data primitif atau ENUM.

Jika menentukan indeks pada kolom pesan protokol, Anda tidak dapat mengubah atau menghapus kolom tersebut dari skema proto. Untuk mengetahui informasi selengkapnya, lihat Pembaruan pada skema yang berisi indeks pada kolom proto.

Berikut adalah contoh tabel Singers dengan kolom pesan proto SingerInfo. Untuk menentukan indeks di kolom nationality dari PROTO, Anda perlu membuat kolom yang dihasilkan dan disimpan:

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

Proto ini memiliki definisi jenis proto googlesql.example.SingerInfo berikut:

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

Kemudian, tentukan indeks pada kolom nationality proto:

GoogleSQL

CREATE INDEX SingersByNationality ON Singers(SingerNationality);

Kueri SQL berikut membaca data menggunakan indeks sebelumnya:

GoogleSQL

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

Catatan:

  • Gunakan petunjuk indeks untuk mengakses indeks di kolom buffer protokol.
  • Anda tidak dapat membuat indeks pada kolom buffering protokol berulang.

Pembaruan pada skema yang berisi indeks pada kolom proto

Jika menentukan indeks pada kolom pesan protokol, Anda tidak dapat mengubah atau menghapus kolom tersebut dari skema proto. Hal ini karena setelah Anda menentukan indeks, pemeriksaan jenis dilakukan setiap kali skema diperbarui. Spanner mengambil informasi jenis untuk semua kolom di jalur yang digunakan dalam definisi indeks.

Indeks unik

Indeks dapat dideklarasikan UNIQUE. Indeks UNIQUE menambahkan batasan ke data yang diindeks yang melarang entri duplikat untuk kunci indeks tertentu. Batasan ini diterapkan oleh Spanner pada waktu commit transaksi. Secara khusus, setiap transaksi yang akan menyebabkan beberapa entri indeks untuk kunci yang sama akan gagal di-commit.

Jika tabel berisi data non-UNIQUE, upaya untuk membuat indeks UNIQUE di dalamnya akan gagal.

Catatan tentang indeks UNIQUE NULL_FILTERED

Indeks UNIQUE NULL_FILTERED tidak menerapkan keunikan kunci indeks jika setidaknya salah satu bagian kunci indeks adalah NULL.

Misalnya, Anda membuat tabel dan indeks berikut:

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;

Dua baris berikut di ExampleTable memiliki nilai yang sama untuk kunci indeks sekunder Key1, Key2, dan Col1:

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

Karena Key2 adalah NULL dan indeks difilter null, baris tidak akan ada dalam indeks ExampleIndex. Karena tidak disisipkan ke dalam indeks, indeks tidak akan menolaknya karena melanggar keunikan di (Key1, Key2, Col1).

Jika Anda ingin indeks menerapkan keunikan nilai tuple (Key1, Key2, Col1), Anda harus menganotasi Key2 dengan NOT NULL dalam definisi tabel atau membuat indeks tanpa memfilter null.

Menghapus indeks

Gunakan pernyataan DROP INDEX untuk menghapus indeks sekunder dari skema Anda.

Untuk menghapus indeks bernama SingersByFirstLastName:

DROP INDEX SingersByFirstLastName;

Indeks untuk pemindaian yang lebih cepat

Saat Spanner perlu melakukan pemindaian tabel (bukan pencarian terindek) untuk mengambil nilai dari satu atau beberapa kolom, Anda dapat menerima hasil yang lebih cepat jika indeks ada untuk kolom tersebut, dan dalam urutan yang ditentukan oleh kueri. Jika Anda sering melakukan kueri yang memerlukan pemindaian, sebaiknya buat indeks sekunder untuk membantu pemindaian ini berjalan dengan lebih efisien.

Secara khusus, jika Anda memerlukan Spanner untuk sering memindai kunci utama tabel atau indeks lainnya dalam urutan terbalik, Anda dapat meningkatkan efisiensinya melalui indeks sekunder yang membuat urutan yang dipilih eksplisit.

Misalnya, kueri berikut selalu menampilkan hasil yang cepat, meskipun Spanner perlu memindai Songs untuk menemukan nilai SongId terendah:

SELECT SongId FROM Songs LIMIT 1;

SongId adalah kunci utama tabel, yang disimpan (seperti semua kunci utama) dalam urutan menaik. Spanner dapat memindai indeks kunci tersebut dan menemukan hasil pertama dengan cepat.

Namun, tanpa bantuan indeks sekunder, kueri berikut tidak akan ditampilkan dengan cepat, terutama jika Songs menyimpan banyak data:

SELECT SongId FROM Songs ORDER BY SongId DESC LIMIT 1;

Meskipun SongId adalah kunci utama tabel, Spanner tidak memiliki cara untuk mengambil nilai tertinggi kolom tanpa menggunakan pemindaian tabel penuh.

Menambahkan indeks berikut akan memungkinkan kueri ini ditampilkan lebih cepat:

CREATE INDEX SongIdDesc On Songs(SongId DESC);

Dengan indeks ini, Spanner akan menggunakannya untuk menampilkan hasil kueri kedua dengan lebih cepat.

Langkah selanjutnya