1. Übersicht
Google Cloud Spanner ist ein vollständig verwalteter, horizontal skalierbarer, global verteilter, relationaler Datenbankdienst, der ACID-Transaktionen und SQL-Semantik bietet, ohne Leistung und Hochverfügbarkeit zu beeinträchtigen.
In diesem Lab erfahren Sie, wie Sie eine Cloud Spanner-Instanz einrichten. Sie durchlaufen die Schritte zum Erstellen einer Datenbank und eines Schemas, die für eine Bestenliste für Spiele verwendet werden können. Zuerst erstellen Sie eine Tabelle „Players“ zum Speichern von Spielerinformationen und eine Tabelle „Scores“ zum Speichern von Spielergebnissen.
Als Nächstes füllen Sie die Tabellen mit Beispieldaten. Zum Schluss führen Sie einige Beispielabfragen für die zehn wichtigsten Elemente aus und löschen dann die Instanz, um Ressourcen freizugeben.
Lerninhalte
- Cloud Spanner-Instanz einrichten
- Datenbank und Tabellen erstellen
- Commit-Zeitstempelspalte verwenden
- Daten mit Zeitstempeln in die Cloud Spanner-Datenbanktabelle laden
- Cloud Spanner-Datenbank abfragen
- Cloud Spanner-Instanz löschen
W****as Sie benötigen
Wie werden Sie diese Anleitung verwenden?
Wie würden Sie Ihre Erfahrung mit der Google Cloud Platform bewerten?
2. Einrichtung und Anforderungen
Umgebung zum selbstbestimmten Lernen einrichten
Wenn Sie noch kein Google-Konto (Gmail oder Google Apps) haben, müssen Sie eines erstellen. Melden Sie sich in der Google Cloud Console ( console.cloud.google.com) an und erstellen Sie ein neues Projekt.
Wenn Sie bereits ein Projekt haben, klicken Sie oben links in der Console auf das Drop-down-Menü zur Projektauswahl:

Klicken Sie im angezeigten Dialogfeld auf die Schaltfläche „NEUES PROJEKT“, um ein neues Projekt zu erstellen:

Wenn Sie noch kein Projekt haben, wird ein Dialogfeld wie das folgende angezeigt, in dem Sie Ihr erstes Projekt erstellen können:

Im nachfolgenden Dialogfeld zum Erstellen von Projekten können Sie die Details Ihres neuen Projekts eingeben:

Merken Sie sich die Projekt-ID. Sie ist für alle Google Cloud-Projekte ein eindeutiger Name. Der Name oben ist bereits vergeben und kann nicht verwendet werden. Sie wird später in diesem Codelab als PROJECT_ID bezeichnet.
Als Nächstes müssen Sie, falls noch nicht geschehen, die Abrechnung in der Entwicklerkonsole aktivieren, um Google Cloud-Ressourcen verwenden zu können, und die Cloud Spanner API aktivieren.

Dieses Codelab sollte Sie nicht mehr als ein paar Dollar kosten, aber es könnte mehr sein, wenn Sie sich für mehr Ressourcen entscheiden oder wenn Sie sie laufen lassen (siehe Abschnitt „Bereinigen“ am Ende dieses Dokuments). Die Preise für Google Cloud Spanner sind hier dokumentiert.
Neuen Nutzern der Google Cloud Platform steht eine kostenlose Testversion mit einem Guthaben von 300$ zur Verfügung. Dieses Codelab sollte damit vollständig kostenlos sein.
Google Cloud Shell einrichten
Während Sie Google Cloud und Spanner von Ihrem Laptop aus per Fernzugriff nutzen können, wird in diesem Codelab Google Cloud Shell verwendet, eine Befehlszeilenumgebung, die in der Cloud ausgeführt wird.
Diese Debian-basierte virtuelle Maschine verfügt über alle Entwicklungstools, die Sie benötigen. Sie bietet ein Basisverzeichnis mit 5 GB nichtflüchtigem Speicher und läuft in Google Cloud, was die Netzwerkleistung und Authentifizierung erheblich verbessert. Für dieses Codelab benötigen Sie also nur einen Browser (es funktioniert auch auf einem Chromebook).
- Klicken Sie zum Aktivieren von Cloud Shell in der Cloud Console einfach auf Cloud Shell aktivieren
. Die Bereitstellung und Verbindung mit der Umgebung sollte nur wenige Augenblicke dauern.


Sobald die Verbindung mit der Cloud Shell hergestellt ist, sehen Sie, dass Sie bereits authentifiziert sind und für das Projekt schon Ihre PROJECT_ID eingestellt ist.
gcloud auth list
Befehlsausgabe
Credentialed accounts: - <myaccount>@<mydomain>.com (active)
gcloud config list project
Befehlsausgabe
[core] project = <PROJECT_ID>
Wenn das Projekt aus irgendeinem Grund nicht festgelegt ist, führen Sie einfach den folgenden Befehl aus:
gcloud config set project <PROJECT_ID>
Suchst du nach deinem PROJECT_ID? Sehen Sie nach, welche ID Sie in den Einrichtungsschritten verwendet haben, oder suchen Sie sie im Cloud Console-Dashboard:

In Cloud Shell werden auch einige Umgebungsvariablen standardmäßig festgelegt, die für zukünftige Befehle nützlich sein können.
echo $GOOGLE_CLOUD_PROJECT
Befehlsausgabe
<PROJECT_ID>
- Legen Sie zum Schluss die Standardzone und die Projektkonfiguration fest.
gcloud config set compute/zone us-central1-f
Sie können verschiedene Zonen auswählen. Weitere Informationen finden Sie unter Regionen und Zonen.
Zusammenfassung
In diesem Schritt richten Sie Ihre Umgebung ein.
Als Nächstes
Als Nächstes richten Sie eine Cloud Spanner-Instanz ein.
3. Cloud Spanner-Instanz einrichten
In diesem Schritt richten wir unsere Cloud Spanner-Instanz für dieses Codelab ein. Suchen Sie nach dem Spanner-Eintrag
im Dreistrich-Menü oben links
oder suchen Sie nach Spanner, indem Sie „/“ drücken und „Spanner“ eingeben.

Klicken Sie dann auf
und füllen Sie das Formular aus, indem Sie den Instanznamen cloudspanner-leaderboard für Ihre Instanz eingeben, eine Konfiguration auswählen (regionale Instanz) und die Anzahl der Knoten festlegen. Für dieses Codelab benötigen wir nur einen Knoten. Für Produktionsinstanzen und um das Cloud Spanner-SLA zu erfüllen, müssen Sie mindestens drei Knoten in Ihrer Cloud Spanner-Instanz ausführen.
Klicken Sie abschließend auf „Erstellen“. Innerhalb von Sekunden steht Ihnen eine Cloud Spanner-Instanz zur Verfügung.

Im nächsten Schritt verwenden wir die Go-Clientbibliothek, um eine Datenbank und ein Schema in unserer neuen Instanz zu erstellen.
4. Datenbank und Schema erstellen
In diesem Schritt erstellen wir unsere Beispieldatenbank und unser Schema.
Wir verwenden die Go-Clientbibliothek, um zwei Tabellen zu erstellen: eine „Players“-Tabelle für Spielerinformationen und eine „Scores“-Tabelle zum Speichern von Spielergebnissen. Dazu erstellen wir eine Go-Konsolenanwendung in Cloud Shell.
Klonen Sie zuerst den Beispielcode für dieses Codelab von GitHub, indem Sie den folgenden Befehl in Cloud Shell eingeben:
export GO111MODULE=auto
go get -u github.com/GoogleCloudPlatform/golang-samples/spanner/...
Wechseln Sie dann in das Verzeichnis „leaderboard“, in dem Sie Ihre Anwendung erstellen.
cd gopath/src/github.com/GoogleCloudPlatform/golang-samples/spanner/spanner_leaderboard
Der gesamte für dieses Codelab erforderliche Code befindet sich im vorhandenen Verzeichnis golang-samples/spanner/spanner_leaderboard/ als ausführbare Go-Anwendung mit dem Namen leaderboard. Sie dient als Referenz, während Sie das Codelab durcharbeiten. Wir erstellen ein neues Verzeichnis und erstellen nach und nach eine Kopie der Bestenlistenanwendung.
Erstellen Sie mit dem folgenden Befehl ein neues Verzeichnis mit dem Namen „codelab“ für die Anwendung und wechseln Sie dorthin:
mkdir codelab && cd $_
Erstellen Sie nun eine einfache Go-Anwendung mit dem Namen „Leaderboard“, in der die Spanner-Clientbibliothek verwendet wird, um eine Bestenliste mit zwei Tabellen zu erstellen: „Players“ und „Scores“. Das können Sie direkt im Cloud Shell-Editor tun:
Öffnen Sie den Cloud Shell-Editor, indem Sie auf das unten hervorgehobene Symbol „Editor öffnen“ klicken:

Erstellen Sie im Ordner „~/gopath/src/github.com/GoogleCloudPlatform/golang-samples/spanner/codelab“ eine Datei mit dem Namen „leaderboard.go“.
- Achten Sie zuerst darauf, dass der Ordner „codelab“ in der Ordnerliste des Cloud Shell-Editors ausgewählt ist.
- Wählen Sie dann im Menü „Datei“ des Cloud Shell-Editors „Neue Datei“ aus.
- Geben Sie „leaderboard.go“ als Namen für die neue Datei ein.
Dies ist die Hauptdatei der Anwendung, die unseren Anwendungscode und Verweise zur Einbeziehung von Abhängigkeiten enthält.
Um die Datenbank leaderboard und die Tabellen Players und Scores zu erstellen, kopieren Sie den folgenden Go-Code (Strg + C) und fügen Sie ihn in die Datei leaderboard.go ein (Strg + V):
package main
import (
"context"
"flag"
"fmt"
"io"
"log"
"os"
"regexp"
"time"
"cloud.google.com/go/spanner"
database "cloud.google.com/go/spanner/admin/database/apiv1"
adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)
type adminCommand func(ctx context.Context, w io.Writer, adminClient *database.DatabaseAdminClient, database string) error
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 Players(
PlayerId INT64 NOT NULL,
PlayerName STRING(2048) NOT NULL
) PRIMARY KEY(PlayerId)`,
`CREATE TABLE Scores(
PlayerId INT64 NOT NULL,
Score INT64 NOT NULL,
Timestamp TIMESTAMP NOT NULL
OPTIONS(allow_commit_timestamp=true)
) PRIMARY KEY(PlayerId, Timestamp),
INTERLEAVE IN PARENT Players ON DELETE NO ACTION`,
},
})
if err != nil {
return err
}
if _, err := op.Wait(ctx); err != nil {
return err
}
fmt.Fprintf(w, "Created database [%s]\n", db)
return nil
}
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
}
func run(ctx context.Context, adminClient *database.DatabaseAdminClient, dataClient *spanner.Client, w io.Writer,
cmd string, db string, timespan int) error {
// createdatabase command
if cmd == "createdatabase" {
err := createDatabase(ctx, w, adminClient, db)
if err != nil {
fmt.Fprintf(w, "%s failed with %v", cmd, err)
}
return err
}
return nil
}
func main() {
flag.Usage = func() {
fmt.Fprintf(os.Stderr, `Usage: leaderboard <command> <database_name> [command_option]
Command can be one of: createdatabase
Examples:
leaderboard createdatabase projects/my-project/instances/my-instance/databases/example-db
- Create a sample Cloud Spanner database along with sample tables in your project.
`)
}
flag.Parse()
flagCount := len(flag.Args())
if flagCount != 2 {
flag.Usage()
os.Exit(2)
}
cmd, db := flag.Arg(0), flag.Arg(1)
// Set timespan to zero, as it's not currently being used
var timespan int = 0
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancel()
adminClient, dataClient := createClients(ctx, db)
if err := run(ctx, adminClient, dataClient, os.Stdout, cmd, db, timespan); err != nil {
os.Exit(1)
}
}
Speichern Sie die Änderungen, die Sie an der Datei leaderboard.go vorgenommen haben, indem Sie im Cloud Shell-Editor im Menü „Datei“ die Option „Speichern“ auswählen.
In der Datei leaderboard.go im Verzeichnis golang-samples/spanner/spanner_leaderboard finden Sie ein Beispiel dafür, wie Ihre leaderboard.go-Datei aussehen sollte, nachdem Sie den Code zum Aktivieren des Befehls createdatabase hinzugefügt haben.
Führen Sie zum Erstellen Ihrer App in Cloud Shell den Befehl „go build“ im Verzeichnis codelab aus, in dem sich Ihre leaderboard.go-Datei befindet:
go build leaderboard.go
Nachdem Ihre Anwendung erfolgreich erstellt wurde, führen Sie sie in Cloud Shell aus, indem Sie den folgenden Befehl eingeben:
./leaderboard
Die Ausgabe sollte etwa so aussehen:
Usage: leaderboard <command> <database_name> [command_option]
Command can be one of: createdatabase
Examples:
leaderboard createdatabase projects/my-project/instances/my-instance/databases/example-db
- Create a sample Cloud Spanner database along with sample tables in your project.
Aus dieser Antwort geht hervor, dass es sich um die Anwendung Leaderboard handelt, die derzeit einen möglichen Befehl hat: createdatabase. Wir sehen, dass das erwartete Argument für den Befehl createdatabase ein String mit einer bestimmten Instanz-ID und Datenbank-ID ist.
Führen Sie nun den folgenden Befehl aus. Achten Sie darauf, my-project durch die Projekt-ID zu ersetzen, die Sie am Anfang dieses Codelabs erstellt haben.
./leaderboard createdatabase projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard
Nach einigen Sekunden sollte eine Antwort wie die folgende angezeigt werden:
Created database [projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard]
Im Bereich Cloud Spanner-Datenbanken – Übersicht der Cloud Console sollten Ihre neue Datenbank und die neuen Tabellen im Menü auf der linken Seite angezeigt werden.

Im nächsten Schritt aktualisieren wir unsere Anwendung, um einige Daten in Ihre neue Datenbank zu laden.
5. Daten laden
Wir haben jetzt eine Datenbank mit dem Namen leaderboard, die zwei Tabellen enthält: Players und Scores. Jetzt verwenden wir die Go-Clientbibliothek, um die Tabelle Players mit Spielern und die Tabelle Scores mit zufälligen Punktzahlen für jeden Spieler zu füllen.
Öffnen Sie den Cloud Shell-Editor, falls er noch nicht geöffnet ist. Klicken Sie dazu auf das unten hervorgehobene Symbol:

Bearbeiten Sie als Nächstes die Datei leaderboard.go im Cloud Shell-Editor, um den Befehl insertplayers hinzuzufügen, mit dem 100 Spieler in die Tabelle Players eingefügt werden können. Außerdem fügen wir den Befehl insertscores hinzu, mit dem für jeden Spieler in der Tabelle Players vier zufällige Ergebnisse in die Tabelle Scores eingefügt werden können.
Aktualisieren Sie zuerst den imports-Abschnitt oben in der Datei leaderboard.go. Ersetzen Sie den aktuellen Inhalt durch Folgendes:
import (
"context"
"flag"
"fmt"
"io"
"log"
"math/rand"
"os"
"regexp"
"time"
"cloud.google.com/go/spanner"
database "cloud.google.com/go/spanner/admin/database/apiv1"
"google.golang.org/api/iterator"
adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)
Fügen Sie als Nächstes oben in der Datei, direkt unter der Zeile, die mit „type adminCommand …“ beginnt, einen neuen Befehlstyp zusammen mit einer Liste von Befehlen hinzu. Wenn Sie fertig sind, sollte es so aussehen:
type adminCommand func(ctx context.Context, w io.Writer, adminClient *database.DatabaseAdminClient, database string) error
type command func(ctx context.Context, w io.Writer, client *spanner.Client) error
var (
commands = map[string]command{
"insertplayers": insertPlayers,
"insertscores": insertScores,
}
)
Fügen Sie als Nächstes die Funktionen „insertPlayers“ und „insertScores“ unter der vorhandenen Funktion createdatabase() hinzu:
func insertPlayers(ctx context.Context, w io.Writer, client *spanner.Client) error {
// Get number of players to use as an incrementing value for each PlayerName to be inserted
stmt := spanner.Statement{
SQL: `SELECT Count(PlayerId) as PlayerCount FROM Players`,
}
iter := client.Single().Query(ctx, stmt)
defer iter.Stop()
row, err := iter.Next()
if err != nil {
return err
}
var numberOfPlayers int64 = 0
if err := row.Columns(&numberOfPlayers); err != nil {
return err
}
// Initialize values for random PlayerId
rand.Seed(time.Now().UnixNano())
min := 1000000000
max := 9000000000
// Insert 100 player records into the Players table
_, err = client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
stmts := []spanner.Statement{}
for i := 1; i <= 100; i++ {
numberOfPlayers++
playerID := rand.Intn(max-min) + min
playerName := fmt.Sprintf("Player %d", numberOfPlayers)
stmts = append(stmts, spanner.Statement{
SQL: `INSERT INTO Players
(PlayerId, PlayerName)
VALUES (@playerID, @playerName)`,
Params: map[string]interface{}{
"playerID": playerID,
"playerName": playerName,
},
})
}
_, err := txn.BatchUpdate(ctx, stmts)
if err != nil {
return err
}
return nil
})
fmt.Fprintf(w, "Inserted players \n")
return nil
}
func insertScores(ctx context.Context, w io.Writer, client *spanner.Client) error {
playerRecordsFound := false
// Create slice for insert statements
stmts := []spanner.Statement{}
// Select all player records
stmt := spanner.Statement{SQL: `SELECT PlayerId FROM Players`}
iter := client.Single().Query(ctx, stmt)
defer iter.Stop()
// Insert 4 score records into the Scores table for each player in the Players table
for {
row, err := iter.Next()
if err == iterator.Done {
break
}
if err != nil {
return err
}
playerRecordsFound = true
var playerID int64
if err := row.ColumnByName("PlayerId", &playerID); err != nil {
return err
}
// Initialize values for random score and date
rand.Seed(time.Now().UnixNano())
min := 1000
max := 1000000
for i := 0; i < 4; i++ {
// Generate random score between 1,000 and 1,000,000
score := rand.Intn(max-min) + min
// Generate random day within the past two years
now := time.Now()
endDate := now.Unix()
past := now.AddDate(0, -24, 0)
startDate := past.Unix()
randomDateInSeconds := rand.Int63n(endDate-startDate) + startDate
randomDate := time.Unix(randomDateInSeconds, 0)
// Add insert statement to stmts slice
stmts = append(stmts, spanner.Statement{
SQL: `INSERT INTO Scores
(PlayerId, Score, Timestamp)
VALUES (@playerID, @score, @timestamp)`,
Params: map[string]interface{}{
"playerID": playerID,
"score": score,
"timestamp": randomDate,
},
})
}
}
if !playerRecordsFound {
fmt.Fprintln(w, "No player records currently exist. First insert players then insert scores.")
} else {
_, err := client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
// Commit insert statements for all scores to be inserted as a single transaction
_, err := txn.BatchUpdate(ctx, stmts)
return err
})
if err != nil {
return err
}
fmt.Fprintln(w, "Inserted scores")
}
return nil
}
Damit der Befehl insert funktioniert, fügen Sie den folgenden Code in die Funktion „run“ Ihrer Anwendung unter der Anweisung zur Verarbeitung von createdatabase ein und ersetzen Sie die Anweisung return nil :
// insert and query commands
cmdFn := commands[cmd]
if cmdFn == nil {
flag.Usage()
os.Exit(2)
}
err := cmdFn(ctx, w, dataClient)
if err != nil {
fmt.Fprintf(w, "%s failed with %v", cmd, err)
}
return err
Wenn Sie fertig sind, sollte die Funktion run so aussehen:
func run(ctx context.Context, adminClient *database.DatabaseAdminClient, dataClient *spanner.Client, w io.Writer,
cmd string, db string, timespan int) error {
// createdatabase command
if cmd == "createdatabase" {
err := createDatabase(ctx, w, adminClient, db)
if err != nil {
fmt.Fprintf(w, "%s failed with %v", cmd, err)
}
return err
}
// insert and query commands
cmdFn := commands[cmd]
if cmdFn == nil {
flag.Usage()
os.Exit(2)
}
err := cmdFn(ctx, w, dataClient)
if err != nil {
fmt.Fprintf(w, "%s failed with %v", cmd, err)
}
return err
}
Der letzte Schritt, um die „insert“-Funktion in Ihre Anwendung einzufügen, besteht darin, der flag.Usage()-Funktion Hilfetext für die Befehle „insertplayers“ und „insertscores“ hinzuzufügen. Fügen Sie der Funktion flag.Usage() den folgenden Hilfetext hinzu, um Hilfetext für die Einfügebefehle einzufügen:
Fügen Sie die beiden Befehle der Liste der möglichen Befehle hinzu:
Command can be one of: createdatabase, insertplayers, insertscores
Fügen Sie diesen zusätzlichen Hilfetext unter dem Hilfetext für den Befehl createdatabase ein.
leaderboard insertplayers projects/my-project/instances/my-instance/databases/example-db
- Insert 100 sample Player records into the database.
leaderboard insertscores projects/my-project/instances/my-instance/databases/example-db
- Insert sample score data into Scores sample Cloud Spanner database table.
Speichern Sie die Änderungen, die Sie an der Datei leaderboard.go vorgenommen haben, indem Sie im Cloud Shell-Editor im Menü „Datei“ die Option „Speichern“ auswählen.
In der Datei leaderboard.go im Verzeichnis golang-samples/spanner/spanner_leaderboard finden Sie ein Beispiel dafür, wie Ihre leaderboard.go-Datei aussehen sollte, nachdem Sie den Code zum Aktivieren der Befehle insertplayers und insertscores hinzugefügt haben.
Erstellen und führen Sie die Anwendung nun aus, um zu bestätigen, dass die neuen Befehle insertplayers und insertscores in der Liste der möglichen Befehle der Anwendung enthalten sind. Führen Sie den folgenden Befehl aus, um die Anwendung zu erstellen:
go build leaderboard.go
Führen Sie die resultierende Anwendung in Cloud Shell aus, indem Sie den folgenden Befehl eingeben:
./leaderboard
Die Befehle insertplayers und insertscores sollten jetzt in der Standardausgabe der Anwendung enthalten sein:
Usage: leaderboard <command> <database_name> [command_option]
Command can be one of: createdatabase, insertplayers, insertscores
Examples:
leaderboard createdatabase projects/my-project/instances/my-instance/databases/example-db
- Create a sample Cloud Spanner database along with sample tables in your project.
leaderboard insertplayers projects/my-project/instances/my-instance/databases/example-db
- Insert 100 sample Player records into the database.
leaderboard insertscores projects/my-project/instances/my-instance/databases/example-db
- Insert sample score data into Scores sample Cloud Spanner database table.
Führen wir nun den Befehl insertplayers mit denselben Argumentwerten aus, die wir beim Aufrufen des Befehls createdatabase verwendet haben. Achten Sie darauf, my-project durch die Projekt-ID zu ersetzen, die Sie am Anfang dieses Codelabs erstellt haben.
./leaderboard insertplayers projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard
Nach einigen Sekunden sollte eine Antwort wie die folgende angezeigt werden:
Inserted players
Nun verwenden wir die Go-Clientbibliothek, um die Tabelle Scores mit vier zufälligen Punktzahlen sowie Zeitstempeln für jeden Spieler in der Tabelle Players zu füllen.
Die Spalte Timestamp der Tabelle Scores wurde mit der folgenden SQL-Anweisung, die beim vorherigen Ausführen des Befehls create ausgeführt wurde, als „Commit-Zeitstempel“-Spalte definiert:
CREATE TABLE Scores(
PlayerId INT64 NOT NULL,
Score INT64 NOT NULL,
Timestamp TIMESTAMP NOT NULL OPTIONS(allow_commit_timestamp=true)
) PRIMARY KEY(PlayerId, Timestamp),
INTERLEAVE IN PARENT Players ON DELETE NO ACTION
Beachten Sie das Attribut OPTIONS(allow_commit_timestamp=true). Dadurch wird Timestamp zu einer „Commit-Zeitstempel“-Spalte, die bei INSERT- und UPDATE-Vorgängen für eine bestimmte Tabellenzeile automatisch mit dem genauen Transaktionszeitstempel gefüllt werden kann.
Sie können auch eigene Zeitstempelwerte in eine Commit-Zeitstempelspalte einfügen, sofern Sie einen Zeitstempel mit einem Wert in der Vergangenheit einfügen. Das werden wir in diesem Codelab tun.
Führen wir nun den Befehl insertscores mit denselben Argumentwerten aus, die wir beim Aufrufen des Befehls insertplayers verwendet haben. Achten Sie darauf, my-project durch die Projekt-ID zu ersetzen, die Sie am Anfang dieses Codelabs erstellt haben.
./leaderboard insertscores projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard
Nach einigen Sekunden sollte eine Antwort wie die folgende angezeigt werden:
Inserted scores
Beim Ausführen der Funktion insertScores wird das folgende Code-Snippet verwendet, um einen zufällig generierten Zeitstempel mit einem Datum und einer Uhrzeit in der Vergangenheit einzufügen:
now := time.Now()
endDate := now.Unix()
past := now.AddDate(0, -24, 0)
startDate := past.Unix()
randomDateInSeconds := rand.Int63n(endDate-startDate) + startDate
randomDate := time.Unix(randomDateInSeconds, 0)
stmts = append(stmts, spanner.Statement{
SQL: `INSERT INTO Scores
(PlayerId, Score, Timestamp)
VALUES (@playerID, @score, @timestamp)`,
Params: map[string]interface{}{
"playerID": playerID,
"score": score,
"timestamp": randomDate,
},
})
Wenn Sie die Spalte Timestamp automatisch mit dem Zeitstempel der „Insert“-Transaktion füllen möchten, können Sie stattdessen die Go-Konstante spanner.CommitTimestamp einfügen, wie im folgenden Code-Snippet:
...
stmts = append(stmts, spanner.Statement{
SQL: `INSERT INTO Scores
(PlayerId, Score, Timestamp)
VALUES (@playerID, @score, @timestamp)`,
Params: map[string]interface{}{
"playerID": playerID,
"score": score,
"timestamp": spanner.CommitTimestamp,
},
})
Nachdem wir die Daten geladen haben, prüfen wir die Werte, die wir gerade in unsere neuen Tabellen geschrieben haben. Rufen Sie dazu in der Cloud Console den Cloud Spanner-Bereich auf. Wählen Sie zuerst die Datenbank leaderboard und dann die Tabelle Players aus. Klicken Sie auf den Tab Data. Sie sollten sehen, dass die Spalten PlayerId und PlayerName der Tabelle Daten enthalten.

Als Nächstes prüfen wir, ob die Tabelle „Scores“ auch Daten enthält. Klicken Sie dazu auf die Tabelle Scores und wählen Sie den Tab Data aus. Sie sollten sehen, dass die Spalten PlayerId, Timestamp und Score der Tabelle Daten enthalten.

Gut gemacht! Wir aktualisieren unsere App, um einige Abfragen auszuführen, mit denen wir eine Bestenliste für Spiele erstellen können.
6. Bestenlistenabfragen ausführen
Nachdem wir unsere Datenbank eingerichtet und Informationen in unsere Tabellen geladen haben, erstellen wir nun eine Bestenliste mit diesen Daten. Dazu müssen wir die folgenden vier Fragen beantworten:
- Welche Spieler gehören zu den „Top 10“ aller Zeiten?
- Welche Spieler gehören zu den „Top Ten“ des Jahres?
- Welche Spieler gehören zu den Top 10 des Monats?
- Welche Spieler gehören zu den „Top Ten“ der Woche?
Wir aktualisieren unsere Anwendung, damit die SQL-Abfragen ausgeführt werden, mit denen diese Fragen beantwortet werden.
Wir fügen einen query-Befehl und einen queryWithTimespan-Befehl hinzu, mit denen die Abfragen ausgeführt werden können, um die Fragen zu beantworten, die die für unsere Bestenliste erforderlichen Informationen liefern.
Bearbeiten Sie die Datei leaderboard.go im Cloud Shell-Editor, um die Anwendung zu aktualisieren und einen query-Befehl und einen queryWithTimespan-Befehl hinzuzufügen. Außerdem fügen wir eine formatWithCommas-Hilfsfunktion hinzu, um unsere Werte mit Kommas zu formatieren.
Aktualisieren Sie zuerst den imports-Abschnitt oben in der Datei leaderboard.go. Ersetzen Sie den aktuellen Inhalt durch Folgendes:
import (
"bytes"
"context"
"flag"
"fmt"
"io"
"log"
"math/rand"
"os"
"regexp"
"strconv"
"time"
"cloud.google.com/go/spanner"
database "cloud.google.com/go/spanner/admin/database/apiv1"
"google.golang.org/api/iterator"
adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)
Fügen Sie als Nächstes die folgenden beiden Funktionen und die Hilfsfunktion unter der vorhandenen insertScores-Methode hinzu:
func query(ctx context.Context, w io.Writer, client *spanner.Client) error {
stmt := spanner.Statement{
SQL: `SELECT p.PlayerId, p.PlayerName, s.Score, s.Timestamp
FROM Players p
JOIN Scores s ON p.PlayerId = s.PlayerId
ORDER BY s.Score DESC LIMIT 10`}
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 playerID, score int64
var playerName string
var timestamp time.Time
if err := row.Columns(&playerID, &playerName, &score, ×tamp); err != nil {
return err
}
fmt.Fprintf(w, "PlayerId: %d PlayerName: %s Score: %s Timestamp: %s\n",
playerID, playerName, formatWithCommas(score), timestamp.String()[0:10])
}
}
func queryWithTimespan(ctx context.Context, w io.Writer, client *spanner.Client, timespan int) error {
stmt := spanner.Statement{
SQL: `SELECT p.PlayerId, p.PlayerName, s.Score, s.Timestamp
FROM Players p
JOIN Scores s ON p.PlayerId = s.PlayerId
WHERE s.Timestamp > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL @Timespan HOUR)
ORDER BY s.Score DESC LIMIT 10`,
Params: map[string]interface{}{"Timespan": timespan},
}
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 playerID, score int64
var playerName string
var timestamp time.Time
if err := row.Columns(&playerID, &playerName, &score, ×tamp); err != nil {
return err
}
fmt.Fprintf(w, "PlayerId: %d PlayerName: %s Score: %s Timestamp: %s\n",
playerID, playerName, formatWithCommas(score), timestamp.String()[0:10])
}
}
func formatWithCommas(n int64) string {
numberAsString := strconv.FormatInt(n, 10)
numberLength := len(numberAsString)
if numberLength < 4 {
return numberAsString
}
var buffer bytes.Buffer
comma := []rune(",")
bufferPosition := numberLength % 3
if (bufferPosition) > 0 {
bufferPosition = 3 - bufferPosition
}
for i := 0; i < numberLength; i++ {
if bufferPosition == 3 {
buffer.WriteRune(comma[0])
bufferPosition = 0
}
bufferPosition++
buffer.WriteByte(numberAsString[i])
}
return buffer.String()
}
Fügen Sie als Nächstes oben in der Datei leaderboard.go „query“ als eine der Befehlsoptionen in der Variablen commands direkt unter der Option insertscores": insertScores ein, sodass die Variable commands so aussieht:
var (
commands = map[string]command{
"insertplayers": insertPlayers,
"insertscores": insertScores,
"query": query,
}
)
Fügen Sie als Nächstes „queryWithTimespan“ als Befehlsoption in die Funktion run ein, unter dem Befehlsabschnitt „createdatabase“ und über dem Abschnitt für die Verarbeitung der Befehle „insert and query“:
// querywithtimespan command
if cmd == "querywithtimespan" {
err := queryWithTimespan(ctx, w, dataClient, timespan)
if err != nil {
fmt.Fprintf(w, "%s failed with %v", cmd, err)
}
return err
}
Wenn Sie fertig sind, sollte die Funktion run so aussehen:
func run(ctx context.Context, adminClient *database.DatabaseAdminClient, dataClient *spanner.Client, w io.Writer,
cmd string, db string, timespan int) error {
// createdatabase command
if cmd == "createdatabase" {
err := createDatabase(ctx, w, adminClient, db)
if err != nil {
fmt.Fprintf(w, "%s failed with %v", cmd, err)
}
return err
}
// querywithtimespan command
if cmd == "querywithtimespan" {
if timespan == 0 {
flag.Usage()
os.Exit(2)
}
err := queryWithTimespan(ctx, w, dataClient, timespan)
if err != nil {
fmt.Fprintf(w, "%s failed with %v", cmd, err)
}
return err
}
// insert and query commands
cmdFn := commands[cmd]
if cmdFn == nil {
flag.Usage()
os.Exit(2)
}
err := cmdFn(ctx, w, dataClient)
if err != nil {
fmt.Fprintf(w, "%s failed with %v", cmd, err)
}
return err
}
Damit der Befehl queryWithTimespan funktioniert, aktualisieren Sie den Codeblock „flag.Parse()“ in der „main“-Methode Ihrer Anwendung so, dass er wie folgt aussieht:
flag.Parse()
flagCount := len(flag.Args())
if flagCount < 2 || flagCount > 3 {
flag.Usage()
os.Exit(2)
}
cmd, db := flag.Arg(0), flag.Arg(1)
// If query timespan flag is specified, parse to int
var timespan int = 0
if flagCount == 3 {
parsedTimespan, err := strconv.Atoi(flag.Arg(2))
if err != nil {
fmt.Println(err)
os.Exit(2)
}
timespan = parsedTimespan
}
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancel()
adminClient, dataClient := createClients(ctx, db)
if err := run(ctx, adminClient, dataClient, os.Stdout, cmd, db, timespan); err != nil {
os.Exit(1)
}
Der letzte Schritt, um Ihrer Anwendung die Funktion „query“ hinzuzufügen, besteht darin, der flag.Usage()-Funktion Hilfetext für die Befehle „query“ und „querywithtimespan“ hinzuzufügen. Fügen Sie der Funktion flag.Usage() die folgenden Codezeilen hinzu, um Hilfetext für die Abfragebefehle einzufügen:
Fügen Sie der Liste der möglichen Befehle die beiden „query“-Befehle hinzu:
Command can be one of: createdatabase, insertplayers, insertscores, query, querywithtimespan
Fügen Sie diesen zusätzlichen Hilfetext unter dem Hilfetext für den Befehl insertscores ein.
leaderboard query projects/my-project/instances/my-instance/databases/example-db
- Query players with top ten scores of all time.
leaderboard querywithtimespan projects/my-project/instances/my-instance/databases/example-db 168
- Query players with top ten scores within a timespan specified in hours.
Speichern Sie die Änderungen, die Sie an der Datei leaderboard.go vorgenommen haben, indem Sie im Cloud Shell-Editor im Menü „Datei“ die Option „Speichern“ auswählen.
In der Datei leaderboard.go im Verzeichnis golang-samples/spanner/spanner_leaderboard finden Sie ein Beispiel dafür, wie Ihre leaderboard.go-Datei aussehen sollte, nachdem Sie den Code zum Aktivieren der Befehle query und querywithtimespan hinzugefügt haben.
Erstellen und führen Sie die Anwendung nun aus, um zu bestätigen, dass die neuen Befehle query und querywithtimespan in der Liste der möglichen Befehle der Anwendung enthalten sind.
Führen Sie den folgenden Befehl in Cloud Shell aus, um die Anwendung zu erstellen:
go build leaderboard.go
Führen Sie die resultierende Anwendung in Cloud Shell aus, indem Sie den folgenden Befehl eingeben:
./leaderboard
Die Befehle query und querywithtimespan sollten jetzt als neue Befehlsoption in der Standardausgabe der App enthalten sein:
Usage: leaderboard <command> <database_name> [command_option]
Command can be one of: createdatabase, insertplayers, insertscores, query, querywithtimespan
Examples:
leaderboard createdatabase projects/my-project/instances/my-instance/databases/example-db
- Create a sample Cloud Spanner database along with sample tables in your project.
leaderboard insertplayers projects/my-project/instances/my-instance/databases/example-db
- Insert 100 sample Player records into the database.
leaderboard insertscores projects/my-project/instances/my-instance/databases/example-db
- Insert sample score data into Scores sample Cloud Spanner database table.
leaderboard query projects/my-project/instances/my-instance/databases/example-db
- Query players with top ten scores of all time.
leaderboard querywithtimespan projects/my-project/instances/my-instance/databases/example-db 168
- Query players with top ten scores within a timespan specified in hours.
Wie Sie in der Antwort sehen, können wir mit dem Befehl query eine Liste der zehn besten Spieler aller Zeiten abrufen. Wir sehen auch, dass wir mit dem Befehl querywithtimespan einen Zeitraum in Stunden angeben können, der zum Filtern von Datensätzen basierend auf ihrem Wert in der Spalte Timestamp der Tabelle Scores verwendet werden soll.
Führen wir den Befehl query mit denselben Argumentwerten aus, die wir beim Ausführen des Befehls create verwendet haben. Achten Sie darauf, my-project durch die Projekt-ID zu ersetzen, die Sie am Anfang dieses Codelabs erstellt haben.
./leaderboard query projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard
Sie sollten eine Antwort mit den zehn besten Spielern aller Zeiten sehen, die in etwa so aussieht:
PlayerId: 4018687297 PlayerName: Player 83 Score: 999,618 Timestamp: 2017-07-01
PlayerId: 4018687297 PlayerName: Player 83 Score: 998,956 Timestamp: 2017-09-02
PlayerId: 4285713246 PlayerName: Player 51 Score: 998,648 Timestamp: 2017-12-01
PlayerId: 5267931774 PlayerName: Player 49 Score: 997,733 Timestamp: 2017-11-09
PlayerId: 1981654448 PlayerName: Player 35 Score: 997,480 Timestamp: 2018-12-06
PlayerId: 4953940705 PlayerName: Player 87 Score: 995,184 Timestamp: 2018-09-14
PlayerId: 2456736905 PlayerName: Player 84 Score: 992,881 Timestamp: 2017-04-14
PlayerId: 8234617611 PlayerName: Player 19 Score: 992,399 Timestamp: 2017-12-27
PlayerId: 1788051688 PlayerName: Player 76 Score: 992,265 Timestamp: 2018-11-22
PlayerId: 7127686505 PlayerName: Player 97 Score: 992,038 Timestamp: 2017-12-02
Führen Sie nun den Befehl querywithtimespan mit den erforderlichen Argumenten aus, um die zehn besten Spieler des Jahres abzufragen. Geben Sie dazu einen „timespan“ an, der der Anzahl der Stunden in einem Jahr entspricht, also 8.760. Achten Sie darauf, my-project durch die Projekt-ID zu ersetzen, die Sie am Anfang dieses Codelabs erstellt haben.
./leaderboard querywithtimespan projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard 8760
Sie sollten eine Antwort mit den zehn besten Spielern des Jahres sehen, die so aussieht:
PlayerId: 1981654448 PlayerName: Player 35 Score: 997,480 Timestamp: 2018-12-06
PlayerId: 4953940705 PlayerName: Player 87 Score: 995,184 Timestamp: 2018-09-14
PlayerId: 1788051688 PlayerName: Player 76 Score: 992,265 Timestamp: 2018-11-22
PlayerId: 6862349579 PlayerName: Player 30 Score: 990,877 Timestamp: 2018-09-14
PlayerId: 5529627211 PlayerName: Player 16 Score: 989,142 Timestamp: 2018-03-30
PlayerId: 9743904155 PlayerName: Player 1 Score: 988,765 Timestamp: 2018-05-30
PlayerId: 6809119884 PlayerName: Player 7 Score: 986,673 Timestamp: 2018-05-16
PlayerId: 2132710638 PlayerName: Player 54 Score: 983,108 Timestamp: 2018-09-11
PlayerId: 2320093590 PlayerName: Player 79 Score: 981,373 Timestamp: 2018-05-07
PlayerId: 9554181430 PlayerName: Player 80 Score: 981,087 Timestamp: 2018-06-21
Führen Sie nun den Befehl querywithtimespan aus, um die zehn besten Spieler des Monats abzufragen. Geben Sie dazu einen „timespan“ an, der der Anzahl der Stunden in einem Monat entspricht, also 730. Achten Sie darauf, my-project durch die Projekt-ID zu ersetzen, die Sie am Anfang dieses Codelabs erstellt haben.
./leaderboard querywithtimespan projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard 730
Sie sollten eine Antwort mit den zehn besten Spielern des Monats sehen, die in etwa so aussieht:
PlayerId: 3869829195 PlayerName: Player 69 Score: 949,686 Timestamp: 2019-02-19
PlayerId: 7448359883 PlayerName: Player 20 Score: 938,998 Timestamp: 2019-02-07
PlayerId: 1981654448 PlayerName: Player 35 Score: 929,003 Timestamp: 2019-02-22
PlayerId: 9336678658 PlayerName: Player 44 Score: 914,106 Timestamp: 2019-01-27
PlayerId: 6968576389 PlayerName: Player 40 Score: 898,041 Timestamp: 2019-02-21
PlayerId: 5529627211 PlayerName: Player 16 Score: 896,433 Timestamp: 2019-01-29
PlayerId: 9395039625 PlayerName: Player 59 Score: 879,495 Timestamp: 2019-02-09
PlayerId: 2094604854 PlayerName: Player 39 Score: 860,434 Timestamp: 2019-02-01
PlayerId: 9395039625 PlayerName: Player 59 Score: 849,955 Timestamp: 2019-02-21
PlayerId: 4285713246 PlayerName: Player 51 Score: 805,654 Timestamp: 2019-02-02
Führen Sie nun den Befehl querywithtimespan aus, um die zehn besten Spieler der Woche abzufragen. Geben Sie dazu einen „timespan“ (Zeitraum) an, der der Anzahl der Stunden in einer Woche entspricht, also 168. Achten Sie darauf, my-project durch die Projekt-ID zu ersetzen, die Sie am Anfang dieses Codelabs erstellt haben.
./leaderboard querywithtimespan projects/my-project/instances/cloudspanner-leaderboard/databases/leaderboard 168
Sie sollten eine Antwort mit den zehn besten Spielern der Woche sehen, die so aussieht:
PlayerId: 3869829195 PlayerName: Player 69 Score: 949,686 Timestamp: 2019-02-19
PlayerId: 1981654448 PlayerName: Player 35 Score: 929,003 Timestamp: 2019-02-22
PlayerId: 6968576389 PlayerName: Player 40 Score: 898,041 Timestamp: 2019-02-21
PlayerId: 9395039625 PlayerName: Player 59 Score: 849,955 Timestamp: 2019-02-21
PlayerId: 5954045812 PlayerName: Player 8 Score: 795,639 Timestamp: 2019-02-22
PlayerId: 3889939638 PlayerName: Player 71 Score: 775,252 Timestamp: 2019-02-21
PlayerId: 5529627211 PlayerName: Player 16 Score: 604,695 Timestamp: 2019-02-19
PlayerId: 9006728426 PlayerName: Player 3 Score: 457,208 Timestamp: 2019-02-22
PlayerId: 8289497066 PlayerName: Player 58 Score: 227,697 Timestamp: 2019-02-20
PlayerId: 8065482904 PlayerName: Player 99 Score: 198,429 Timestamp: 2019-02-24
Super!
Wenn Sie jetzt Datensätze hinzufügen, skaliert Cloud Spanner Ihre Datenbank auf die benötigte Größe. Ganz gleich, wie groß Ihre Datenbank wird, die Bestenliste Ihres Spiels kann mit Cloud Spanner und der Truetime-Technologie weiterhin genau skaliert werden.
7. Bereinigen
Nachdem wir uns mit Spanner ausgetobt haben, müssen wir unseren Spielplatz aufräumen, um wertvolle Ressourcen und Geld zu sparen. Glücklicherweise ist das ein einfacher Schritt. Rufen Sie einfach den Cloud Spanner-Bereich der Cloud Console auf und löschen Sie die Instanz, die wir im Codelab-Schritt „Cloud Spanner-Instanz einrichten“ erstellt haben.
8. Glückwunsch!
Behandelte Themen:
- Google Cloud Spanner-Instanzen, -Datenbanken und Tabellenschema für eine Bestenliste
- Go-Konsolenanwendung erstellen
- Spanner-Datenbank und -Tabellen mit der Go-Clientbibliothek erstellen
- Daten mit der Go-Clientbibliothek in eine Spanner-Datenbank laden
- Die zehn besten Ergebnisse aus Ihren Daten mit Spanner-Commit-Zeitstempeln und der Go-Clientbibliothek abfragen
Vorgehensweise:
- CAP-Whitepaper zu Spanner lesen
- Best Practices für Schemadesign und Abfragen
- Weitere Informationen zu Commit-Zeitstempeln in Cloud Spanner
Feedback geben
- Bitte nehmen Sie sich einen Moment Zeit, um an unserer kurzen Umfrage teilzunehmen.