開始使用 Python 中的 Spanner


目標

本教學課程將逐步引導您使用 Python 適用的 Spanner 用戶端程式庫進行下列步驟:

  • 建立 Spanner 執行個體和資料庫。
  • 對資料庫中的資料進行寫入和讀取,以及執行 SQL 查詢。
  • 更新資料庫結構定義。
  • 使用讀取/寫入交易來更新資料。
  • 將次要索引新增至資料庫。
  • 使用索引對資料執行讀取作業和 SQL 查詢。
  • 使用唯讀交易擷取資料。

費用

本教學課程使用 Spanner,這是Google Cloud的計費元件。如要瞭解 Spanner 的使用費用,請參閱「定價」一文。

事前準備

完成「設定」一文中說明的步驟,包含建立與設定預設的 Google Cloud 專案、啟用計費功能、啟用 Cloud Spanner API 和設定 OAuth 2.0 以取得使用 Cloud Spanner API 的驗證憑證。

特別提醒您,請務必執行 gcloud auth application-default login 來設定本機開發環境的驗證憑證。

準備本機 Python 環境

  1. 按照設定 Python 開發環境一文的說明操作。

  2. 將範例應用程式存放區複製到您的本機電腦:

    git clone https://github.com/googleapis/python-spanner
    

    您也可以下載 zip 格式的範例,然後解壓縮該檔案。

  3. 變更為包含 Spanner 範例程式碼的目錄:

    cd python-spanner/samples/samples
    
  4. 建立獨立的 Python 環境,並安裝依附元件:

    virtualenv env
    source env/bin/activate
    pip install -r requirements.txt
    

建立執行個體

首次使用 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.

瀏覽範例檔案

範例存放區中有一項範例,說明如何透過 Python 使用 Spanner。

請查看 snippets.py 檔案,瞭解如何使用 Spanner。該檔案中的程式碼會顯示如何建立及使用新資料庫。這份資料會使用結構定義與資料模型頁面中顯示的範例結構定義。

建立資料庫

GoogleSQL

python snippets.py test-instance --database-id example-db create_database

PostgreSQL

python pg_snippets.py test-instance --database-id example-db create_database

畫面上會顯示下列訊息:

Created database example-db on instance test-instance
以下程式碼會建立資料庫,並在資料庫中建立兩個資料表。

GoogleSQL

def create_database(instance_id, database_id):
    """Creates a database and tables for sample data."""
    from google.cloud.spanner_admin_database_v1.types import spanner_database_admin

    spanner_client = spanner.Client()
    database_admin_api = spanner_client.database_admin_api

    request = spanner_database_admin.CreateDatabaseRequest(
        parent=database_admin_api.instance_path(spanner_client.project, instance_id),
        create_statement=f"CREATE DATABASE `{database_id}`",
        extra_statements=[
            """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""",
        ],
    )

    operation = database_admin_api.create_database(request=request)

    print("Waiting for operation to complete...")
    database = operation.result(OPERATION_TIMEOUT_SECONDS)

    print(
        "Created database {} on instance {}".format(
            database.name,
            database_admin_api.instance_path(spanner_client.project, instance_id),
        )
    )

PostgreSQL

def create_database(instance_id, database_id):
    """Creates a PostgreSql database and tables for sample data."""

    from google.cloud.spanner_admin_database_v1.types import \
        spanner_database_admin

    spanner_client = spanner.Client()
    database_admin_api = spanner_client.database_admin_api

    request = spanner_database_admin.CreateDatabaseRequest(
        parent=database_admin_api.instance_path(spanner_client.project, instance_id),
        create_statement=f'CREATE DATABASE "{database_id}"',
        database_dialect=DatabaseDialect.POSTGRESQL,
    )

    operation = database_admin_api.create_database(request=request)

    print("Waiting for operation to complete...")
    database = operation.result(OPERATION_TIMEOUT_SECONDS)

    create_table_using_ddl(database.name)
    print("Created database {} on instance {}".format(database_id, instance_id))


def create_table_using_ddl(database_name):
    from google.cloud.spanner_admin_database_v1.types import \
        spanner_database_admin

    spanner_client = spanner.Client()
    request = spanner_database_admin.UpdateDatabaseDdlRequest(
        database=database_name,
        statements=[
            """CREATE TABLE Singers (
  SingerId   bigint NOT NULL,
  FirstName  character varying(1024),
  LastName   character varying(1024),
  SingerInfo bytea,
  FullName   character varying(2048)
    GENERATED ALWAYS AS (FirstName || ' ' || LastName) STORED,
  PRIMARY KEY (SingerId)
  )""",
            """CREATE TABLE Albums (
  SingerId     bigint NOT NULL,
  AlbumId      bigint NOT NULL,
  AlbumTitle   character varying(1024),
  PRIMARY KEY (SingerId, AlbumId)
  ) INTERLEAVE IN PARENT Singers ON DELETE CASCADE""",
        ],
    )
    operation = spanner_client.database_admin_api.update_database_ddl(request)
    operation.result(OPERATION_TIMEOUT_SECONDS)

下一個步驟是將資料寫入資料庫。

建立資料庫用戶端

您必須先建立 Client,才能執行讀取或寫入作業。您可以將 Client 視為一種資料庫連線:您與 Spanner 的所有互動都必須透過 Client。一般而言,您會在應用程式啟動時建立 Client,然後重複使用該 Client 進行讀取、寫入及執行交易。以下程式碼顯示如何建立用戶端。

# Imports the Google Cloud Client Library.
from google.cloud import spanner

# Your Cloud Spanner instance ID.
# instance_id = "my-instance-id"
#
# Your Cloud Spanner database ID.
# database_id = "my-database-id"
# Instantiate a client.
spanner_client = spanner.Client()

# Get a Cloud Spanner instance by ID.
instance = spanner_client.instance(instance_id)

# Get a Cloud Spanner database by ID.
database = instance.database(database_id)

# Execute a simple SQL statement.
with database.snapshot() as snapshot:
    results = snapshot.execute_sql("SELECT 1")

    for row in results:
        print(row)

詳情請參閱 Client 參考資料。

使用 DML 寫入資料

您可以使用資料操縱語言 (DML) 在讀寫交易中插入資料。

請使用 execute_update() 方法執行 DML 陳述式。

# instance_id = "your-spanner-instance"
# database_id = "your-spanner-db-id"
spanner_client = spanner.Client()
instance = spanner_client.instance(instance_id)
database = instance.database(database_id)

def insert_singers(transaction):
    row_ct = transaction.execute_update(
        "INSERT INTO Singers (SingerId, FirstName, LastName) VALUES "
        "(12, 'Melissa', 'Garcia'), "
        "(13, 'Russell', 'Morales'), "
        "(14, 'Jacqueline', 'Long'), "
        "(15, 'Dylan', 'Shaw')"
    )
    print("{} record(s) inserted.".format(row_ct))

database.run_in_transaction(insert_singers)

使用 insert_with_dml 引數執行範例。

python snippets.py test-instance --database-id example-db insert_with_dml

畫面上會顯示下列訊息:

4 record(s) inserted.

使用變異寫入資料

您也可以使用變異來插入資料。

您可以利用 Batch 物件寫入資料。Batch 物件是變異作業的容器。變異代表一系列的插入、更新和刪除作業,Spanner 會以不可分割的形式,將這些作業套用至 Spanner 資料庫中不同的資料列和資料表。

Batch 類別中的 insert() 方法會將一或多個插入變異新增至批次。系統會以不可分割的形式套用單一批次中的所有變異。

此程式碼顯示如何使用變異寫入資料:

def insert_data(instance_id, database_id):
    """Inserts sample data into the given database.

    The database and table must already exist and can be created using
    `create_database`.
    """
    spanner_client = spanner.Client()
    instance = spanner_client.instance(instance_id)
    database = instance.database(database_id)

    with database.batch() as batch:
        batch.insert(
            table="Singers",
            columns=("SingerId", "FirstName", "LastName"),
            values=[
                (1, "Marc", "Richards"),
                (2, "Catalina", "Smith"),
                (3, "Alice", "Trentor"),
                (4, "Lea", "Martin"),
                (5, "David", "Lomond"),
            ],
        )

        batch.insert(
            table="Albums",
            columns=("SingerId", "AlbumId", "AlbumTitle"),
            values=[
                (1, 1, "Total Junk"),
                (1, 2, "Go, Go, Go"),
                (2, 1, "Green"),
                (2, 2, "Forever Hold Your Peace"),
                (2, 3, "Terrified"),
            ],
        )

    print("Inserted data.")

使用 insert_data 引數執行範例。

python snippets.py test-instance --database-id example-db insert_data

畫面上會顯示下列訊息:

Inserted data.

使用 SQL 查詢資料

Spanner 支援可用於讀取資料的 SQL 介面。您可以透過 Google Cloud CLI 在指令列上存取這個介面,或者透過程式使用 Python 適用的 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

使用 Python 適用的 Spanner 用戶端程式庫

除了在指令列上執行 SQL 陳述式之外,您也可以使用 Python 專用的 Spanner 用戶端程式庫,以程式輔助方式發出相同的 SQL 陳述式。

請使用 Snapshot 物件的 execute_sql() 方法執行 SQL 查詢。如要取得 Snapshot 物件,請在 with 陳述式中呼叫 Database 類別的 snapshot() 方法。

下面說明如何發出查詢和存取資料:

def query_data(instance_id, database_id):
    """Queries sample data from the database using SQL."""
    spanner_client = spanner.Client()
    instance = spanner_client.instance(instance_id)
    database = instance.database(database_id)

    with database.snapshot() as snapshot:
        results = snapshot.execute_sql(
            "SELECT SingerId, AlbumId, AlbumTitle FROM Albums"
        )

        for row in results:
            print("SingerId: {}, AlbumId: {}, AlbumTitle: {}".format(*row))

使用 query_data 引數執行範例。

python snippets.py test-instance --database-id example-db query_data

畫面上應會顯示下列結果:

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

使用 SQL 參數執行查詢

如果應用程式有經常執行的查詢,您可以透過參數化來提升效能。系統可快取並重新使用產生的參數查詢,減少編譯的成本。詳情請參閱「使用查詢參數,針對經常執行的查詢加快速度」。

以下範例說明如何在 WHERE 子句中使用參數,查詢包含 LastName 特定值的記錄。

# instance_id = "your-spanner-instance"
# database_id = "your-spanner-db-id"
spanner_client = spanner.Client()
instance = spanner_client.instance(instance_id)
database = instance.database(database_id)

with database.snapshot() as snapshot:
    results = snapshot.execute_sql(
        "SELECT SingerId, FirstName, LastName FROM Singers "
        "WHERE LastName = @lastName",
        params={"lastName": "Garcia"},
        param_types={"lastName": spanner.param_types.STRING},
    )

    for row in results:
        print("SingerId: {}, FirstName: {}, LastName: {}".format(*row))

使用 query_data_with_parameter 引數執行範例。

python snippets.py test-instance --database-id example-db query_data_with_parameter

畫面上應會顯示下列結果:

SingerId: 12, FirstName: Melissa, LastName: Garcia

使用讀取 API 讀取資料

除了 Spanner 的 SQL 介面外,Spanner 也支援讀取介面。

使用 Snapshot 物件的 read() 方法,從資料庫讀取資料列。如要取得 Snapshot 物件,請在 with 陳述式中呼叫 Database 類別的 snapshot() 方法。使用 KeySet 物件定義要讀取的索引鍵集合和索引鍵範圍。

下列內容將示範如何讀取資料:

def read_data(instance_id, database_id):
    """Reads sample data from the database."""
    spanner_client = spanner.Client()
    instance = spanner_client.instance(instance_id)
    database = instance.database(database_id)

    with database.snapshot() as snapshot:
        keyset = spanner.KeySet(all_=True)
        results = snapshot.read(
            table="Albums", columns=("SingerId", "AlbumId", "AlbumTitle"), keyset=keyset
        )

        for row in results:
            print("SingerId: {}, AlbumId: {}, AlbumTitle: {}".format(*row))

使用 read_data 引數執行範例。

python snippets.py test-instance --database-id example-db read_data

畫面會顯示類似以下的輸出:

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

更新資料庫結構定義

假設您需要新增名稱為 MarketingBudget 的新資料欄到 Albums 資料表,必須先更新資料庫結構定義,才能新增新資料欄到現有的資料表。Spanner 可在資料庫持續處理流量時,支援資料庫的結構定義更新作業。結構定義更新作業不需要讓資料庫離線,也不會鎖定整個資料表或資料欄;您可以在結構定義更新期間持續將資料寫入資料庫。詳情請參閱進行結構定義更新一文中支援的結構定義更新和結構定義變更效能。

新增資料欄

您可以使用 Google Cloud CLI 在指令列中新增資料欄,或使用程式輔助方式透過 Python 專用的 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.

使用 Python 適用的 Spanner 用戶端程式庫

請使用 Database 類別的 update_ddl() 方法修改結構定義:

def add_column(instance_id, database_id):
    """Adds a new column to the Albums table in the example database."""

    from google.cloud.spanner_admin_database_v1.types import spanner_database_admin

    spanner_client = spanner.Client()
    database_admin_api = spanner_client.database_admin_api

    request = spanner_database_admin.UpdateDatabaseDdlRequest(
        database=database_admin_api.database_path(
            spanner_client.project, instance_id, database_id
        ),
        statements=[
            "ALTER TABLE Albums ADD COLUMN MarketingBudget INT64",
        ],
    )

    operation = database_admin_api.update_database_ddl(request)

    print("Waiting for operation to complete...")
    operation.result(OPERATION_TIMEOUT_SECONDS)
    print("Added the MarketingBudget column.")

使用 add_column 引數執行範例。

python snippets.py test-instance --database-id example-db add_column

畫面上會顯示下列訊息:

Added the MarketingBudget column.

寫入資料到新資料欄

以下程式碼會將資料寫入新資料欄,並在 Albums(1, 1)Albums(2, 2) 這兩個索引鍵表示的資料列中將 MarketingBudget 分別設為 100000500000

def update_data(instance_id, database_id):
    """Updates sample data in the database.

    This updates the `MarketingBudget` column which must be created before
    running this sample. You can add the column by running the `add_column`
    sample or by running this DDL statement against your database:

        ALTER TABLE Albums ADD COLUMN MarketingBudget INT64

    """
    spanner_client = spanner.Client()
    instance = spanner_client.instance(instance_id)
    database = instance.database(database_id)

    with database.batch() as batch:
        batch.update(
            table="Albums",
            columns=("SingerId", "AlbumId", "MarketingBudget"),
            values=[(1, 1, 100000), (2, 2, 500000)],
        )

    print("Updated data.")

使用 update_data 引數執行範例。

python snippets.py test-instance --database-id example-db update_data

您也可以執行 SQL 查詢或讀取呼叫,以擷取剛寫入的值。

以下是執行查詢的程式碼:

def query_data_with_new_column(instance_id, database_id):
    """Queries sample data from the database using SQL.

    This sample uses the `MarketingBudget` column. You can add the column
    by running the `add_column` sample or by running this DDL statement against
    your database:

        ALTER TABLE Albums ADD COLUMN MarketingBudget INT64
    """
    spanner_client = spanner.Client()
    instance = spanner_client.instance(instance_id)
    database = instance.database(database_id)

    with database.snapshot() as snapshot:
        results = snapshot.execute_sql(
            "SELECT SingerId, AlbumId, MarketingBudget FROM Albums"
        )

        for row in results:
            print("SingerId: {}, AlbumId: {}, MarketingBudget: {}".format(*row))

如要執行這項查詢,請使用 query_data_with_new_column 引數執行範例檔案。

python snippets.py test-instance --database-id example-db query_data_with_new_column

畫面上會顯示下列訊息:

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

更新資料

您可以在讀寫交易中使用 DML 來更新資料。

請使用 execute_update() 方法執行 DML 陳述式。

# instance_id = "your-spanner-instance"
# database_id = "your-spanner-db-id"

spanner_client = spanner.Client()
instance = spanner_client.instance(instance_id)
database = instance.database(database_id)

def transfer_budget(transaction):
    # Transfer marketing budget from one album to another. Performed in a
    # single transaction to ensure that the transfer is atomic.
    second_album_result = transaction.execute_sql(
        "SELECT MarketingBudget from Albums " "WHERE SingerId = 2 and AlbumId = 2"
    )
    second_album_row = list(second_album_result)[0]
    second_album_budget = second_album_row[0]

    transfer_amount = 200000

    # 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 second_album_budget >= transfer_amount:
        first_album_result = transaction.execute_sql(
            "SELECT MarketingBudget from Albums "
            "WHERE SingerId = 1 and AlbumId = 1"
        )
        first_album_row = list(first_album_result)[0]
        first_album_budget = first_album_row[0]

        second_album_budget -= transfer_amount
        first_album_budget += transfer_amount

        # Update first album
        transaction.execute_update(
            "UPDATE Albums "
            "SET MarketingBudget = @AlbumBudget "
            "WHERE SingerId = 1 and AlbumId = 1",
            params={"AlbumBudget": first_album_budget},
            param_types={"AlbumBudget": spanner.param_types.INT64},
        )

        # Update second album
        transaction.execute_update(
            "UPDATE Albums "
            "SET MarketingBudget = @AlbumBudget "
            "WHERE SingerId = 2 and AlbumId = 2",
            params={"AlbumBudget": second_album_budget},
            param_types={"AlbumBudget": spanner.param_types.INT64},
        )

        print(
            "Transferred {} from Album2's budget to Album1's".format(
                transfer_amount
            )
        )

database.run_in_transaction(transfer_budget)

使用 write_with_dml_transaction 引數執行範例。

python snippets.py test-instance --database-id example-db write_with_dml_transaction

畫面上會顯示下列訊息:

Transferred 200000 from Album2's budget to Album1's

使用次要索引

假設您要針對 Albums 擷取 AlbumTitle 值在特定範圍內的所有資料列,可以先利用 SQL 陳述式或讀取呼叫,從 AlbumTitle 資料欄讀取所有值,然後再捨棄條件不符的資料列。不過,執行完整資料表掃描的費用高昂,對於內含大量資料列的資料表而言更是如此。因此您可以改為在資料表建立次要索引,以在將非主鍵資料欄做為搜尋條件時,能加快資料列的擷取速度。

您必須先更新結構定義,才能將次要索引新增至現有資料表。如同其他結構定義更新,Spanner 支援在資料庫持續處理流量時新增索引。Spanner 會自動使用現有資料填入索引。補充作業可能需要幾分鐘才能完成,但您不必將資料庫設為離線,也不必避免在這個程序中寫入已編入索引的資料表。詳情請參閱「新增次要索引」。

新增次要索引後,Spanner 會自動將其用於 SQL 查詢,這些查詢可能會在使用索引後執行得更快。如果您使用讀取介面,則必須指定要使用的索引。

新增次要索引

您可以使用 gcloud 指令列工具在指令列上新增索引,或使用 Python 專用的 Spanner 用戶端程式庫透過程式輔助方式新增索引。

使用指令列

使用下列 CREATE INDEX 指令,在資料庫中新增索引:

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

畫面上會顯示下列訊息:

Schema updating...done.

使用 Python 適用的 Spanner 用戶端程式庫

使用 Database 類別的 update_ddl() 方法新增索引:

def add_index(instance_id, database_id):
    """Adds a simple index to the example database."""

    from google.cloud.spanner_admin_database_v1.types import spanner_database_admin

    spanner_client = spanner.Client()
    database_admin_api = spanner_client.database_admin_api

    request = spanner_database_admin.UpdateDatabaseDdlRequest(
        database=database_admin_api.database_path(
            spanner_client.project, instance_id, database_id
        ),
        statements=["CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)"],
    )

    operation = database_admin_api.update_database_ddl(request)

    print("Waiting for operation to complete...")
    operation.result(OPERATION_TIMEOUT_SECONDS)

    print("Added the AlbumsByAlbumTitle index.")

使用 add_index 引數執行範例。

python snippets.py test-instance --database-id example-db add_index

索引可能需要幾分鐘才能新增完成。之後畫面上會顯示以下訊息:

Added the AlbumsByAlbumTitle index.

使用索引進行讀取

對於 SQL 查詢,Spanner 會自動使用適當的索引。在讀取介面中,您必須在要求中指定索引。

如要在讀取介面中使用索引,請為 Snapshot 物件的 read() 方法提供 Index 引數。如要取得 Snapshot 物件,請在 with 陳述式中呼叫 Database 類別的 snapshot() 方法。

def read_data_with_index(instance_id, database_id):
    """Reads sample data from the database using an index.

    The index must exist before running this sample. You can add the index
    by running the `add_index` sample or by running this DDL statement against
    your database:

        CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)

    """
    spanner_client = spanner.Client()
    instance = spanner_client.instance(instance_id)
    database = instance.database(database_id)

    with database.snapshot() as snapshot:
        keyset = spanner.KeySet(all_=True)
        results = snapshot.read(
            table="Albums",
            columns=("AlbumId", "AlbumTitle"),
            keyset=keyset,
            index="AlbumsByAlbumTitle",
        )

        for row in results:
            print("AlbumId: {}, AlbumTitle: {}".format(*row))

使用 read_data_with_index 引數執行範例。

python snippets.py test-instance --database-id example-db read_data_with_index

畫面上會顯示下列訊息:

AlbumId: 2, AlbumTitle: Forever Hold Your Peace
AlbumId: 2, AlbumTitle: Go, Go, Go
AlbumId: 1, AlbumTitle: Green
AlbumId: 3, AlbumTitle: Terrified
AlbumId: 1, AlbumTitle: 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.

使用 Python 適用的 Spanner 用戶端程式庫

使用 Database 類別的 update_ddl() 方法,透過 STORING 子句新增索引:

def add_storing_index(instance_id, database_id):
    """Adds an storing index to the example database."""

    from google.cloud.spanner_admin_database_v1.types import spanner_database_admin

    spanner_client = spanner.Client()
    database_admin_api = spanner_client.database_admin_api

    request = spanner_database_admin.UpdateDatabaseDdlRequest(
        database=database_admin_api.database_path(
            spanner_client.project, instance_id, database_id
        ),
        statements=[
            "CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle)"
            "STORING (MarketingBudget)"
        ],
    )

    operation = database_admin_api.update_database_ddl(request)

    print("Waiting for operation to complete...")
    operation.result(OPERATION_TIMEOUT_SECONDS)

    print("Added the AlbumsByAlbumTitle2 index.")

使用 add_storing_index 引數執行範例。

python snippets.py test-instance --database-id example-db add_storing_index

畫面上會顯示下列訊息:

Added the AlbumsByAlbumTitle2 index.

現在您可以執行讀取作業,從 AlbumsByAlbumTitle2 索引中擷取所有 AlbumIdAlbumTitleMarketingBudget 欄:

def read_data_with_storing_index(instance_id, database_id):
    """Reads sample data from the database using an index with a storing
    clause.

    The index must exist before running this sample. You can add the index
    by running the `add_scoring_index` sample or by running this DDL statement
    against your database:

        CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle)
        STORING (MarketingBudget)

    """
    spanner_client = spanner.Client()
    instance = spanner_client.instance(instance_id)
    database = instance.database(database_id)

    with database.snapshot() as snapshot:
        keyset = spanner.KeySet(all_=True)
        results = snapshot.read(
            table="Albums",
            columns=("AlbumId", "AlbumTitle", "MarketingBudget"),
            keyset=keyset,
            index="AlbumsByAlbumTitle2",
        )

        for row in results:
            print("AlbumId: {}, AlbumTitle: {}, " "MarketingBudget: {}".format(*row))

使用 read_data_with_storing_index 引數執行範例。

python snippets.py test-instance --database-id example-db read_data_with_storing_index

畫面會顯示類似以下的輸出:

AlbumId: 2, AlbumTitle: Forever Hold Your Peace, MarketingBudget: 300000
AlbumId: 2, AlbumTitle: Go, Go, Go, MarketingBudget: None
AlbumId: 1, AlbumTitle: Green, MarketingBudget: None
AlbumId: 3, AlbumTitle: Terrified, MarketingBudget: None
AlbumId: 1, AlbumTitle: Total Junk, MarketingBudget: 300000

使用唯讀交易擷取資料

假設您想要在相同時間戳記執行一次以上的讀取作業。唯讀交易會觀察出交易修訂記錄中一致的前置字串,讓應用程式取得的資料始終保持一致。如要執行唯讀交易,請使用 Snapshot 物件。如要取得 Snapshot 物件,請在 with 陳述式中呼叫 Database 類別的 snapshot() 方法。

以下顯示如何執行查詢,並在同一個唯讀交易中執行讀取作業:

def read_only_transaction(instance_id, database_id):
    """Reads data inside of a read-only transaction.

    Within the read-only transaction, or "snapshot", the application sees
    consistent view of the database at a particular timestamp.
    """
    spanner_client = spanner.Client()
    instance = spanner_client.instance(instance_id)
    database = instance.database(database_id)

    with database.snapshot(multi_use=True) as snapshot:
        # Read using SQL.
        results = snapshot.execute_sql(
            "SELECT SingerId, AlbumId, AlbumTitle FROM Albums"
        )

        print("Results from first read:")
        for row in results:
            print("SingerId: {}, AlbumId: {}, AlbumTitle: {}".format(*row))

        # Perform another read using the `read` method. Even if the data
        # is updated in-between the reads, the snapshot ensures that both
        # return the same data.
        keyset = spanner.KeySet(all_=True)
        results = snapshot.read(
            table="Albums", columns=("SingerId", "AlbumId", "AlbumTitle"), keyset=keyset
        )

        print("Results from second read:")
        for row in results:
            print("SingerId: {}, AlbumId: {}, AlbumTitle: {}".format(*row))

使用 read_only_transaction 引數執行範例。

python snippets.py test-instance --database-id example-db read_only_transaction

畫面會顯示類似以下的輸出:

Results from first read:
SingerId: 2, AlbumId: 2, AlbumTitle: Forever Hold Your Peace
SingerId: 1, AlbumId: 2, AlbumTitle: Go, Go, Go
SingerId: 2, AlbumId: 1, AlbumTitle: Green
SingerId: 2, AlbumId: 3, AlbumTitle: Terrified
SingerId: 1, AlbumId: 1, AlbumTitle: Total Junk
Results from second read:
SingerId: 1, AlbumId: 1, AlbumTitle: Total Junk
SingerId: 1, AlbumId: 2, AlbumTitle: Go, Go, Go
SingerId: 2, AlbumId: 1, AlbumTitle: Green
SingerId: 2, AlbumId: 2, AlbumTitle: Forever Hold Your Peace
SingerId: 2, AlbumId: 3, AlbumTitle: Terrified

清除所用資源

如要避免系統向您的 Cloud Billing 帳戶收取您在本教學課程中所用資源的額外費用,請捨棄資料庫並刪除您建立的執行個體。

刪除資料庫

您刪除執行個體時,也會自動刪除其中所有資料庫。這個步驟將示範如何在保留執行個體的情況下刪除資料庫 (您仍須支付執行個體費用)。

使用指令列

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

使用 Google Cloud 主控台

  1. 前往 Google Cloud 控制台的「Spanner 執行個體」頁面。

    前往「Instances」(執行個體) 頁面

  2. 點選執行個體。

  3. 點選您要刪除的資料庫。

  4. 在「Database details」(資料庫詳細資料) 頁面,按一下 [Delete] (刪除)

  5. 確認您要刪除資料庫,然後按一下 [Delete] (刪除)

刪除執行個體

您刪除執行個體時,也會自動捨棄您在其中建立的所有資料庫。

使用指令列

gcloud spanner instances delete test-instance

使用 Google Cloud 主控台

  1. 前往 Google Cloud 控制台的「Spanner 執行個體」頁面。

    前往「Instances」(執行個體) 頁面

  2. 點選執行個體。

  3. 按一下 [Delete] (刪除)

  4. 確認您要刪除執行個體,然後按一下 [Delete] (刪除)

後續步驟