Indeks sekunder

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

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

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

Selain manfaat yang diberikan pada pencarian, indeks sekunder juga dapat membantu Spanner mengeksekusi pemindaian dengan 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 digunakan untuk kueri yang sesuai.

Menambahkan indeks sekunder

Waktu 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 sudah ada sementara database terus menyalurkan traffic. Seperti perubahan skema lainnya di Spanner, menambahkan indeks ke database yang ada tidak mengharuskan database tersebut offline dan tidak mengunci seluruh kolom atau tabel.

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

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

  • Ukuran {i>dataset<i}
  • Kapasitas komputasi instance
  • Beban pada instance

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

Perhatikan bahwa menggunakan kolom stempel waktu commit sebagai bagian pertama dari indeks sekunder dapat membuat hotspot dan mengurangi performa penulisan.

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

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

GoogleSQL

CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName);

PostgreSQL

CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName);

Untuk membuat indeks semua Songs dalam database dengan nilai SongName:

GoogleSQL

CREATE INDEX SongsBySongName ON Songs(SongName);

PostgreSQL

CREATE INDEX SongsBySongName ON Songs(SongName);

Untuk mengindeks lagu saja dari 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 lagu di album tertentu saja:

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 dengan urutan menurun dari 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;

Perlu diperhatikan bahwa anotasi DESC di atas hanya berlaku untuk SongName. Untuk mengindeks dengan urutan menurun kunci indeks lainnya, anotasikan juga dengan DESC: SingerId DESC, AlbumId DESC.

Perhatikan juga bahwa PRIMARY_KEY adalah kata yang dicadangkan dan tidak dapat digunakan sebagai nama indeks. Ini adalah nama yang diberikan pada indeks pseudo yang dibuat ketika tabel dengan spesifikasi PRIMARY KEY dibuat

Untuk mengetahui detail selengkapnya dan praktik terbaik dalam memilih indeks non-disisipkan 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 Operations 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 selesai, seperti yang ditunjukkan pada gambar berikut:

    Screenshot indikator progres yang menampilkan 98%

gcloud

Gunakan gcloud spanner operations describe untuk memeriksa progres operasi.

  1. Dapatkan 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 pada database tertentu.
      • --filter="error:*" hanya mencantumkan operasi pencadangan yang gagal.

      Untuk mengetahui informasi tentang sintaksis filter, lihat gcloud topic filters. Untuk mengetahui 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 pada output menunjukkan persentase operasi yang selesai. Outputnya 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

Dapatkan 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 dengan sesuai. Perhatikan bahwa output tidak menunjukkan perkiraan waktu kapan progres pengisian ulang akan selesai.

Jika operasi berlangsung 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 update skema, dan mungkin ada beberapa pernyataan yang merupakan bagian dari operasi update skema.

Skenario pertama adalah yang paling sederhana, yaitu ketika pernyataan pembuatan indeks adalah pernyataan pertama dalam operasi pembaruan skema. Karena pernyataan pembuatan indeks adalah pernyataan pertama, pernyataan tersebut adalah yang pertama diproses dan dieksekusi karena urutan eksekusi. Segera, kolom startTime dari pernyataan pembuatan indeks akan segera diisi dengan waktu mulai operasi pembaruan skema. Selanjutnya, kolom progressPercent pernyataan pembuatan indeks diisi saat progres pengisian ulang indeks di atas 0%. Terakhir, kolom endTime akan terisi 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 di-commit karena urutan eksekusi. Serupa dengan skenario di atas, setelah pernyataan sebelumnya di-commit, kolom startTime dalam pernyataan pembuatan indeks akan diisi terlebih dahulu, diikuti dengan kolom progressPercent. Terakhir, kolom endTime akan diisi setelah pernyataan selesai di-commit.

Batalkan 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

Lihat 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 akan menampilkan daftar indeks.

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

gcloud

Gunakan perintah gcloud spanner databases ddl describe:

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

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

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 yang disebut 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 secara otomatis akan menggunakan indeks apa pun yang cenderung membuat kueri lebih efisien. Dengan demikian, Anda tidak perlu menentukan indeks untuk kueri SQL. Namun, untuk kueri yang sangat penting bagi beban kerja Anda, Google menyarankan agar menggunakan perintah FORCE_INDEX dalam pernyataan SQL Anda untuk performa yang lebih konsisten.

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

Untuk menentukan indeks dalam pernyataan SQL, gunakan petunjuk FORCE_INDEX untuk memberikan perintah indeks. Perintah 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 tersebut dengan menggabungkan indeks dan tabel dasar. Untuk menghindari gabungan tambahan ini, gunakan klausa STORING (database dialek GoogleSQL) atau klausa INCLUDE (database dialek PostgreSQL) untuk menyimpan kolom tambahan dalam indeks.

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

Spanner akan menimbulkan 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 untuk Spanner, dan ingin Spanner menggunakan indeks, Anda harus menentukan indeksnya. 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 gabungan antara indeks dan tabel dasar. Jika Anda perlu menyertakan kolom lain dalam hasil kueri, Anda memiliki beberapa pilihan:

  • 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 tata urutan menaik berdasarkan 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 tersebut 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

Buat indeks untuk pemindaian khusus indeks

Anda juga 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 menggunakan indeks, dengan mengorbankan penyimpanan tambahan:

  • Kueri SQL yang menggunakan indeks dan kolom tertentu yang disimpan dalam klausa STORING atau INCLUDE tidak memerlukan gabungan 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 di indeks (perhatikan klausa STORING atau INCLUDE dalam cetak 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 yang baru, Spanner membaca kolom langsung dari indeks, sehingga lebih efisien.

Jika Anda menggunakan antarmuka baca, bukan SQL, indeks AlbumsByAlbumTitle2 yang 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 dalam indeks yang ada atau kolom lepas. 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 baru AlbumsByAlbumTitle2, 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 dalam 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 menampilkan daftar operasi yang berjalan lama dan melihat statusnya. Untuk informasi selengkapnya, lihat menjelaskan operasi.

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

Setelah pengisian ulang selesai, Spanner akan menambahkan kolom ke indeks. Seiring dengan membesarnya Indeks, hal ini mungkin memperlambat kueri yang menggunakan indeks tersebut.

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 NULL nilai. 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 penyortiran untuk nilai NULL

Spanner mengurutkan NULL sebagai nilai terkecil untuk jenis tertentu. Untuk kolom dalam urutan menaik (ASC), nilai NULL akan diurutkan terlebih dahulu. Untuk kolom dalam urutan menurun (DESC), nilai NULL akan 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 sparse, yang sebagian besar barisnya berisi nilai NULL. Dalam kasus ini, indeks NULL_FILTERED dapat jauh lebih kecil dan lebih efisien untuk dikelola daripada indeks normal yang menyertakan nilai NULL.

Berikut adalah definisi alternatif dari SingersByFirstLastName yang tidak mengindeks 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 terindeks, gunakan predikat WHERE COLUMN IS NOT NULL. Indeks yang difilter null sangat berguna untuk mengindeks kolom sparse, yang mana sebagian besar baris berisi nilai NULL. Dalam kasus ini, indeks yang difilter dengan null dapat jauh lebih kecil dan lebih efisien untuk dikelola daripada indeks normal yang menyertakan nilai NULL.

Berikut adalah definisi alternatif dari SingersByFirstLastName yang tidak mengindeks 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 semua baris Singers yang LastName-nya adalah NULL; 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 agar Spanner tidak menyertakan 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;

Indeks unik

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

Jika tabel berisi data non-UNIQUE di dalamnya pada awalnya, upaya untuk membuat indeks UNIQUE pada tabel tersebut 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.

Sebagai contoh, anggaplah 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 di ExampleTable berikut 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 oleh null, baris tidak akan ada dalam indeks ExampleIndex. Karena nilai tersebut tidak disisipkan ke dalam indeks, indeks tidak akan menolaknya karena melanggar keunikan pada (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.

Meletakkan 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 yang diindeks) untuk mengambil nilai dari satu atau beberapa kolom, Anda dapat menerima hasil yang lebih cepat jika ada indeks untuk kolom tersebut, dan dalam urutan yang ditentukan oleh kueri. Jika Anda sering melakukan kueri yang memerlukan pemindaian, pertimbangkan untuk membuat indeks sekunder untuk membantu pemindaian ini terjadi 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 diinginkan menjadi eksplisit.

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

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 menampilkan lebih cepat:

CREATE INDEX SongIdDesc On Songs(SongId DESC);

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

Langkah selanjutnya