Go で Cloud Spanner を使ってみる

目標

このチュートリアルでは、Go 用の Cloud Spanner クライアント ライブラリを使用するステップを詳しく説明します。

  • Cloud Spanner のインスタンスとデータベースを作成します。
  • データベースのデータに対し、書き込み、読み取り、SQL クエリの実行を行います。
  • データベース スキーマを更新します。
  • 読み取り / 書き込みトランザクションを使用してデータを更新します。
  • セカンダリ インデックスをデータベースに追加します。
  • インデックスを使用して、データの読み込みと SQL クエリの実行を行います。
  • 読み取り専用トランザクションを使用してデータを取得します。

料金

このチュートリアルで使用する Cloud Spanner は、Google Cloud Platform の有料コンポーネントです。Cloud Spanner を使用する料金については、料金をご覧ください。

始める前に

  1. 設定に示されている手順を完了します。その手順では、Google Cloud Platform のデフォルト プロジェクトの作成と設定、課金の有効化、Cloud Spanner API の有効化、Cloud Spanner API を使用するのに必要な認証情報を取得するために OAuth 2.0 を設定する方法について説明しています。
    特に、ローカル開発環境で認証情報を設定するために、必ず gcloud auth application-default login を実行してください。

    1. まだインストールしていない場合は開発マシンに Go(ダウンロード)をインストールします。
    2. GOPATH 環境変数をまだ構成していない場合は、インストールをテストするの説明に沿って構成します。
    3. サンプルをマシンにダウンロードします。

      go get -u github.com/GoogleCloudPlatform/golang-samples/spanner/...
      
    4. Cloud Spanner サンプルコードが含まれるディレクトリに移動します。

      cd $GOPATH/src/github.com/GoogleCloudPlatform/golang-samples/spanner/spanner_snippets
      
    5. GCLOUD_PROJECT 環境変数に Google Cloud Platform プロジェクト ID を設定します。

      export GCLOUD_PROJECT=[MY_PROJECT_ID]
      

インスタンスの作成

Cloud Spanner を最初に使用するときは、インスタンスを作成する必要があります。インスタンスとは、Cloud Spanner データベースによって使用されるリソースの割り当てのことです。インスタンスを作成するときは、インスタンス構成を選択してデータの格納場所を指定し、さらに使用するノード数も選択して、インスタンスの配信リソースおよびストレージ リソースの量を決定します。

次のコマンドを実行して、1 ノードの us-central1 リージョンに Cloud Spanner インスタンスを作成します。

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

これにより、次の特性を持つインスタンスが作成されます。

  • インスタンス ID test-instance
  • 表示名 Test Instance
  • インスタンス構成 regional-us-central1(リージョン構成ではデータが単一のリージョンに保存され、マルチリージョン構成ではデータが複数のリージョンに分散されます。詳しくは、インスタンスをご覧ください。)
  • ノード数 1(node_count はインスタンスのデータベースで使用可能な配信リソースとストレージ リソースの量に対応します。詳しくは、ノード数をご覧ください。)

以下のように表示されます。

Creating instance...done.

サンプル ファイルの確認

サンプル リポジトリには、Go で Cloud Spanner を使用する方法を示すサンプルがあります。

Cloud Spanner の使用方法については、snippet.go ファイルをご覧ください。このファイルのコードは、新しいデータベースを作成して使用する方法を示しています。データで使用しているサンプル スキーマは、スキーマとデータモデルのページにあります。

データベースの作成

コマンドラインで次のコマンドを実行して、test-instance というインスタンスに example-db というデータベースを作成します。

go run snippet.go createdatabase projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

以下のように表示されます。

Created database [example-db]

これで Cloud Spanner データベースが作成されました。データベースを作成したコードは次のとおりです。

func createDatabase(ctx context.Context, w io.Writer, adminClient *database.DatabaseAdminClient, db string) error {
	matches := regexp.MustCompile("^(.*)/databases/(.*)$").FindStringSubmatch(db)
	if matches == nil || len(matches) != 3 {
		return fmt.Errorf("Invalid database id %s", db)
	}
	op, err := adminClient.CreateDatabase(ctx, &adminpb.CreateDatabaseRequest{
		Parent:          matches[1],
		CreateStatement: "CREATE DATABASE `" + matches[2] + "`",
		ExtraStatements: []string{
			`CREATE TABLE Singers (
				SingerId   INT64 NOT NULL,
				FirstName  STRING(1024),
				LastName   STRING(1024),
				SingerInfo BYTES(MAX)
			) PRIMARY KEY (SingerId)`,
			`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`,
		},
	})
	if err != nil {
		return err
	}
	if _, err := op.Wait(ctx); err != nil {
		return err
	}
	fmt.Fprintf(w, "Created database [%s]\n", db)
	return nil
}

コードでは、基本的な音楽アプリケーション用の 2 つのテーブル SingersAlbums も定義されています。これらのテーブルはこのページ全体で使用されています。まだスキーマ例を見ていない場合は確認してください。

次のステップでは、データベースにデータを書き込みます。

データベース クライアントの作成

読み取りや書き込みを行うには、その前に Client を作成する必要があります。

func createClients(ctx context.Context, db string) (*database.DatabaseAdminClient, *spanner.Client) {
	adminClient, err := database.NewDatabaseAdminClient(ctx)
	if err != nil {
		log.Fatal(err)
	}

	dataClient, err := spanner.NewClient(ctx, db)
	if err != nil {
		log.Fatal(err)
	}

	return adminClient, dataClient
}

Client はデータベース接続と考えることができます。Cloud Spanner とのすべてのやりとりは Client 経由で実行されます。通常はアプリケーション開始時に Client を作成し、読み取り、書き込み、トランザクションの実行に Client を再利用します。各クライアントは Cloud Spanner のリソースを使用するため、Client.Close() を呼び出してクライアントのリソース(ネットワーク接続を含む)をクリーンアップする必要があります。

詳細については、Client のリファレンスをご覧ください。

前述のコードには、データベースの作成に使用される DatabaseAdminClient の作成方法も示されています。

DML を使用してデータを書き込む

読み取り / 書き込みトランザクションでデータ操作言語(DML)を使用してデータを挿入できます。

Update() メソッドを使用して DML ステートメントを実行します。

func writeUsingDML(ctx context.Context, w io.Writer, client *spanner.Client) error {
	_, err := client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
		stmt := spanner.Statement{
			SQL: `INSERT Singers (SingerId, FirstName, LastName) VALUES
				(12, 'Melissa', 'Garcia'),
				(13, 'Russell', 'Morales'),
				(14, 'Jacqueline', 'Long'),
				(15, 'Dylan', 'Shaw')`,
		}
		rowCount, err := txn.Update(ctx, stmt)
		if err != nil {
			return err
		}
		fmt.Fprintf(w, "%d record(s) inserted.\n", rowCount)
		return err
	})
	return err
}

dmlwrite 引数を使用してサンプルを実行します。

go run snippet.go dmlwrite projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

以下のように表示されます。

4 record(s) inserted.

ミューテーションを使用してデータを書き込む

ミューテーションを使ってデータを挿入することもできます。

Mutation はミューテーション オペレーションのためのコンテナです。Mutation は挿入、更新、削除など、Cloud Spanner データベース中のさまざまな行やテーブルに対して、Cloud Spanner がアトミックに適用する一連の操作を表します。

Mutation.InsertOrUpdate() を使用して INSERT_OR_UPDATE ミューテーションを作成できます。これによって新しい行が追加されます。行がすでに存在している場合は列値が更新されます。あるいは、Mutation.Insert() メソッドを使用して、新しい行を追加する INSERT ミューテーションを作成します。

Client.Apply() はデータベースに対し、ミューテーションをアトミックに適用します。

次のコードは、ミューテーションを使用してデータを書き込む方法を示しています。

func write(ctx context.Context, w io.Writer, client *spanner.Client) error {
	singerColumns := []string{"SingerId", "FirstName", "LastName"}
	albumColumns := []string{"SingerId", "AlbumId", "AlbumTitle"}
	m := []*spanner.Mutation{
		spanner.InsertOrUpdate("Singers", singerColumns, []interface{}{1, "Marc", "Richards"}),
		spanner.InsertOrUpdate("Singers", singerColumns, []interface{}{2, "Catalina", "Smith"}),
		spanner.InsertOrUpdate("Singers", singerColumns, []interface{}{3, "Alice", "Trentor"}),
		spanner.InsertOrUpdate("Singers", singerColumns, []interface{}{4, "Lea", "Martin"}),
		spanner.InsertOrUpdate("Singers", singerColumns, []interface{}{5, "David", "Lomond"}),
		spanner.InsertOrUpdate("Albums", albumColumns, []interface{}{1, 1, "Total Junk"}),
		spanner.InsertOrUpdate("Albums", albumColumns, []interface{}{1, 2, "Go, Go, Go"}),
		spanner.InsertOrUpdate("Albums", albumColumns, []interface{}{2, 1, "Green"}),
		spanner.InsertOrUpdate("Albums", albumColumns, []interface{}{2, 2, "Forever Hold Your Peace"}),
		spanner.InsertOrUpdate("Albums", albumColumns, []interface{}{2, 3, "Terrified"}),
	}
	_, err := client.Apply(ctx, m)
	return err
}

write 引数を使用してサンプルを実行します。

go run snippet.go write projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

コマンドが正常に実行されることを確認します。

SQL を使用したデータのクエリ

Cloud Spanner は、データを読み取るためにネイティブ SQL インターフェースをサポートします。これにアクセスするには、コマンドラインで gcloud コマンドライン ツールを使用するか、プログラミングによって Go 用の Cloud Spanner クライアント ライブラリを使用します。

コマンドラインから

Albums テーブルのすべての列から値を読み取るには、次の SQL ステートメントを実行します。

gcloud spanner databases execute-sql example-db --instance=test-instance --sql='SELECT SingerId, AlbumId, AlbumTitle FROM Albums'

結果は次のようになります。

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

Go 用の Cloud Spanner クライアント ライブラリの使用

コマンドラインで SQL 文を実行するだけでなく、Go 用の Cloud Spanner クライアント ライブラリを使用して同じ SQL 文をプログラマティックに発行できます。

SQL クエリの実行に使用されるメソッドとタイプを次に示します。

  • Client.Single(): このメソッドは、Cloud Spanner テーブルの 1 つ以上の行から 1 つ以上の列の値を読み取ります。Client.Single は、読み取りまたは SQL ステートメントを実行するために使用される ReadOnlyTransaction を返します。
  • ReadOnlyTransaction.Query(): このメソッドは、データベースに対してクエリを実行します。
  • Statement タイプ: このタイプは、SQL 文字列の作成に使用します。
  • Row タイプ: このタイプを使って、SQL ステートメントまたは読み取り呼び出しから返されるデータにアクセスします。

クエリを発行してデータにアクセスする方法を次に示します。

func query(ctx context.Context, w io.Writer, client *spanner.Client) error {
	stmt := spanner.Statement{SQL: `SELECT SingerId, AlbumId, AlbumTitle FROM Albums`}
	iter := client.Single().Query(ctx, stmt)
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			return nil
		}
		if err != nil {
			return err
		}
		var singerID, albumID int64
		var albumTitle string
		if err := row.Columns(&singerID, &albumID, &albumTitle); err != nil {
			return err
		}
		fmt.Fprintf(w, "%d %d %s\n", singerID, albumID, albumTitle)
	}
}

query 引数を使用してサンプルを実行します。

go run snippet.go query projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

次のような結果が表示されます。

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

読み取り API を使用したデータの読み込み

Cloud Spanner の SQL インターフェースに加えて、Cloud Spanner は読み取りインターフェースもサポートしています。

データベースから行を読み取るには、ReadOnlyTransaction.Read() を使用します。読み取るキーおよびキー範囲のコレクションを定義するには、KeySet を使用します。

データを読み取る方法を次に示します。

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

read 引数を使用してサンプルを実行します。

go run snippet.go read projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

次のような出力が表示されます。

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

データベース スキーマの更新

MarketingBudget という列を新たに Albums テーブルに追加する必要があるとします。既存のテーブルに新しい列を追加するには、データベース スキーマの更新が必要です。Cloud Spanner は、トラフィック提供中のデータベースへのスキーマの更新をサポートしています。スキーマの更新では、データベースをオフラインにする必要がなく、テーブル全体や列がロックされないため、スキーマの更新中もデータベースへのデータの書き込みを続けることができます。サポートされるスキーマの更新とスキーマ変更のパフォーマンスの詳細については、スキーマの更新をご覧ください。

列の追加

列を追加するには、コマンドラインで gcloud コマンドライン ツールを使用するか、Go 用の Cloud Spanner クライアント ライブラリを使用してプログラミングによって行います。

コマンドラインから

テーブルに新しい列を追加するには、次の ALTER TABLE コマンドを使用します。

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

以下のように表示されます。

DDL updating...done.

Go 用の Cloud Spanner クライアント ライブラリの使用

スキーマを変更するには、DatabaseAdminClient.UpdateDatabaseDdl() を使用します。

func addNewColumn(ctx context.Context, w io.Writer, adminClient *database.DatabaseAdminClient, database string) error {
	op, err := adminClient.UpdateDatabaseDdl(ctx, &adminpb.UpdateDatabaseDdlRequest{
		Database: database,
		Statements: []string{
			"ALTER TABLE Albums ADD COLUMN MarketingBudget INT64",
		},
	})
	if err != nil {
		return err
	}
	if err := op.Wait(ctx); err != nil {
		return err
	}
	fmt.Fprintf(w, "Added MarketingBudget column\n")
	return nil
}

addnewcolumn 引数を使用してサンプルを実行します。

go run snippet.go addnewcolumn projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

以下のように表示されます。

Added MarketingBudget column.

新しい列へのデータの書き込み

次のコードは、新しい列にデータを書き込みます。キーが Albums(1, 1) の行は MarketingBudget100000 に、キーが Albums(2, 2) の行は 500000 に設定されます。

func update(ctx context.Context, w io.Writer, client *spanner.Client) error {
	cols := []string{"SingerId", "AlbumId", "MarketingBudget"}
	_, err := client.Apply(ctx, []*spanner.Mutation{
		spanner.Update("Albums", cols, []interface{}{1, 1, 100000}),
		spanner.Update("Albums", cols, []interface{}{2, 2, 500000}),
	})
	return err
}

update 引数を使用してサンプルを実行します。

go run snippet.go update projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

SQL クエリまたは読み取り呼び出しを実行して、書き込んだばかりの値を取得することもできます。

クエリを実行するコードを次に示します。

func queryNewColumn(ctx context.Context, w io.Writer, client *spanner.Client) error {
	stmt := spanner.Statement{SQL: `SELECT SingerId, AlbumId, MarketingBudget FROM Albums`}
	iter := client.Single().Query(ctx, stmt)
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			return nil
		}
		if err != nil {
			return err
		}
		var singerID, albumID int64
		var marketingBudget spanner.NullInt64
		if err := row.ColumnByName("SingerId", &singerID); err != nil {
			return err
		}
		if err := row.ColumnByName("AlbumId", &albumID); 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 %d %s\n", singerID, albumID, budget)
	}
}

このクエリを実行するには、querynewcolumn 引数を使用してサンプルを実行します。

go run snippet.go querynewcolumn projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

以下のように表示されます。

1 1 100000
1 2 NULL
2 1 NULL
2 2 500000
2 3 NULL

データの更新

読み取り / 書き込みトランザクションで DML を使用してデータを更新できます。

Update() メソッドを使用して DML ステートメントを実行します。

func writeWithTransactionUsingDML(ctx context.Context, w io.Writer, client *spanner.Client) error {
	_, err := client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
		// getBudget returns the budget for a record with a given albumId and singerId.
		getBudget := func(albumID, singerID int64) (int64, error) {
			key := spanner.Key{albumID, singerID}
			row, err := txn.ReadRow(ctx, "Albums", key, []string{"MarketingBudget"})
			if err != nil {
				return 0, err
			}
			var budget int64
			if err := row.Column(0, &budget); err != nil {
				return 0, err
			}
			return budget, nil
		}
		// updateBudget updates the budget for a record with a given albumId and singerId.
		updateBudget := func(singerID, albumID, albumBudget int64) error {
			stmt := spanner.Statement{
				SQL: `UPDATE Albums
					SET MarketingBudget = @AlbumBudget
					WHERE SingerId = @SingerId and AlbumId = @AlbumId`,
				Params: map[string]interface{}{
					"SingerId":    singerID,
					"AlbumId":     albumID,
					"AlbumBudget": albumBudget,
				},
			}
			_, err := txn.Update(ctx, stmt)
			return err
		}

		// Transfer the marketing budget from one album to another. By keeping the actions
		// in a single transaction, it ensures the movement is atomic.
		const transferAmt = 200000
		var album1budget, album2budget int64
		var err error
		if album1budget, err = getBudget(1, 1); err != nil {
			return err
		}
		// The transaction will only be committed if this condition still holds at the time
		// of commit. Otherwise it will be aborted and the callable will be rerun by the
		// client library.
		if album1budget >= transferAmt {
			if album2budget, err = getBudget(2, 2); err != nil {
				return err
			}
			if err = updateBudget(1, 1, album1budget-transferAmt); err != nil {
				return err
			}
			if err = updateBudget(2, 2, album2budget+transferAmt); err != nil {
				return err
			}
			fmt.Fprintf(w, "Moved %d from Album1's MarketingBudget to Album2's.", transferAmt)
		}
		return nil
	})
	return err
}

dmlwritetxn 引数を使用してサンプルを実行します。

go run snippet.go dmlwritetxn projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

以下のように表示されます。

Moved 200000 from Album1's MarketingBudget to Album2's.

セカンダリ インデックスの使用

Albums から AlbumTitle の値が特定の範囲内である行すべてをフェッチしたいと仮定します。SQL ステートメントまたは読み取りインターフェースを使用して AlbumTitle 列からすべての値を読み取り、基準を満たしていない行を破棄することもできますが、この方法では処理量が大きくなります(特に、行数が多いテーブルの場合)。代わりに、テーブルにセカンダリ インデックスを作成することにより、主キー以外の列を検索するときの行の取得速度を上げることができます。

既存のテーブルにセカンダリ インデックスを追加するには、スキーマの更新が必要です。他のスキーマの更新と同様に、Cloud Spanner ではデータベースがトラフィックを提供している間にインデックスを追加できます。Cloud Spanner は、内部でインデックスにデータを書き込みます(「バックフィル」)。バックフィルには数分かかることがありますが、このプロセスの間に、データベースをオフラインにしたり、特定のテーブルや列への書き込みを避けたりする必要はありません。詳細については、インデックスのバックフィリングをご覧ください。

セカンダリ インデックスの追加

インデックスを追加するには、コマンドラインで gcloud コマンドライン ツールを使用するか、Go 用の Cloud Spanner クライアント ライブラリを使用してプログラミングによって行います。

コマンドラインから

データベースにインデックスを追加するには、次の CREATE INDEX コマンドを使用します。

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

以下のように表示されます。

DDL updating...done.

Go 用の Cloud Spanner クライアント ライブラリの使用

インデックスを追加するには、UpdateDatabaseDdl() を使用します。

func addIndex(ctx context.Context, w io.Writer, adminClient *database.DatabaseAdminClient, database string) error {
	op, err := adminClient.UpdateDatabaseDdl(ctx, &adminpb.UpdateDatabaseDdlRequest{
		Database: database,
		Statements: []string{
			"CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)",
		},
	})
	if err != nil {
		return err
	}
	if err := op.Wait(ctx); err != nil {
		return err
	}
	fmt.Fprintf(w, "Added index\n")
	return nil
}

インデックスの追加には数分かかる場合があります。インデックスが追加されると、次のように表示されます。

Added index

インデックスを使用したクエリ

新しいインデックスを使用してクエリを実行するには、コマンドラインまたはクライアント ライブラリを使用します。

コマンドラインから

gcloud コマンドライン ツールで SQL ステートメントを実行し、["Aardvark", "Goo")AlbumsTitle の範囲に対して AlbumsByAlbumTitle インデックスを使用して Albums から AlbumIdAlbumTitleMarketingBudget を取得します。

gcloud spanner databases execute-sql example-db --instance=test-instance --sql='SELECT AlbumId, AlbumTitle, MarketingBudget FROM Albums@{FORCE_INDEX=AlbumsByAlbumTitle} WHERE AlbumTitle >= "Aardvark" AND AlbumTitle < "Goo"'

結果は次のようになります。

AlbumId  AlbumTitle               MarketingBudget
2        Go, Go, Go
2        Forever Hold Your Peace  300000

Go 用の Cloud Spanner クライアント ライブラリの使用

プログラムでインデックスを使用するコードは、前に使用したクエリのコードと似ています。

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

queryindex 引数を使用してサンプルを実行します。

go run snippet.go queryindex projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

次のような出力が表示されます。

2 Go, Go, Go NULL
2 Forever Hold Your Peace 300000

詳細については、以下のリファレンスをご覧ください。

インデックスを使用した読み取り

インデックスを使用して読み取りを行うには、ReadOnlyTransaction.ReadUsingIndex() を使用します。これは、インデックスでデータベースから 0 個以上の行を読み取るために使用されます。

次のコードは、すべての AlbumId 列と AlbumTitle 列を AlbumsByAlbumTitle インデックスからフェッチします。

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

readindex 引数を使用してサンプルを実行します。

go run snippet.go readindex projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

以下のように表示されます。

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

STORING 句を使用したインデックスの追加

上記の読み取り例に MarketingBudget の列の読み取りが含まれていなかったことに気付かれたかもしれません。これは、Cloud Spanner の読み取りインターフェースが、インデックスとデータテーブルを結合してインデックスに格納されていない値を検索する機能をサポートしていないためです。

MarketingBudget のコピーをインデックスに格納する AlbumsByAlbumTitle の代替定義を作成します。

コマンドラインから

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

インデックスの追加には数分かかる場合があります。インデックスの追加が済むと、次のように表示されます。

DDL updating...done.

Go 用の Cloud Spanner クライアント ライブラリの使用

UpdateDatabaseDdl() を使用し、STORING 句を指定してインデックスを追加します。

func addStoringIndex(ctx context.Context, w io.Writer, adminClient *database.DatabaseAdminClient, database string) error {
	op, err := adminClient.UpdateDatabaseDdl(ctx, &adminpb.UpdateDatabaseDdlRequest{
		Database: database,
		Statements: []string{
			"CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget)",
		},
	})
	if err != nil {
		return err
	}
	if err := op.Wait(ctx); err != nil {
		return err
	}
	fmt.Fprintf(w, "Added storing index\n")
	return nil
}

addstoringindex 引数を使用してサンプルを実行します。

go run snippet.go addstoringindex projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

インデックスの追加には数分かかる場合があります。インデックスが追加されると、次のように表示されます。

Added storing index

これで、インデックス AlbumsByAlbumTitle2 から AlbumIdAlbumTitleMarketingBudget 列をすべて取得する読み取りを実行できるようになります。

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

readstoringindex 引数を使用してサンプルを実行します。

go run snippet.go readstoringindex projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

次のような出力が表示されます。

2 Forever Hold Your Peace 300000
2 Go, Go, Go NULL
1 Green NULL
3 Terrified NULL
1 Total Junk 300000

読み取り専用トランザクションを使用したデータの取得

同じタイムスタンプで複数の読み取りを実行する場合について考えます。読み取り専用トランザクションはトランザクションの commit 履歴の一貫性のあるプレフィックスを監視しているので、アプリケーションは常に一貫性のあるデータを取得できます。読み取り専用トランザクションを実行するには、ReadOnlyTransaction タイプを使用します。Client.ReadOnlyTransaction() を使用して ReadOnlyTransaction を取得します。

同じ読み取り専用トランザクションでクエリと読み取りを実行する方法を次に示します。

func readOnlyTransaction(ctx context.Context, w io.Writer, client *spanner.Client) error {
	ro := client.ReadOnlyTransaction()
	defer ro.Close()
	stmt := spanner.Statement{SQL: `SELECT SingerId, AlbumId, AlbumTitle FROM Albums`}
	iter := ro.Query(ctx, stmt)
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			return err
		}
		var singerID int64
		var albumID int64
		var albumTitle string
		if err := row.Columns(&singerID, &albumID, &albumTitle); err != nil {
			return err
		}
		fmt.Fprintf(w, "%d %d %s\n", singerID, albumID, albumTitle)
	}

	iter = ro.Read(ctx, "Albums", spanner.AllKeys(), []string{"SingerId", "AlbumId", "AlbumTitle"})
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			return nil
		}
		if err != nil {
			return err
		}
		var singerID int64
		var albumID int64
		var albumTitle string
		if err := row.Columns(&singerID, &albumID, &albumTitle); err != nil {
			return err
		}
		fmt.Fprintf(w, "%d %d %s\n", singerID, albumID, albumTitle)
	}
}

readonlytransaction 引数を使用してサンプルを実行します。

go run snippet.go readonlytransaction projects/$GCLOUD_PROJECT/instances/test-instance/databases/example-db

次のような出力が表示されます。

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

クリーンアップ

このチュートリアルで使用したリソースの Google Cloud Platform アカウントが課金されないようにするため、作成したデータベースとインスタンスを削除します。

データベースの削除

インスタンスを削除すると、それに含まれるすべてのデータベースが自動的に削除されます。このステップでは、インスタンスを削除しないでデータベースを削除する方法を示します(インスタンスの料金は引き続き発生します)。

コマンドラインから

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

GCP Console の使用

  1. Google Cloud Platform Console の [Cloud Spanner インスタンス] ページに移動します。
    [Cloud Spanner インスタンス] ページに移動
  2. インスタンスをクリックします。
  3. 削除するデータベースをクリックします。
  4. [データベースの詳細] ページで [削除] をクリックします。
  5. データベースを削除することを確認し、[削除] をクリックします。

インスタンスの削除

インスタンスを削除すると、そのインスタンスで作成されたすべてのデータベースが自動的に削除されます。

コマンドラインから

gcloud spanner instances delete test-instance

GCP Console の使用

  1. Google Cloud Platform Console の [Cloud Spanner インスタンス] ページに移動します。
    [Cloud Spanner インスタンス] ページに移動
  2. インスタンスをクリックします。
  3. [削除] をクリックします。
  4. インスタンスを削除することを確認し、[削除] をクリックします。

次のステップ

このページは役立ちましたか?評価をお願いいたします。

フィードバックを送信...

Cloud Spanner のドキュメント