Go에서 Spanner 시작하기


목표

이 튜토리얼에서는 Go용 Spanner 클라이언트 라이브러리를 사용하는 방법을 단계적으로 설명합니다.

  • Spanner 인스턴스와 데이터베이스 만들기
  • 데이터베이스에서 데이터 읽기, 쓰기 및 데이터에서 SQL 쿼리 실행
  • 데이터베이스 스키마 업데이트
  • 읽기-쓰기 트랜잭션을 사용하여 데이터 업데이트
  • 데이터베이스에 보조 색인 추가
  • 색인을 사용하여 데이터 읽기 및 데이터에서 SQL 쿼리 실행
  • 읽기 전용 트랜잭션을 사용하여 데이터 검색

비용

이 튜토리얼에서는 Google Cloud의 비용 청구 가능한 구성요소인 Spanner를 사용합니다. Spanner 사용 비용에 대한 자세한 내용은 가격 책정을 참조하세요.

시작하기 전에

설정에 설명된 단계를 완료하세요. 기본 Google Cloud 프로젝트 생성, 결제 사용 설정, Cloud Spanner API 사용 설정을 수행하고 Cloud Spanner API 사용에 필요한 사용자 인증 정보를 가져오기 위한 OAuth 2.0 설정을 완료해야 합니다.

특히 gcloud auth application-default login을 실행하여 사용자 인증 정보로 로컬 개발 환경을 설정해야 합니다.

로컬 Go 환경 준비

  1. 개발 머신에 아직 설치되어 있지 않다면 Go를 설치합니다(다운로드).

  2. GOPATH 환경 변수가 아직 구성되어 있지 않으면 설치 테스트의 설명에 따라 이 환경 변수를 구성합니다.

  3. 머신에 샘플을 다운로드합니다.

    git clone https://github.com/GoogleCloudPlatform/golang-samples $GOPATH/src/github.com/GoogleCloudPlatform/golang-samples
    
  4. Spanner 샘플 코드가 있는 디렉터리로 변경합니다.

    cd $GOPATH/src/github.com/GoogleCloudPlatform/golang-samples/spanner/spanner_snippets
    
  5. GCLOUD_PROJECT 환경 변수를 Google Cloud 프로젝트 ID로 설정합니다.

    export GCLOUD_PROJECT=[MY_PROJECT_ID]
    

인스턴스 만들기

Spanner를 처음 사용할 때는 인스턴스를 만들어야 합니다. 이 인스턴스는 Spanner 데이터베이스에서 사용하는 리소스를 할당한 것입니다. 인스턴스를 만들 때는 인스턴스 구성을 선택합니다. 이 구성에 따라 데이터 저장 위치와 사용할 노드 수가 결정되고, 또한 노드 수에 따라 인스턴스의 제공 리소스 및 스토리지 리소스 양이 결정됩니다.

us-central1 리전에 1개의 노드로 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로 Spanner를 사용하는 방법을 보여주는 샘플이 있습니다.

Spanner 사용 방법을 보여주는 snippet.go 파일을 살펴보세요. 코드는 새 데이터베이스를 만들고 사용하는 방법을 보여줍니다. 데이터는 스키마 및 데이터 모델 페이지에 나와 있는 스키마 예시를 사용합니다.

데이터베이스 만들기

명령줄에서 다음을 실행하여 test-instance라는 인스턴스에 example-db라는 데이터베이스를 만듭니다.

GoogleSQL

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

PostgreSQL

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

다음과 같이 표시됩니다.

Created database [example-db]
다음 코드에서는 데이터베이스와 데이터베이스에 테이블 두 개를 만듭니다.

GoogleSQL

import (
	"context"
	"fmt"
	"io"
	"regexp"

	database "cloud.google.com/go/spanner/admin/database/apiv1"
	adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)

func createDatabase(ctx context.Context, w io.Writer, db string) error {
	matches := regexp.MustCompile("^(.*)/databases/(.*)$").FindStringSubmatch(db)
	if matches == nil || len(matches) != 3 {
		return fmt.Errorf("Invalid database id %s", db)
	}

	adminClient, err := database.NewDatabaseAdminClient(ctx)
	if err != nil {
		return err
	}
	defer adminClient.Close()

	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),
				FullName   STRING(2048) AS (
					ARRAY_TO_STRING([FirstName, LastName], " ")
				) STORED
			) 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
}

PostgreSQL

import (
	"context"
	"fmt"
	"io"
	"regexp"

	database "cloud.google.com/go/spanner/admin/database/apiv1"
	adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)

// pgCreateDatabase shows how to create a Spanner database that uses the
// PostgreSQL dialect.
func pgCreateDatabase(ctx context.Context, w io.Writer, db string) error {
	// db := "projects/my-project/instances/my-instance/databases/my-database"
	matches := regexp.MustCompile("^(.*)/databases/(.*)$").FindStringSubmatch(db)
	if matches == nil || len(matches) != 3 {
		return fmt.Errorf("invalid database id %v", db)
	}

	adminClient, err := database.NewDatabaseAdminClient(ctx)
	if err != nil {
		return err
	}
	defer adminClient.Close()

	// Databases with PostgreSQL dialect do not support extra DDL statements in the `CreateDatabase` call.
	req := &adminpb.CreateDatabaseRequest{
		Parent:          matches[1],
		DatabaseDialect: adminpb.DatabaseDialect_POSTGRESQL,
		// Note that PostgreSQL uses double quotes for quoting identifiers. This also
		// includes database names in the CREATE DATABASE statement.
		CreateStatement: `CREATE DATABASE "` + matches[2] + `"`,
	}
	opCreate, err := adminClient.CreateDatabase(ctx, req)
	if err != nil {
		return err
	}
	if _, err := opCreate.Wait(ctx); err != nil {
		return err
	}
	updateReq := &adminpb.UpdateDatabaseDdlRequest{
		Database: db,
		Statements: []string{
			`CREATE TABLE Singers (
				SingerId   bigint NOT NULL PRIMARY KEY,
				FirstName  varchar(1024),
				LastName   varchar(1024),
				SingerInfo bytea
			)`,
			`CREATE TABLE Albums (
				AlbumId      bigint NOT NULL,
				SingerId     bigint NOT NULL REFERENCES Singers (SingerId),
				AlbumTitle   text,
                PRIMARY KEY(SingerId, AlbumId)
			)`,
			`CREATE TABLE Venues (
				VenueId  bigint NOT NULL PRIMARY KEY,
				Name     varchar(1024) NOT NULL
			)`,
		},
	}
	opUpdate, err := adminClient.UpdateDatabaseDdl(ctx, updateReq)
	if err != nil {
		return err
	}
	if err := opUpdate.Wait(ctx); err != nil {
		return err
	}
	fmt.Fprintf(w, "Created Spanner PostgreSQL database [%v]\n", db)
	return nil
}

다음 단계는 데이터베이스에 데이터 쓰기입니다.

데이터베이스 클라이언트 만들기

읽기 또는 쓰기를 수행하려면 먼저 Client를 만들어야 합니다.


import (
	"context"
	"io"

	"cloud.google.com/go/spanner"
	database "cloud.google.com/go/spanner/admin/database/apiv1"
)

func createClients(w io.Writer, db string) error {
	ctx := context.Background()

	adminClient, err := database.NewDatabaseAdminClient(ctx)
	if err != nil {
		return err
	}
	defer adminClient.Close()

	dataClient, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer dataClient.Close()

	_ = adminClient
	_ = dataClient

	return nil
}

Client를 데이터베이스 연결이라고 생각하면 됩니다. Spanner와의 모든 상호작용은 Client를 거쳐야 합니다. 일반적으로 애플리케이션이 시작될 때 Client를 만들며, 이후에는 해당 Client를 재사용하여 트랜잭션을 읽고 쓰고 실행할 수 있습니다. 각 클라이언트에서 Spanner의 리소스를 사용합니다.

동일한 앱에서 여러 클라이언트를 만드는 경우 Client.Close()를 호출하여 네트워크 연결을 포함한 클라이언트의 리소스를 정리해야 합니다(더 이상 필요하지 않은 경우).

자세한 내용은 Client 참조에서 확인하세요.

또한 이전 예시의 코드는 데이터베이스를 만드는 데 사용되는 DatabaseAdminClient를 만드는 방법을 보여줍니다.

DML을 사용하여 데이터 쓰기

읽기-쓰기 트랜잭션에서 DML(Data Manipulation Language)을 사용하여 데이터를 삽입할 수 있습니다.

Update() 메서드를 사용하여 DML 문을 실행합니다.

GoogleSQL


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
)

func writeUsingDML(w io.Writer, db string) error {
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	_, 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
}

PostgreSQL


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
)

func pgWriteUsingDML(w io.Writer, db string) error {
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	_, err = client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
		stmt := spanner.Statement{
			SQL: `INSERT INTO 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
}

Google SQL의 경우 dmlwrite 인수를, PostgreSQL의 경우 pgdmlwrite 인수를 사용하여 샘플을 실행합니다.

GoogleSQL

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

PostgreSQL

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

다음과 같이 표시됩니다.

4 record(s) inserted.

변형을 사용하여 데이터 쓰기

변형을 사용하여 데이터를 삽입할 수도 있습니다.

Mutation은 변형 작업의 컨테이너입니다. Mutation은 Spanner가 Spanner 데이터베이스의 여러 행과 테이블에 원자적으로 적용할 수 있는 일련의 삽입, 업데이트, 삭제를 나타냅니다.

Mutation.InsertOrUpdate()INSERT_OR_UPDATE를 사용하여 새 행을 추가하거나 행이 이미 존재하는 경우에는 열 값을 업데이트하는 변형을 생성합니다. 또는 Mutation.Insert() 메서드를 사용하여 새 행을 추가하는 INSERT 변형을 생성합니다.

Client.Apply()는 데이터베이스에 변형을 원자적으로 적용합니다.

다음 코드는 변형을 사용하여 데이터를 쓰는 방법을 보여줍니다.


import (
	"context"
	"io"

	"cloud.google.com/go/spanner"
)

func write(w io.Writer, db string) error {
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	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을 사용하여 데이터 쿼리

Spanner에서는 데이터 읽기용 SQL 인터페이스를 지원하며 개발자는 명령줄에서 Google Cloud CLI를 사용하거나 Go용 Spanner 클라이언트 라이브러리를 프로그래매틱 방식으로 사용하여 이 인터페이스에 액세스할 수 있습니다.

명령줄에서

다음 SQL 문을 실행하여 Albums 테이블에서 모든 열의 값을 읽습니다.

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용 Spanner 클라이언트 라이브러리 사용

명령줄에서 SQL 문 실행 외에도 Go용 Spanner 클라이언트 라이브러리를 프로그래매틱 방식으로 사용하여 같은 SQL 문을 실행할 수 있습니다.

SQL 쿼리를 실행할 때는 다음과 같은 메서드와 유형이 사용됩니다.

  • Client.Single(): Spanner 테이블에 있는 행 하나 이상에서 열 값을 하나 이상 읽을 때 사용합니다. Client.Single은 읽기 또는 SQL 문을 실행하는 데 사용되는 ReadOnlyTransaction을 반환합니다.
  • ReadOnlyTransaction.Query(): 데이터베이스에 쿼리를 실행하려면 이 메서드를 사용합니다.
  • Statement 유형: SQL 문자열을 생성할 때 사용합니다.
  • Row 유형: SQL 문 또는 읽기 호출에서 반환된 데이터에 액세스할 때 사용합니다.

다음은 쿼리를 실행하고 데이터에 액세스하는 방법입니다.


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
	"google.golang.org/api/iterator"
)

func query(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 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

SQL 매개변수를 사용하여 쿼리

애플리케이션에 자주 실행되는 쿼리가 있는 경우 이를 매개변수화하여 성능을 개선할 수 있습니다. 매개변수화된 결과 쿼리를 캐시하고 다시 사용할 수 있으므로 컴파일 비용이 절감됩니다. 자세한 내용은 쿼리 매개변수를 사용하여 자주 실행되는 쿼리 속도 향상을 참조하세요.

다음 예시에서는 WHERE 절의 매개변수를 사용하여 LastName의 특정 값이 포함된 레코드를 쿼리합니다.

GoogleSQL


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
	"google.golang.org/api/iterator"
)

func queryWithParameter(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 SingerId, FirstName, LastName FROM Singers
			WHERE LastName = @lastName`,
		Params: map[string]interface{}{
			"lastName": "Garcia",
		},
	}
	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 int64
		var firstName, lastName string
		if err := row.Columns(&singerID, &firstName, &lastName); err != nil {
			return err
		}
		fmt.Fprintf(w, "%d %s %s\n", singerID, firstName, lastName)
	}
}

PostgreSQL


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
	"google.golang.org/api/iterator"
)

// pgQueryParameter shows how to execute a query with parameters on a Spanner
// PostgreSQL database. The PostgreSQL dialect uses positional parameters, as
// opposed to the named parameters of Cloud Spanner.
func pgQueryParameter(w io.Writer, db string) error {
	// db := "projects/my-project/instances/my-instance/databases/my-database"
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	stmt := spanner.Statement{
		SQL: `SELECT SingerId, FirstName, LastName FROM Singers
			WHERE LastName = $1`,
		Params: map[string]interface{}{
			"p1": "Garcia",
		},
	}
	type Singers struct {
		SingerID            int64
		FirstName, LastName string
	}
	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 val Singers
		if err := row.ToStruct(&val); err != nil {
			return err
		}
		fmt.Fprintf(w, "%d %s %s\n", val.SingerID, val.FirstName, val.LastName)
	}
}

Google SQL의 경우 querywithparameter 인수를, PostgreSQL의 경우 pgqueryparameter 인수를 사용하여 샘플을 실행합니다.

GoogleSQL

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

PostgreSQL

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

다음과 비슷한 출력이 표시됩니다.

12 Melissa Garcia

읽기 API를 사용하여 데이터 읽기

Spanner는 Spanner의 SQL 인터페이스 외에도 읽기 인터페이스도 지원합니다.

ReadOnlyTransaction.Read()를 사용하여 데이터베이스의 행을 읽습니다. KeySet을 사용하여 읽을 키 모음과 키 범위를 정의합니다.

다음은 데이터를 읽는 방법입니다.


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
	"google.golang.org/api/iterator"
)

func read(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().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

데이터베이스 스키마 업데이트

Albums 테이블에 MarketingBudget이라는 새 열을 추가해야 한다고 가정합니다. 기존 테이블에 새 열을 추가하려면 데이터베이스 스키마를 업데이트해야 합니다. Spanner는 데이터베이스에서 트래픽이 계속 처리되는 동안 데이터베이스의 스키마 업데이트를 지원합니다. 스키마 업데이트 시 데이터베이스를 오프라인으로 전환할 필요가 없고 전체 테이블 또는 열을 잠그지 않습니다. 스키마 업데이트 중에도 데이터베이스에 계속 데이터를 쓸 수 있습니다. 스키마 업데이트에서 지원되는 스키마 업데이트와 스키마 변경 성능에 대해 자세히 알아보세요.

열 추가

명령줄에서 Google Cloud CLI를 사용하거나 Go용 Spanner 클라이언트 라이브러리를 프로그래매틱 방식으로 사용하여 열을 추가할 수 있습니다.

명령줄에서

다음과 같은 ALTER TABLE 명령어를 사용하여 테이블에 새 열을 추가합니다.

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'

다음과 같이 표시됩니다.

Schema updating...done.

Go용 Spanner 클라이언트 라이브러리 사용

DatabaseAdminClient.UpdateDatabaseDdl()를 사용하여 스키마를 수정합니다.

GoogleSQL


import (
	"context"
	"fmt"
	"io"

	database "cloud.google.com/go/spanner/admin/database/apiv1"
	adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)

func addNewColumn(ctx context.Context, w io.Writer, db string) error {
	adminClient, err := database.NewDatabaseAdminClient(ctx)
	if err != nil {
		return err
	}
	defer adminClient.Close()

	op, err := adminClient.UpdateDatabaseDdl(ctx, &adminpb.UpdateDatabaseDdlRequest{
		Database: db,
		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
}

PostgreSQL


import (
	"context"
	"fmt"
	"io"

	database "cloud.google.com/go/spanner/admin/database/apiv1"
	adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)

func pgAddNewColumn(ctx context.Context, w io.Writer, db string) error {
	adminClient, err := database.NewDatabaseAdminClient(ctx)
	if err != nil {
		return err
	}
	defer adminClient.Close()

	op, err := adminClient.UpdateDatabaseDdl(ctx, &adminpb.UpdateDatabaseDdlRequest{
		Database: db,
		Statements: []string{
			"ALTER TABLE Albums ADD COLUMN MarketingBudget bigint",
		},
	})
	if err != nil {
		return err
	}
	if err := op.Wait(ctx); err != nil {
		return err
	}
	fmt.Fprintf(w, "Added MarketingBudget column\n")
	return nil
}

Google SQL의 경우 addnewcolumn 인수를, PostgreSQL의 경우 pgaddnewcolumn 인수를 사용하여 샘플을 실행합니다.

GoogleSQL

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

PostgreSQL

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

다음과 같이 표시됩니다.

Added MarketingBudget column.

새 열에 데이터 쓰기

다음 코드는 새 열에 데이터를 씁니다. 이 코드는 MarketingBudgetAlbums(1, 1)로 키가 지정된 행에서는 100000으로, Albums(2, 2)로 키가 지정된 행에서는 500000으로 설정합니다.

import (
	"context"
	"io"

	"cloud.google.com/go/spanner"
)

func update(w io.Writer, db string) error {
	ctx := context.Background()

	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	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 쿼리 또는 읽기 호출을 실행할 수도 있습니다.

다음은 쿼리를 실행하는 코드입니다.

GoogleSQL


import (
	"context"
	"fmt"
	"io"
	"strconv"

	"cloud.google.com/go/spanner"
	"google.golang.org/api/iterator"
)

func queryNewColumn(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 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)
	}
}

PostgreSQL


import (
	"context"
	"fmt"
	"io"
	"strconv"

	"cloud.google.com/go/spanner"
	"google.golang.org/api/iterator"
)

func pgQueryNewColumn(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 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)
	}
}

이 쿼리를 실행하려면 Google SQL의 경우 querynewcolumn 인수를, PostgreSQL의 경우 pgquerynewcolumn 인수를 사용하여 샘플을 실행합니다.

GoogleSQL

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

PostgreSQL

go run snippet.go pgquerynewcolumn 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 문을 실행합니다.

GoogleSQL


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
)

func writeWithTransactionUsingDML(w io.Writer, db string) error {
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	_, 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
		album2Budget, err := getBudget(2, 2)
		if 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 album2Budget >= transferAmt {
			album1Budget, err := getBudget(1, 1)
			if 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 Album2's MarketingBudget to Album1's.", transferAmt)
		}
		return nil
	})
	return err
}

PostgreSQL


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
)

func pgWriteWithTransactionUsingDML(w io.Writer, db string) error {
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	_, 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, fmt.Errorf("error reading marketing budget for album_id=%v,singer_id=%v: %w",
					albumID, singerID, err)
			}
			var budget int64
			if err := row.Column(0, &budget); err != nil {
				return 0, fmt.Errorf("error decoding marketing budget for album_id=%v,singer_id=%v: %w",
					albumID, singerID, 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 = $1
					WHERE SingerId = $2 and AlbumId = $3`,
				Params: map[string]interface{}{
					"p1": albumBudget,
					"p2": singerID,
					"p3": albumID,
				},
			}
			_, 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
		album2Budget, err := getBudget(2, 2)
		if 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 album2Budget >= transferAmt {
			album1Budget, err := getBudget(1, 1)
			if 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 Album2's MarketingBudget to Album1's.", transferAmt)
		}
		return nil
	})
	return err
}

dmlwritetxn 인수를 사용하여 샘플을 실행합니다.

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

다음과 같이 표시됩니다.

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

보조 색인 사용

Albums에서 특정 범위의 AlbumTitle 값이 있는 모든 행을 가져오려고 한다고 가정합니다. SQL 문 또는 읽기 호출을 사용하여 AlbumTitle 열에서 모든 값을 읽은 다음 기준을 충족하지 않는 행을 삭제할 수 있지만 이렇게 전체 테이블 스캔을 수행하는 것은 비용이 많이 들며, 특히 많은 행이 있는 테이블의 경우에는 더욱 그렇습니다. 대신 테이블에 보조 색인을 만들어 기본 키가 아닌 열로 검색하면 행을 빠르게 검색할 수 있습니다.

기존 테이블에 보조 색인을 추가하려면 스키마를 업데이트해야 합니다. 다른 스키마 업데이트와 같이 Spanner는 데이터베이스에서 트래픽이 계속 처리되는 동안 색인을 추가할 수 있습니다. Spanner는 기존 데이터로 색인을 자동 백필합니다. 백필을 완료하는 데 몇 분 정도 걸릴 수 있지만 이 프로세스가 진행되는 동안 데이터베이스를 오프라인으로 전환하거나 색인이 생성되는 테이블에 대한 쓰기를 금지할 필요는 없습니다. 자세한 내용은 보조 색인 추가를 참조하세요.

보조 색인을 추가하고 나면 Spanner는 보조 색인 사용 시 더 빨리 실행될 가능성이 높은 SQL 쿼리에 자동으로 보조 색인을 사용합니다. 읽기 인터페이스를 사용하는 경우에는 사용할 색인을 지정해야 합니다.

보조 색인 추가

명령줄에서 gcloud CLI를 사용하거나 Go용 Spanner 클라이언트 라이브러리를 프로그래매틱 방식으로 사용하여 색인을 추가할 수 있습니다.

명령줄에서

다음과 같은 CREATE INDEX 명령어를 사용하여 데이터베이스에 색인을 추가합니다.

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

다음과 같이 표시됩니다.

Schema updating...done.

Go용 Spanner 클라이언트 라이브러리 사용

UpdateDatabaseDdl()를 사용하여 색인을 추가합니다.


import (
	"context"
	"fmt"
	"io"

	database "cloud.google.com/go/spanner/admin/database/apiv1"
	adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)

func addIndex(ctx context.Context, w io.Writer, db string) error {
	adminClient, err := database.NewDatabaseAdminClient(ctx)
	if err != nil {
		return err
	}
	defer adminClient.Close()

	op, err := adminClient.UpdateDatabaseDdl(ctx, &adminpb.UpdateDatabaseDdlRequest{
		Database: db,
		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

색인을 사용하여 읽기

SQL 쿼리의 경우 Spanner는 자동으로 적절한 색인을 사용합니다. 읽기 인터페이스에서는 사용자가 직접 요청에서 색인을 지정해야 합니다.

읽기 인터페이스에서 색인을 사용하려면 색인을 사용하여 데이터베이스에서 0개 이상의 행을 읽는 ReadOnlyTransaction.ReadUsingIndex()를 사용합니다.

다음 코드는 AlbumsByAlbumTitle 색인에서 모든 AlbumIdAlbumTitle 열을 가져옵니다.


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

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

색인 전용 읽기에 필요한 색인 추가

이전 읽기 예시에는 MarketingBudget 열 읽기가 포함되지 않은 것을 확인할 수 있습니다. 이는 Spanner의 읽기 인터페이스가 색인에 저장되지 않은 값을 찾기 위해 색인을 데이터 테이블에 조인하는 기능을 지원하지 않기 때문입니다.

색인에 MarketingBudget 사본을 저장하는 AlbumsByAlbumTitle 대체 정의를 만듭니다.

명령줄에서

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)

색인을 추가하는 데 몇 분 정도 걸릴 수 있습니다. 색인이 추가되면 다음과 같이 표시됩니다.

Schema updating...done.

Go용 Spanner 클라이언트 라이브러리 사용

UpdateDatabaseDdl()을 사용하여 GoogleSQL의 경우 STORING 절을, PostgreSQL의 경우 INCLUDE 절을 사용하여 색인을 추가합니다.

GoogleSQL


import (
	"context"
	"fmt"
	"io"

	database "cloud.google.com/go/spanner/admin/database/apiv1"
	adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)

func addStoringIndex(ctx context.Context, w io.Writer, db string) error {
	adminClient, err := database.NewDatabaseAdminClient(ctx)
	if err != nil {
		return err
	}
	defer adminClient.Close()

	op, err := adminClient.UpdateDatabaseDdl(ctx, &adminpb.UpdateDatabaseDdlRequest{
		Database: db,
		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
}

PostgreSQL


import (
	"context"
	"fmt"
	"io"

	database "cloud.google.com/go/spanner/admin/database/apiv1"
	adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)

// pgAddStoringIndex shows how to create 'STORING' indexes on a Spanner
// PostgreSQL database. The PostgreSQL dialect uses INCLUDE keyword, as
// opposed to the STORING keyword of Cloud Spanner.
func pgAddStoringIndex(ctx context.Context, w io.Writer, db string) error {
	// db := "projects/my-project/instances/my-instance/databases/my-database"
	adminClient, err := database.NewDatabaseAdminClient(ctx)
	if err != nil {
		return fmt.Errorf("failed to initialize spanner database admin client: %w", err)
	}
	defer adminClient.Close()

	op, err := adminClient.UpdateDatabaseDdl(ctx, &adminpb.UpdateDatabaseDdlRequest{
		Database: db,
		Statements: []string{
			"CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) INCLUDE (MarketingBudget)",
		},
	})
	if err != nil {
		return fmt.Errorf("failed to execute spanner database DDL request: %w", err)
	}
	if err := op.Wait(ctx); err != nil {
		return fmt.Errorf("failed to complete spanner database DDL request: %w", err)
	}
	fmt.Fprintf(w, "Added storing index\n")
	return nil
}

addstoringindex 인수를 사용하여 샘플을 실행합니다.

GoogleSQL

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

PostgreSQL

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

색인을 추가하는 데 몇 분 정도 걸릴 수 있습니다. 색인이 추가되면 다음과 같이 표시됩니다.

Added storing index

이제 AlbumsByAlbumTitle2 색인의 AlbumId, AlbumTitle, MarketingBudget 열을 모두 가져오는 읽기를 실행할 수 있습니다.


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

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

읽기 전용 트랜잭션을 사용하여 데이터 검색

같은 타임스탬프에서 읽기를 하나 이상 실행한다고 가정해 봅시다. 읽기 전용 트랜잭션은 트랜잭션 커밋 기록의 일관된 프리픽스를 관찰하므로 애플리케이션이 항상 일관된 데이터를 가져옵니다. ReadOnlyTransaction 유형을 사용하여 읽기 전용 트랜잭션을 실행합니다. Client.ReadOnlyTransaction()을 사용하여 ReadOnlyTransaction을 가져옵니다.

다음은 같은 읽기 전용 트랜잭션에서 쿼리를 실행하고 읽기를 수행하는 방법을 보여줍니다.


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
	"google.golang.org/api/iterator"
)

func readOnlyTransaction(w io.Writer, db string) error {
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	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

삭제

이 튜토리얼에서 사용한 리소스에 대한 추가 비용이 Cloud Billing 계정에 청구되지 않도록 하려면 데이터베이스와 새로 만든 인스턴스를 삭제합니다.

데이터베이스 삭제

인스턴스를 삭제하면 인스턴스 내의 모든 데이터베이스가 자동으로 삭제됩니다. 다음 단계는 인스턴스를 삭제하지 않고 데이터베이스를 삭제하는 방법을 보여줍니다. 인스턴스에 대한 비용은 여전히 발생합니다.

명령줄에서

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

Google Cloud 콘솔 사용

  1. Google Cloud 콘솔에서 Spanner 인스턴스 페이지로 이동합니다.

    인스턴스 페이지로 이동

  2. 인스턴스를 클릭합니다.

  3. 삭제할 데이터베이스를 클릭합니다.

  4. 데이터베이스 세부정보 페이지에서 삭제를 클릭합니다.

  5. 데이터베이스 삭제 여부를 확인하고 삭제를 클릭합니다.

인스턴스 삭제

인스턴스를 삭제하면 해당 인스턴스에서 만든 모든 데이터베이스가 자동으로 삭제됩니다.

명령줄에서

gcloud spanner instances delete test-instance

Google Cloud 콘솔 사용

  1. Google Cloud 콘솔에서 Spanner 인스턴스 페이지로 이동합니다.

    인스턴스 페이지로 이동

  2. 인스턴스를 클릭합니다.

  3. 삭제를 클릭합니다.

  4. 인스턴스 삭제 여부를 확인하고 삭제를 클릭합니다.

다음 단계