תחילת העבודה עם Spanner ב-Go

מטרות

במדריך הזה נסביר איך לבצע את השלבים הבאים באמצעות ספריית הלקוח של Spanner ל-Go:‏

  • יוצרים מכונה ומסד נתונים ב-Spanner.
  • לכתוב, לקרוא ולהריץ שאילתות SQL על נתונים במסד הנתונים.
  • מעדכנים את הסכימה של מסד הנתונים.
  • עדכון נתונים באמצעות טרנזקציה של קריאה וכתיבה.
  • מוסיפים אינדקס משני למסד הנתונים.
  • השימוש באינדקס מאפשר לקרוא ולהריץ שאילתות SQL על נתונים.
  • אחזור נתונים באמצעות טרנזקציה לקריאה בלבד.

עלויות

במדריך הזה נעשה שימוש ב-Spanner, שהוא רכיב בתשלום שלGoogle Cloud. מידע על עלות השימוש ב-Spanner מופיע בקטע תמחור.

לפני שמתחילים

צריך לבצע את השלבים שמפורטים במאמר הגדרה, שכוללים יצירה והגדרה של פרויקט ברירת מחדל Google Cloud , הפעלת החיוב, הפעלת Cloud Spanner API והגדרת OAuth 2.0 כדי לקבל אישורי אימות לשימוש ב-Cloud Spanner API.

בפרט, חשוב להריץ את הפקודה 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. מגדירים את משתנה הסביבה PROJECT_ID למזהה הפרויקט ב- Google Cloud :

    export PROJECT_ID=[MY_PROJECT_ID]
    

יצירת מופע

בפעם הראשונה שמשתמשים ב-Spanner, צריך ליצור מופע, שהוא הקצאה של משאבים שמשמשים מסדי נתונים של Spanner. כשיוצרים מופע, בוחרים הגדרת מופע, שקובעת איפה הנתונים מאוחסנים, וגם את מספר הצמתים לשימוש, שקובע את כמות משאבי ההגשה והאחסון במופע.

במאמר יצירת מכונה מוסבר איך ליצור מכונת Spanner באמצעות אחת מהשיטות הבאות. אפשר לתת למופע שם test-instance כדי להשתמש בו עם נושאים אחרים במסמך הזה שמפנים למופע בשם test-instance.

  • ‫Google Cloud CLI
  • מסוף Google Cloud
  • ספריית לקוח (C++‎,‏ C#‎,‏ Go,‏ Java,‏ Node.js,‏ PHP,‏ Python או Ruby)

עיון בקבצים לדוגמה

מאגר הדוגמאות מכיל דוגמה שמראה איך להשתמש ב-Spanner עם Go.

מעיינים בקובץ snippet.go שבו מוסבר איך להשתמש ב-Spanner. בדוגמת הקוד מוצג איך ליצור מסד נתונים חדש ולהשתמש בו. הנתונים מבוססים על סכימת הדוגמה שמוצגת בדף סכימה ומודל נתונים.

יצירת מסד נתונים

GoogleSQL

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

PostgreSQL

go run snippet.go pgcreatedatabase projects/PROJECT_ID/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 "cloud.google.com/go/spanner/admin/database/apiv1/databasepb"
)

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 "cloud.google.com/go/spanner/admin/database/apiv1/databasepb"
)

// 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) בעסקת קריאה-כתיבה.

משתמשים ב-Update() method כדי להריץ פקודת 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
}

מריצים את הדוגמה באמצעות הארגומנט dmlwrite ל-Google SQL והארגומנט pgdmlwrite ל-PostgreSQL:

GoogleSQL

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

PostgreSQL

go run snippet.go pgdmlwrite projects/PROJECT_ID/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/PROJECT_ID/instances/test-instance/databases/example-db

הפקודה אמורה לפעול בהצלחה.

הרצת שאילתות על נתונים באמצעות SQL

‫Spanner תומך בממשק SQL לקריאת נתונים, שאפשר לגשת אליו בשורת הפקודה באמצעות Google Cloud CLI או באופן פרוגרמטי באמצעות ספריית הלקוח של Spanner ל-Go.

בשורת הפקודה

מריצים את הצהרת ה-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

שימוש בספריית הלקוח של Spanner ל-Go

בנוסף להרצת הצהרת SQL בשורת הפקודה, אפשר להריץ את אותה הצהרת SQL באופן פרוגרמטי באמצעות ספריית הלקוח של Spanner ל-Go.

השיטות והסוגים הבאים משמשים להרצת שאילתת ה-SQL:

  • Client.Single(): משתמשים בפונקציה הזו כדי לקרוא את הערך של עמודה אחת או יותר משורה אחת או יותר בטבלת Spanner. ‫Client.Single מחזירה את הערך a ‫ReadOnlyTransaction, שמשמש להרצת שאילתת קריאה או הצהרת SQL.
  • 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/PROJECT_ID/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)
	}
}

מריצים את הדוגמה באמצעות הארגומנט querywithparameter עבור Google SQL והארגומנט pgqueryparameter עבור PostgreSQL.

GoogleSQL

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

PostgreSQL

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

הפלט אמור להיראות כך:

12 Melissa Garcia

קריאת נתונים באמצעות read API

בנוסף לממשק ה-SQL של Spanner, ‏ Spanner תומך גם בממשק קריאה.

כדי לקרוא שורות ממסד הנתונים, משתמשים ב-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/PROJECT_ID/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. כדי להוסיף עמודה חדשה לטבלה קיימת, צריך לעדכן את סכימת מסד הנתונים. מערכת Spanner תומכת בעדכוני סכימה במסד נתונים בזמן שמסד הנתונים ממשיך לשרת תנועה. עדכוני סכימה לא מחייבים להעביר את מסד הנתונים למצב אופליין, והם לא נועלים טבלאות או עמודות שלמות. אתם יכולים להמשיך לכתוב נתונים למסד הנתונים במהלך עדכון הסכימה. מידע נוסף על עדכוני סכימה נתמכים ועל ביצועים של שינויים בסכימה זמין במאמר ביצוע עדכונים בסכימה.

הוספת עמודה

אפשר להוסיף עמודה בשורת הפקודה באמצעות Google Cloud CLI או באופן פרוגרמטי באמצעות ספריית הלקוח של Spanner ל-Go.

בשורת הפקודה

כדי להוסיף את העמודה החדשה לטבלה, משתמשים בפקודה 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.

שימוש בספריית הלקוח של Spanner ל-Go

משתמשים ב-DatabaseAdminClient.UpdateDatabaseDdl() כדי לשנות את הסכימה:

GoogleSQL


import (
	"context"
	"fmt"
	"io"

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

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 "cloud.google.com/go/spanner/admin/database/apiv1/databasepb"
)

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
}

מריצים את הדוגמה באמצעות הארגומנט addnewcolumn עבור Google SQL והארגומנט pgaddnewcolumn עבור PostgreSQL.

GoogleSQL

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

PostgreSQL

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

הפרטים שמוצגים הם:

Added MarketingBudget column.

כתיבת נתונים בעמודה החדשה

הקוד הבא כותב נתונים בעמודה החדשה. הפונקציה מגדירה את MarketingBudget ל-100000 בשורה עם מפתח Albums(1, 1), ול-500000 בשורה עם מפתח Albums(2, 2).

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

כדי להריץ את השאילתה הזו, מריצים את הדוגמה באמצעות הארגומנט querynewcolumn עבור Google SQL והארגומנט pgquerynewcolumn עבור PostgreSQL.

GoogleSQL

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

PostgreSQL

go run snippet.go pgquerynewcolumn projects/PROJECT_ID/instances/test-instance/databases/example-db

הפרטים שמוצגים הם:

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

עדכון נתונים

אפשר לעדכן נתונים באמצעות DML בעסקת קריאה-כתיבה.

משתמשים ב-Update() method כדי להריץ פקודת 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/PROJECT_ID/instances/test-instance/databases/example-db

הפרטים שמוצגים הם:

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

שימוש באינדקס משני

נניח שרוצים לאחזר את כל השורות של Albums שיש להן ערכים של AlbumTitle בטווח מסוים. אפשר לקרוא את כל הערכים מהעמודה AlbumTitle באמצעות הצהרת SQL או קריאת קובץ, ואז להשליך את השורות שלא עומדות בקריטריונים. אבל סריקה מלאה של הטבלה היא פעולה יקרה, במיוחד בטבלאות עם הרבה שורות. במקום זאת, אפשר להאיץ את אחזור השורות כשמחפשים לפי עמודות שאינן מפתח ראשי, על ידי יצירת אינדקס משני בטבלה.

כדי להוסיף אינדקס משני לטבלה קיימת, צריך לעדכן את הסכימה. בדומה לעדכוני סכימה אחרים, Spanner תומך בהוספת אינדקס בזמן שהמסד נתונים ממשיך להעביר תנועה. ‫Spanner ממלא באופן אוטומטי את האינדקס בנתונים הקיימים. יכול להיות שיחלפו כמה דקות עד שהמילוי יסתיים, אבל לא צריך להעביר את מסד הנתונים למצב אופליין או להימנע מכתיבה לטבלה המאונדקסת במהלך התהליך הזה. פרטים נוספים זמינים במאמר בנושא הוספת אינדקס משני.

אחרי שמוסיפים אינדקס משני, Spanner משתמש בו באופן אוטומטי לשאילתות SQL שסביר שיפעלו מהר יותר עם האינדקס. אם משתמשים בממשק הקריאה, צריך לציין את האינדקס שרוצים להשתמש בו.

הוספת אינדקס משני

אפשר להוסיף אינדקס בשורת הפקודה באמצעות ה-CLI של gcloud או באופן פרוגרמטי באמצעות ספריית הלקוח של Spanner ל-Go.

בשורת הפקודה

משתמשים בפקודה CREATE INDEX הבאה כדי להוסיף אינדקס למסד הנתונים:

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

הפרטים שמוצגים הם:

Schema updating...done.

שימוש בספריית הלקוח של Spanner ל-Go

כדי להוסיף אינדקס, משתמשים ב-UpdateDatabaseDdl():


import (
	"context"
	"fmt"
	"io"

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

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 משתמש באופן אוטומטי באינדקס מתאים. בממשק הקריאה, צריך לציין את האינדקס בבקשה.

כדי להשתמש באינדקס בממשק הקריאה, משתמשים בפקודה ReadOnlyTransaction.ReadUsingIndex(), שקוראת אפס שורות או יותר ממסד נתונים באמצעות אינדקס.

הקוד הבא מאחזר את כל העמודות AlbumId ו-AlbumTitle מהאינדקס AlbumsByAlbumTitle.


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/PROJECT_ID/instances/test-instance/databases/example-db

הפרטים שמוצגים הם:

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

הוספת אינדקס לקריאות של אינדקס בלבד

יכול להיות ששמתם לב שבדוגמה הקודמת של קריאה לא נכללת קריאה של העמודה MarketingBudget. הסיבה לכך היא שממשק הקריאה של Spanner לא תומך באפשרות של צירוף אינדקס לטבלת נתונים כדי לחפש ערכים שלא מאוחסנים באינדקס.

יוצרים הגדרה חלופית של AlbumsByAlbumTitle ששומרת עותק של MarketingBudget באינדקס.

בשורת הפקודה

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.

שימוש בספריית הלקוח של Spanner ל-Go

משתמשים ב-UpdateDatabaseDdl() כדי להוסיף אינדקס עם פסקה STORING ל-GoogleSQL ופסקה INCLUDE ל-PostgreSQL:

GoogleSQL


import (
	"context"
	"fmt"
	"io"

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

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 "cloud.google.com/go/spanner/admin/database/apiv1/databasepb"
)

// 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/PROJECT_ID/instances/test-instance/databases/example-db

PostgreSQL

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

הוספת אינדקס יכולה להימשך כמה דקות. אחרי שמוסיפים את האינדקס, אמורים לראות:

Added storing index

עכשיו אפשר להריץ קריאה שמביאה את כל העמודות AlbumId, AlbumTitle ו-MarketingBudget מהאינדקס AlbumsByAlbumTitle2:


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/PROJECT_ID/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/PROJECT_ID/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 על המשאבים שבהם השתמשתם במדריך הזה, צריך להשליך את מסד הנתונים ולמחוק את המופע שיצרתם.

מחיקת מסד הנתונים

אם מוחקים מופע, כל מסדי הנתונים שבו נמחקים אוטומטית. בשלב הזה נסביר איך למחוק מסד נתונים בלי למחוק את המופע (עדיין תחויבו על המופע).

בשורת הפקודה

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

שימוש במסוף Google Cloud

  1. נכנסים לדף Spanner Instances במסוף Google Cloud .

    כניסה לדף Instances

  2. לוחצים על המופע.

  3. לוחצים על מסד הנתונים שרוצים למחוק.

  4. בדף פרטי מסד הנתונים, לוחצים על מחיקה.

  5. מאשרים שרוצים למחוק את מסד הנתונים ולוחצים על מחיקה.

מחיקת המכונה

מחיקת מופע תגרום להסרה אוטומטית של כל מסדי הנתונים שנוצרו במופע הזה.

בשורת הפקודה

gcloud spanner instances delete test-instance

שימוש במסוף Google Cloud

  1. נכנסים לדף Spanner Instances במסוף Google Cloud .

    כניסה לדף Instances

  2. לוחצים על המופע.

  3. לוחצים על Delete.

  4. מאשרים שרוצים למחוק את המופע ולוחצים על מחיקה.

המאמרים הבאים