Mulai menggunakan Spanner di C#


Tujuan

Tutorial ini memandu Anda melakukan langkah-langkah berikut menggunakan library klien Spanner untuk C#:

  • Membuat instance dan database Spanner.
  • Menulis, membaca, dan mengeksekusi kueri SQL pada data dalam {i>database<i}.
  • Perbarui skema database.
  • Memperbarui data menggunakan transaksi baca-tulis.
  • Tambahkan indeks sekunder ke database.
  • Menggunakan indeks untuk membaca dan mengeksekusi kueri SQL pada data.
  • Mengambil data menggunakan transaksi hanya baca.

Biaya

Tutorial ini menggunakan Spanner, yang merupakan komponen Google Cloud yang dapat ditagih. Untuk mengetahui informasi tentang biaya penggunaan Spanner, lihat Harga.

Sebelum memulai

Selesaikan langkah-langkah yang dijelaskan dalam Penyiapan, yang mencakup pembuatan dan penetapan project Google Cloud default, mengaktifkan penagihan, mengaktifkan Cloud Spanner API, dan menyiapkan OAuth 2.0 untuk mendapatkan kredensial autentikasi agar dapat menggunakan Cloud Spanner API.

Secara khusus, pastikan Anda menjalankan gcloud auth application-default login untuk menyiapkan lingkungan pengembangan lokal dengan kredensial autentikasi.

Menyiapkan lingkungan C# lokal

  1. Tetapkan variabel lingkungan GOOGLE_PROJECT_ID ke ID project Google Cloud Anda.

    1. Pertama, setel GOOGLE_PROJECT_ID untuk sesi PowerShell saat ini:

      $env:GOOGLE_PROJECT_ID = "MY_PROJECT_ID"
    2. Kemudian, setel GOOGLE_PROJECT_ID untuk semua proses yang dibuat setelah perintah ini:

      [Environment]::SetEnvironmentVariable("GOOGLE_PROJECT_ID", "MY_PROJECT_ID", "User")
  2. Download kredensial.

    1. Buka halaman Credentials di Konsol Google Cloud.

      Buka halaman Kredensial

    2. Klik Create credentials dan pilih Service account key.

    3. Di bagian "Akun layanan", pilih Akun layanan default Compute Engine, dan biarkan JSON dipilih di bagian "Jenis kunci". Klik Create. Komputer Anda akan mendownload file JSON.

  3. Siapkan kredensial. Untuk file bernama FILENAME.json di direktori Download CURRENT_USER yang terletak di drive C, jalankan perintah berikut untuk menetapkan GOOGLE_APPLICATION_CREDENTIALS agar mengarah ke kunci JSON:

    1. Pertama, guna menetapkan GOOGLE_APPLICATION_CREDENTIALS untuk sesi PowerShell ini:

      $env:GOOGLE_APPLICATION_CREDENTIALS = "C:\Users\CURRENT_USER\Downloads\FILENAME.json"
    2. Kemudian, untuk menyetel GOOGLE_APPLICATION_CREDENTIALS bagi semua proses yang dibuat setelah perintah ini:

      [Environment]::SetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS", "C:\Users\CURRENT_USER\Downloads\FILENAME.json", "User")
  4. Clone repositori aplikasi contoh ke komputer lokal Anda:

    git clone https://github.com/GoogleCloudPlatform/dotnet-docs-samples
    

    Atau, Anda dapat mendownload contoh sebagai file zip dan mengekstraknya.

  5. Buka Spanner.sln yang terletak di direktori dotnet-docs-samples\spanner\api dari repositori yang didownload, dengan Visual Studio 2017 atau yang lebih baru, lalu build.

  6. Ubah ke direktori dalam repositori yang didownload yang berisi aplikasi terkompilasi. Contoh:

    cd dotnet-docs-samples\spanner\api\Spanner
    

Membuat instance

Saat pertama kali menggunakan Spanner, Anda harus membuat instance, yang merupakan alokasi resource yang digunakan oleh database Spanner. Saat membuat instance, Anda memilih konfigurasi instance, yang menentukan tempat data disimpan, dan juga jumlah node yang akan digunakan, yang menentukan jumlah penayangan dan resource penyimpanan dalam instance Anda.

Jalankan perintah berikut untuk membuat instance Spanner di region us-central1 dengan 1 node:

gcloud spanner instances create test-instance --config=regional-us-central1 `
    --description="Test Instance" --nodes=1

Perhatikan bahwa tindakan ini akan membuat instance dengan karakteristik berikut:

  • ID Instance test-instance
  • Nama tampilan Test Instance
  • Konfigurasi instance regional-us-central1 (Konfigurasi regional menyimpan data di satu region, sedangkan konfigurasi multi-region mendistribusikan data di beberapa region. Untuk mengetahui informasi lebih lanjut, lihat artikel Tentang instance.)
  • Jumlah node 1 (node_count sesuai dengan jumlah resource penyajian dan penyimpanan yang tersedia untuk database dalam instance. Pelajari lebih lanjut di Node dan unit pemrosesan.)

Anda akan melihat:

Creating instance...done.

Mencari contoh file

Repositori sampel berisi contoh yang menunjukkan cara menggunakan Spanner dengan C#.

Lihat repositori GitHub Spanner .NET, yang menunjukkan cara membuat database dan mengubah skema database. Data tersebut menggunakan skema contoh yang ditampilkan di halaman Model data dan skema.

Buat database

Buat database yang disebut example-db dalam instance bernama test-instance dengan menjalankan baris berikut di command line.

dotnet run createSampleDatabase $env:GOOGLE_PROJECT_ID test-instance example-db

Anda akan melihat:

Created sample database example-db on instance test-instance

Kode berikut membuat database dan dua tabel dalam database.


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

public class CreateDatabaseAsyncSample
{
    public async Task CreateDatabaseAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}";

        using var connection = new SpannerConnection(connectionString);
        var createDatabase = $"CREATE DATABASE `{databaseId}`";
        // Define create table statement for table #1.
        var createSingersTable =
        @"CREATE TABLE Singers (
                     SingerId INT64 NOT NULL,
                     FirstName STRING(1024),
                     LastName STRING(1024),
                     ComposerInfo BYTES(MAX),
                     FullName STRING(2048) AS (ARRAY_TO_STRING([FirstName, LastName], "" "")) STORED
                 ) PRIMARY KEY (SingerId)";
        // Define create table statement for table #2.
        var createAlbumsTable =
        @"CREATE TABLE Albums (
                     SingerId INT64 NOT NULL,
                     AlbumId INT64 NOT NULL,
                     AlbumTitle STRING(MAX)
                 ) PRIMARY KEY (SingerId, AlbumId),
                 INTERLEAVE IN PARENT Singers ON DELETE CASCADE";

        using var createDbCommand = connection.CreateDdlCommand(createDatabase, createSingersTable, createAlbumsTable);
        await createDbCommand.ExecuteNonQueryAsync();
    }
}

Langkah selanjutnya adalah menulis data ke database Anda.

Membuat klien database

Sebelum dapat melakukan pembacaan atau penulisan, Anda harus membuat Spanner​Connection:


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

namespace GoogleCloudSamples.Spanner
{
    public class QuickStart
    {
        static async Task MainAsync()
        {
            string projectId = "YOUR-PROJECT-ID";
            string instanceId = "my-instance";
            string databaseId = "my-database";
            string connectionString =
                $"Data Source=projects/{projectId}/instances/{instanceId}/"
                + $"databases/{databaseId}";
            // Create connection to Cloud Spanner.
            using (var connection = new SpannerConnection(connectionString))
            {
                // Execute a simple SQL statement.
                var cmd = connection.CreateSelectCommand(
                    @"SELECT ""Hello World"" as test");
                using (var reader = await cmd.ExecuteReaderAsync())
                {
                    while (await reader.ReadAsync())
                    {
                        Console.WriteLine(
                            reader.GetFieldValue<string>("test"));
                    }
                }
            }
        }
        public static void Main(string[] args)
        {
            MainAsync().Wait();
        }
    }
}

Anda dapat menganggap Spanner​Connection sebagai koneksi database: semua interaksi Anda dengan Spanner harus melalui Spanner​Connection.

Baca selengkapnya dalam referensi Spanner​Connection.

Menulis data dengan DML

Anda dapat menyisipkan data menggunakan Data Manipulation Language (DML) dalam transaksi baca-tulis.

Anda menggunakan metode ExecuteNonQueryAsync() untuk mengeksekusi pernyataan DML.


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

public class WriteUsingDmlCoreAsyncSample
{
    public async Task<int> WriteUsingDmlCoreAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";

        using var connection = new SpannerConnection(connectionString);
        await connection.OpenAsync();

        SpannerCommand cmd = connection.CreateDmlCommand(
            "INSERT Singers (SingerId, FirstName, LastName) VALUES "
               + "(12, 'Melissa', 'Garcia'), "
               + "(13, 'Russell', 'Morales'), "
               + "(14, 'Jacqueline', 'Long'), "
               + "(15, 'Dylan', 'Shaw')");
        int rowCount = await cmd.ExecuteNonQueryAsync();

        Console.WriteLine($"{rowCount} row(s) inserted...");
        return rowCount;
    }
}

Jalankan contoh menggunakan argumen writeUsingDml.

dotnet run writeUsingDml $env:GOOGLE_PROJECT_ID test-instance example-db

Anda akan melihat:

4 row(s) inserted...

Menulis data dengan mutasi

Anda juga dapat menyisipkan data menggunakan mutasi.

Anda dapat menyisipkan data menggunakan metode connection.CreateInsertCommand(), yang membuat SpannerCommand baru untuk menyisipkan baris ke dalam tabel. Metode SpannerCommand.ExecuteNonQueryAsync() menambahkan baris baru ke tabel.

Kode ini menunjukkan cara menyisipkan data menggunakan mutasi:


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

public class InsertDataAsyncSample
{
    public class Singer
    {
        public int SingerId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

    public class Album
    {
        public int SingerId { get; set; }
        public int AlbumId { get; set; }
        public string AlbumTitle { get; set; }
    }

    public async Task InsertDataAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";
        List<Singer> singers = new List<Singer>
        {
            new Singer { SingerId = 1, FirstName = "Marc", LastName = "Richards" },
            new Singer { SingerId = 2, FirstName = "Catalina", LastName = "Smith" },
            new Singer { SingerId = 3, FirstName = "Alice", LastName = "Trentor" },
            new Singer { SingerId = 4, FirstName = "Lea", LastName = "Martin" },
            new Singer { SingerId = 5, FirstName = "David", LastName = "Lomond" },
        };
        List<Album> albums = new List<Album>
        {
            new Album { SingerId = 1, AlbumId = 1, AlbumTitle = "Total Junk" },
            new Album { SingerId = 1, AlbumId = 2, AlbumTitle = "Go, Go, Go" },
            new Album { SingerId = 2, AlbumId = 1, AlbumTitle = "Green" },
            new Album { SingerId = 2, AlbumId = 2, AlbumTitle = "Forever Hold your Peace" },
            new Album { SingerId = 2, AlbumId = 3, AlbumTitle = "Terrified" },
        };

        // Create connection to Cloud Spanner.
        using var connection = new SpannerConnection(connectionString);
        await connection.OpenAsync();

        await connection.RunWithRetriableTransactionAsync(async transaction =>
        {
            await Task.WhenAll(singers.Select(singer =>
            {
                // Insert rows into the Singers table.
                using var cmd = connection.CreateInsertCommand("Singers", new SpannerParameterCollection
                {
                        { "SingerId", SpannerDbType.Int64, singer.SingerId },
                        { "FirstName", SpannerDbType.String, singer.FirstName },
                        { "LastName", SpannerDbType.String, singer.LastName }
                });
                cmd.Transaction = transaction;
                return cmd.ExecuteNonQueryAsync();
            }));

            await Task.WhenAll(albums.Select(album =>
            {
                // Insert rows into the Albums table.
                using var cmd = connection.CreateInsertCommand("Albums", new SpannerParameterCollection
                {
                        { "SingerId", SpannerDbType.Int64, album.SingerId },
                        { "AlbumId", SpannerDbType.Int64, album.AlbumId },
                        { "AlbumTitle", SpannerDbType.String,album.AlbumTitle }
                });
                cmd.Transaction = transaction;
                return cmd.ExecuteNonQueryAsync();
            }));
        });
        Console.WriteLine("Data inserted.");
    }
}

Jalankan contoh menggunakan argumen insertSampleData.

dotnet run insertSampleData $env:GOOGLE_PROJECT_ID test-instance example-db

Anda akan melihat:

Inserted data.

Membuat kueri data menggunakan SQL

Spanner mendukung antarmuka SQL untuk membaca data, yang dapat Anda akses dari command line menggunakan Google Cloud CLI atau secara terprogram menggunakan library klien Spanner untuk C#.

Pada command line

Jalankan pernyataan SQL berikut untuk membaca nilai semua kolom dari tabel Albums:

gcloud spanner databaseexecute-sql example-db --instance=test-instance ` --sql='SELECT SingerId, AlbumId, AlbumTitle FROM Album'

Hasilnya seharusnya:

SingerId AlbumId AlbumTitle
1        1       Total Junk
1        2       Go, Go, Go
2        1       Green
2        2       Forever Hold Your Peace
2        3       Terrified

Menggunakan library klien Spanner untuk C#

Selain menjalankan pernyataan SQL di command line, Anda dapat mengeluarkan pernyataan SQL yang sama secara terprogram menggunakan library klien Spanner untuk C#.

Gunakan ExecuteReaderAsync() untuk menjalankan kueri SQL.


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

public class QuerySampleDataAsyncSample
{
    public class Album
    {
        public int SingerId { get; set; }
        public int AlbumId { get; set; }
        public string AlbumTitle { get; set; }
    }

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

        var albums = new List<Album>();
        using var connection = new SpannerConnection(connectionString);
        using var cmd = connection.CreateSelectCommand("SELECT SingerId, AlbumId, AlbumTitle FROM Albums");

        using var reader = await cmd.ExecuteReaderAsync();
        while (await reader.ReadAsync())
        {
            albums.Add(new Album
            {
                AlbumId = reader.GetFieldValue<int>("AlbumId"),
                SingerId = reader.GetFieldValue<int>("SingerId"),
                AlbumTitle = reader.GetFieldValue<string>("AlbumTitle")
            });
        }
        return albums;
    }
}

Berikut cara mengeluarkan kueri dan mengakses data:

dotnet run querySampleData $env:GOOGLE_PROJECT_ID test-instance example-db

Anda akan melihat hasil berikut:

SingerId: 1 AlbumId: 1 AlbumTitle: Total Junk
SingerId: 1 AlbumId: 2 AlbumTitle: Go, Go, Go
SingerId: 2 AlbumId: 1 AlbumTitle: Green
SingerId: 2 AlbumId: 2 AlbumTitle: Forever Hold your Peace
SingerId: 2 AlbumId: 3 AlbumTitle: Terrified

Kueri menggunakan parameter SQL

Jika aplikasi Anda memiliki kueri yang sering dijalankan, Anda dapat meningkatkan performanya dengan membuat parameter pada kueri tersebut. Kueri parametrik yang dihasilkan dapat disimpan dalam cache dan digunakan kembali, sehingga mengurangi biaya kompilasi. Untuk informasi selengkapnya, lihat Menggunakan parameter kueri untuk mempercepat kueri yang sering dijalankan.

Berikut adalah contoh penggunaan parameter dalam klausa WHERE untuk mengueri data yang berisi nilai tertentu untuk LastName.


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

public class QueryWithParameterAsyncSample
{
    public class Singer
    {
        public int SingerId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

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

        using var connection = new SpannerConnection(connectionString);
        using var cmd = connection.CreateSelectCommand(
            $"SELECT SingerId, FirstName, LastName FROM Singers WHERE LastName = @lastName",
            new SpannerParameterCollection { { "lastName", SpannerDbType.String, "Garcia" } });

        var singers = new List<Singer>();
        using var reader = await cmd.ExecuteReaderAsync();
        while (await reader.ReadAsync())
        {
            singers.Add(new Singer
            {
                SingerId = reader.GetFieldValue<int>("SingerId"),
                FirstName = reader.GetFieldValue<string>("FirstName"),
                LastName = reader.GetFieldValue<string>("LastName")
            });
        }
        return singers;
    }
}

Berikut cara mengeluarkan kueri dengan parameter dan mengakses datanya:

dotnet run queryWithParameter $env:GOOGLE_PROJECT_ID test-instance example-db

Anda akan melihat hasil berikut:

SingerId : 12 FirstName : Melissa LastName : Garcia

Memperbarui skema database

Asumsikan Anda perlu menambahkan kolom baru bernama MarketingBudget ke tabel Albums. Untuk menambahkan kolom baru ke tabel yang ada, skema database Anda harus diperbarui. Spanner mendukung pembaruan skema pada database sementara database terus menyalurkan traffic. Pembaruan skema tidak memerlukan pembuatan database secara offline dan pembaruan tersebut tidak mengunci seluruh tabel atau kolom. Anda dapat terus menulis data ke database selama pembaruan skema. Baca selengkapnya tentang update skema yang didukung dan performa perubahan skema di Membuat pembaruan skema.

Menambahkan kolom

Anda dapat menambahkan kolom di command line menggunakan Google Cloud CLI atau secara terprogram menggunakan library klien Spanner untuk C#.

Pada command line

Gunakan perintah ALTER TABLE berikut untuk menambahkan kolom baru ke tabel:

GoogleSQL

gcloud spanner databases ddl update example-db --instance=test-instance `
    --ddl='ALTER TABLE Albums ADD COLUMN MarketingBudget INT64'

PostgreSQL

gcloud spanner databases ddl update example-db --instance=test-instance `
    --ddl='ALTER TABLE Albums ADD COLUMN MarketingBudget BIGINT'

Anda akan melihat:

Schema updating...done.

Menggunakan library klien Spanner untuk C#

Gunakan CreateDdlCommand() untuk mengubah skema:


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

public class AddColumnAsyncSample
{
    public async Task AddColumnAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";
        string alterStatement = "ALTER TABLE Albums ADD COLUMN MarketingBudget INT64";

        using var connection = new SpannerConnection(connectionString);
        using var updateCmd = connection.CreateDdlCommand(alterStatement);
        await updateCmd.ExecuteNonQueryAsync();
        Console.WriteLine("Added the MarketingBudget column.");
    }
}

Jalankan contoh menggunakan perintah addColumn.

dotnet run addColumn $env:GOOGLE_PROJECT_ID test-instance example-db

Anda akan melihat:

Added the MarketingBudget column.

Menulis data ke kolom baru

Kode berikut menulis data ke kolom baru. Fungsi ini menetapkan MarketingBudget ke 100000 untuk baris yang dikunci oleh Albums(1, 1) dan ke 500000 untuk baris yang dikunci oleh Albums(2, 2).


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

public class UpdateDataAsyncSample
{
    public async Task<int> UpdateDataAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";

        using var connection = new SpannerConnection(connectionString);

        var rowCount = 0;
        SpannerCommand cmd = connection.CreateDmlCommand(
            "UPDATE Albums SET MarketingBudget = @MarketingBudget "
            + "WHERE SingerId = 1 and AlbumId = 1");
        cmd.Parameters.Add("MarketingBudget", SpannerDbType.Int64, 100000);
        rowCount += await cmd.ExecuteNonQueryAsync();

        cmd = connection.CreateDmlCommand(
            "UPDATE Albums SET MarketingBudget = @MarketingBudget "
            + "WHERE SingerId = 2 and AlbumId = 2");
        cmd.Parameters.Add("MarketingBudget", SpannerDbType.Int64, 500000);
        rowCount += await cmd.ExecuteNonQueryAsync();

        Console.WriteLine("Data Updated.");
        return rowCount;
    }
}

Jalankan contoh menggunakan perintah writeDataToNewColumn.

dotnet run writeDataToNewColumn $env:GOOGLE_PROJECT_ID test-instance example-db

Anda akan melihat:

Updated data.

Anda juga dapat menjalankan kueri SQL untuk mengambil nilai yang baru saja ditulis.

Berikut adalah kode untuk mengeksekusi kueri:


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

public class QueryNewColumnAsyncSample
{
    public class Album
    {
        public int SingerId { get; set; }
        public int AlbumId { get; set; }
        public long MarketingBudget { get; set; }
    }

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

        var albums = new List<Album>();
        using var connection = new SpannerConnection(connectionString);
        using var cmd = connection.CreateSelectCommand("SELECT * FROM Albums");
        using var reader = await cmd.ExecuteReaderAsync();
        while (await reader.ReadAsync())
        {
            albums.Add(new Album
            {
                SingerId = reader.GetFieldValue<int>("SingerId"),
                AlbumId = reader.GetFieldValue<int>("AlbumId"),
                MarketingBudget = reader.IsDBNull(reader.GetOrdinal("MarketingBudget")) ? 0 : reader.GetFieldValue<long>("MarketingBudget")
            });
        }
        return albums;
    }
}

Untuk menjalankan kueri ini, jalankan contoh menggunakan argumen queryNewColumn.

dotnet run queryNewColumn $env:GOOGLE_PROJECT_ID test-instance example-db

Anda akan melihat:

SingerId : 1 AlbumId : 1 MarketingBudget : 100000
SingerId : 1 AlbumId : 2 MarketingBudget :
SingerId : 2 AlbumId : 1 MarketingBudget :
SingerId : 2 AlbumId : 2 MarketingBudget : 500000
SingerId : 2 AlbumId : 3 MarketingBudget :

Memperbarui data

Anda dapat memperbarui data menggunakan DML dalam transaksi baca-tulis.

Anda menggunakan metode ExecuteNonQueryAsync() untuk mengeksekusi pernyataan DML.


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

public class WriteWithTransactionUsingDmlCoreAsyncSample
{
    public async Task<int> WriteWithTransactionUsingDmlCoreAsync(string projectId, string instanceId, string databaseId)
    {
        // This sample transfers 200,000 from the MarketingBudget
        // field of the second Album to the first Album. Make sure to run
        // the AddColumnAsyncSample and WriteDataToNewColumnAsyncSample first,
        // in that order.
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";

        decimal transferAmount = 200000;
        decimal secondBudget = 0;

        // Create connection to Cloud Spanner.
        using var connection = new SpannerConnection(connectionString);
        await connection.OpenAsync();

        // Create a readwrite transaction that we'll assign
        // to each SpannerCommand.
        using var transaction = await connection.BeginTransactionAsync();
        // Create statement to select the second album's data.
        var cmdLookup = connection.CreateSelectCommand("SELECT * FROM Albums WHERE SingerId = 2 AND AlbumId = 2");
        cmdLookup.Transaction = transaction;
        // Execute the select query.
        using var reader1 = await cmdLookup.ExecuteReaderAsync();
        while (await reader1.ReadAsync())
        {
            // Read the second album's budget.
            secondBudget = reader1.GetFieldValue<decimal>("MarketingBudget");
            // Confirm second Album's budget is sufficient and
            // if not raise an exception. Raising an exception
            // will automatically roll back the transaction.
            if (secondBudget < transferAmount)
            {
                throw new Exception($"The second album's budget {secondBudget} is less than the amount to transfer.");
            }
        }

        // Update second album to remove the transfer amount.
        secondBudget -= transferAmount;
        SpannerCommand cmd = connection.CreateDmlCommand("UPDATE Albums SET MarketingBudget = @MarketingBudget  WHERE SingerId = 2 and AlbumId = 2");
        cmd.Parameters.Add("MarketingBudget", SpannerDbType.Int64, secondBudget);
        cmd.Transaction = transaction;
        var rowCount = await cmd.ExecuteNonQueryAsync();

        // Update first album to add the transfer amount.
        cmd = connection.CreateDmlCommand("UPDATE Albums SET MarketingBudget = MarketingBudget + @MarketingBudgetIncrement WHERE SingerId = 1 and AlbumId = 1");
        cmd.Parameters.Add("MarketingBudgetIncrement", SpannerDbType.Int64, transferAmount);
        cmd.Transaction = transaction;
        rowCount += await cmd.ExecuteNonQueryAsync();

        await transaction.CommitAsync();

        Console.WriteLine("Transaction complete.");
        return rowCount;
    }
}

Jalankan contoh menggunakan argumen writeWithTransactionUsingDml.

dotnet run writeWithTransactionUsingDml $env:GOOGLE_PROJECT_ID test-instance example-db

Anda akan melihat:

Transaction complete.

Menggunakan indeks sekunder

Misalkan Anda ingin mengambil semua baris Albums yang memiliki nilai AlbumTitle dalam rentang tertentu. Anda dapat membaca semua nilai dari kolom AlbumTitle menggunakan pernyataan SQL atau panggilan baca, lalu menghapus baris yang tidak memenuhi kriteria, tetapi melakukan pemindaian tabel lengkap ini mahal, terutama untuk tabel dengan banyak baris. Namun, Anda dapat mempercepat pengambilan baris saat melakukan penelusuran berdasarkan kolom kunci non-utama dengan membuat indeks sekunder pada tabel.

Menambahkan indeks sekunder ke tabel yang ada memerlukan update skema. Seperti update skema lainnya, Spanner mendukung penambahan indeks saat database terus menyalurkan traffic. Spanner secara otomatis mengisi ulang indeks dengan data yang ada. Pengisian ulang mungkin memerlukan waktu beberapa menit untuk diselesaikan, tetapi Anda tidak perlu membuat database offline atau menghindari penulisan ke tabel yang diindeks selama proses ini. Untuk mengetahui detail selengkapnya, lihat Menambahkan indeks sekunder.

Setelah Anda menambahkan indeks sekunder, Spanner akan otomatis menggunakannya untuk kueri SQL yang cenderung berjalan lebih cepat dengan indeks tersebut. Jika menggunakan antarmuka baca, Anda harus menentukan indeks yang ingin digunakan.

Menambahkan indeks sekunder

Anda dapat menambahkan indeks di command line menggunakan gcloud CLI, atau secara terprogram menggunakan library klien Spanner untuk C#.

Pada command line

Gunakan perintah CREATE INDEX berikut untuk menambahkan indeks ke database:

gcloud spanner databases ddl update example-db --instance=test-instance `
    --ddl='CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)'

Anda akan melihat:

Schema updating...done.

Menggunakan library klien Spanner untuk C#

Gunakan CreateDdlCommand() untuk menambahkan indeks:


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

public class AddIndexAsyncSample
{
    public async Task AddIndexAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";
        string createStatement = "CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)";

        using var connection = new SpannerConnection(connectionString);
        using var createCmd = connection.CreateDdlCommand(createStatement);
        await createCmd.ExecuteNonQueryAsync();
        Console.WriteLine("Added the AlbumsByAlbumTitle index.");
    }
}

Jalankan contoh menggunakan perintah addIndex.

  dotnet run addIndex $env:GOOGLE_PROJECT_ID test-instance example-db

Menambahkan indeks dapat memerlukan waktu beberapa menit. Setelah indeks ditambahkan, Anda akan melihat:

  Added the AlbumsByAlbumTitle index.

Menambahkan indeks untuk pembacaan khusus indeks

Anda mungkin telah memperhatikan bahwa contoh pembacaan sebelumnya tidak menyertakan pembacaan kolom MarketingBudget. Hal ini karena antarmuka baca Spanner tidak mendukung kemampuan untuk menggabungkan indeks dengan tabel data guna mencari nilai yang tidak disimpan dalam indeks.

Buat definisi alternatif AlbumsByAlbumTitle yang menyimpan salinan MarketingBudget dalam indeks.

Pada command line

GoogleSQL

gcloud spanner databases ddl update example-db --instance=test-instance `
    --ddl='CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget)

PostgreSQL

gcloud spanner databases ddl update example-db --instance=test-instance `
    --ddl='CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) INCLUDE (MarketingBudget)

Menambahkan indeks dapat memerlukan waktu beberapa menit. Setelah indeks ditambahkan, Anda akan melihat:

Schema updating...done.

Menggunakan library klien Spanner untuk C#

Gunakan CreateDdlCommand() untuk menambahkan indeks dengan klausa STORING:


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

public class AddStoringIndexAsyncSample
{
    public async Task AddStoringIndexAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";
        string createStatement = "CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget)";

        using var connection = new SpannerConnection(connectionString);
        using var createCmd = connection.CreateDdlCommand(createStatement);
        await createCmd.ExecuteNonQueryAsync();
        Console.WriteLine("Added the AlbumsByAlbumTitle2 index.");
    }
}

Jalankan contoh menggunakan perintah addStoringIndex.

dotnet run addStoringIndex $env:GOOGLE_PROJECT_ID test-instance example-db

Anda akan melihat:

Added the AlbumsByAlbumTitle2 index.

Sekarang Anda dapat menjalankan operasi baca yang mengambil semua kolom AlbumId, AlbumTitle, dan MarketingBudget dari indeks AlbumsByAlbumTitle2:

Baca data menggunakan indeks penyimpanan yang Anda buat dengan mengeksekusi kueri yang secara eksplisit menentukan indeks:


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

Jalankan contoh menggunakan perintah queryDataWithStoringIndex.

dotnet run queryDataWithStoringIndex $env:GOOGLE_PROJECT_ID test-instance example-db

Anda akan melihat output yang mirip dengan:

AlbumId : 2 AlbumTitle : Forever Hold your Peace MarketingBudget : 300000
AlbumId : 2 AlbumTitle : Go, Go, Go MarketingBudget : 300000

Mengambil data menggunakan transaksi hanya baca

Misalnya Anda ingin mengeksekusi lebih dari satu pembacaan pada stempel waktu yang sama. Transaksi hanya baca mengamati awalan yang konsisten dari histori commit transaksi, sehingga aplikasi Anda selalu mendapatkan data yang konsisten. Gunakan TransactionScope() framework .NET beserta OpenAsReadOnlyAsync() untuk menjalankan transaksi hanya baca.

Berikut ini cara menjalankan kueri dan melakukan pembacaan dalam transaksi hanya baca yang sama:

.NET Standard 2.0


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

public class QueryDataWithTransactionAsyncSample
{
    public class Album
    {
        public int SingerId { get; set; }
        public int AlbumId { get; set; }
        public string AlbumTitle { get; set; }
    }

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

        var albums = new List<Album>();
        using TransactionScope scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled);
        using var connection = new SpannerConnection(connectionString);

        // Opens the connection so that the Spanner transaction included in the TransactionScope
        // is read-only TimestampBound.Strong.
        await connection.OpenAsync(AmbientTransactionOptions.ForTimestampBoundReadOnly(), default);
        using var cmd = connection.CreateSelectCommand("SELECT SingerId, AlbumId, AlbumTitle FROM Albums");

        // Read #1.
        using (var reader = await cmd.ExecuteReaderAsync())
        {
            while (await reader.ReadAsync())
            {
                Console.WriteLine("SingerId : " + reader.GetFieldValue<string>("SingerId")
                    + " AlbumId : " + reader.GetFieldValue<string>("AlbumId")
                    + " AlbumTitle : " + reader.GetFieldValue<string>("AlbumTitle"));
            }
        }

        // Read #2. Even if changes occur in-between the reads,
        // the transaction ensures that Read #1 and Read #2
        // return the same data.
        using (var reader = await cmd.ExecuteReaderAsync())
        {
            while (await reader.ReadAsync())
            {
                albums.Add(new Album
                {
                    AlbumId = reader.GetFieldValue<int>("AlbumId"),
                    SingerId = reader.GetFieldValue<int>("SingerId"),
                    AlbumTitle = reader.GetFieldValue<string>("AlbumTitle")
                });
            }
        }
        scope.Complete();
        Console.WriteLine("Transaction complete.");
        return albums;
    }
}

.NET Standard 1.5


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

public class QueryDataWithTransactionCoreAsyncSample
{
    public class Album
    {
        public int SingerId { get; set; }
        public int AlbumId { get; set; }
        public string AlbumTitle { get; set; }
    }

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

        var albums = new List<Album>();

        using var connection = new SpannerConnection(connectionString);
        await connection.OpenAsync();

        // Open a new read only transaction.
        using var transaction = await connection.BeginReadOnlyTransactionAsync();
        using var cmd = connection.CreateSelectCommand("SELECT SingerId, AlbumId, AlbumTitle FROM Albums");
        cmd.Transaction = transaction;

        // Read #1.
        using (var reader = await cmd.ExecuteReaderAsync())
        {
            while (await reader.ReadAsync())
            {
                Console.WriteLine("SingerId : " + reader.GetFieldValue<string>("SingerId")
                    + " AlbumId : " + reader.GetFieldValue<string>("AlbumId")
                    + " AlbumTitle : " + reader.GetFieldValue<string>("AlbumTitle"));
            }
        }

        // Read #2. Even if changes occur in-between the reads,
        // the transaction ensures that Read #1 and Read #2
        // return the same data.
        using (var reader = await cmd.ExecuteReaderAsync())
        {
            while (await reader.ReadAsync())
            {
                albums.Add(new Album
                {
                    AlbumId = reader.GetFieldValue<int>("AlbumId"),
                    SingerId = reader.GetFieldValue<int>("SingerId"),
                    AlbumTitle = reader.GetFieldValue<string>("AlbumTitle")
                });
            }
        }

        Console.WriteLine("Transaction complete.");
        return albums;
    }
}

Jalankan contoh menggunakan perintah queryDataWithTransaction.

dotnet run queryDataWithTransaction $env:GOOGLE_PROJECT_ID test-instance example-db

Anda akan melihat output yang mirip dengan:

SingerId : 2 AlbumId : 2 AlbumTitle : Forever Hold your Peace
SingerId : 1 AlbumId : 2 AlbumTitle : Go, Go, Go
SingerId : 2 AlbumId : 1 AlbumTitle : Green
SingerId : 2 AlbumId : 3 AlbumTitle : Terrified
SingerId : 1 AlbumId : 1 AlbumTitle : Total Junk
SingerId : 2 AlbumId : 2 AlbumTitle : Forever Hold your Peace
SingerId : 1 AlbumId : 2 AlbumTitle : Go, Go, Go
SingerId : 2 AlbumId : 1 AlbumTitle : Green
SingerId : 2 AlbumId : 3 AlbumTitle : Terrified
SingerId : 1 AlbumId : 1 AlbumTitle : Total Junk

Pembersihan

Agar tidak menimbulkan biaya tambahan pada akun Penagihan Cloud Anda untuk resource yang digunakan dalam tutorial ini, hapus database dan hapus instance yang Anda buat.

Menghapus database

Jika instance dihapus, semua database di dalamnya akan dihapus secara otomatis. Langkah ini menunjukkan cara menghapus database tanpa menghapus instance (Anda masih akan dikenai biaya untuk instance tersebut).

Pada command line

gcloud spanner databases delete example-db --instance=test-instance

Menggunakan Konsol Google Cloud

  1. Buka halaman Spanner Instances di konsol Google Cloud.

    Buka halaman Instances

  2. Klik instance.

  3. Klik database yang ingin dihapus.

  4. Di halaman Database details, klik Delete.

  5. Konfirmasi bahwa Anda ingin menghapus database, lalu klik Delete.

Menghapus instance

Jika sebuah instance dihapus, semua database yang dibuat dalam instance tersebut akan otomatis dihapus.

Pada command line

gcloud spanner instances delete test-instance

Menggunakan Konsol Google Cloud

  1. Buka halaman Spanner Instances di konsol Google Cloud.

    Buka halaman Instances

  2. Klik instance Anda.

  3. Klik Delete.

  4. Konfirmasi bahwa Anda ingin menghapus instance, lalu klik Hapus.

Langkah selanjutnya