本頁面說明如何使用變異插入、更新及刪除資料。變異代表一系列的插入、更新和刪除作業,Spanner 會以不可分割的形式,將這些作業套用到 Spanner 資料庫中不同的資料列和資料表。
雖然您可使用 gRPC 或 REST 修訂變異,但比較常見的方式是透過用戶端程式庫來存取 API。
本頁面顯示插入、更新與刪除的基本作業。您可以在入門指南教學課程找到更多範例。
如果您需要提交大量盲寫作業,但不需要原子交易,可以使用批次寫入功能大量修改 Spanner 資料表。詳情請參閱「使用批次寫入功能修改資料」。
在資料表中插入新資料列
C++
您可以使用 InsertMutationBuilder()
函式寫入資料。Client::Commit()
會在資料表中新增資料列。系統會以不可分割的形式套用單一批次中的所有插入資料。
此程式碼顯示如何寫入資料:
void InsertData(google::cloud::spanner::Client client) {
namespace spanner = ::google::cloud::spanner;
auto insert_singers = spanner::InsertMutationBuilder(
"Singers", {"SingerId", "FirstName", "LastName"})
.EmplaceRow(1, "Marc", "Richards")
.EmplaceRow(2, "Catalina", "Smith")
.EmplaceRow(3, "Alice", "Trentor")
.EmplaceRow(4, "Lea", "Martin")
.EmplaceRow(5, "David", "Lomond")
.Build();
auto insert_albums = spanner::InsertMutationBuilder(
"Albums", {"SingerId", "AlbumId", "AlbumTitle"})
.EmplaceRow(1, 1, "Total Junk")
.EmplaceRow(1, 2, "Go, Go, Go")
.EmplaceRow(2, 1, "Green")
.EmplaceRow(2, 2, "Forever Hold Your Peace")
.EmplaceRow(2, 3, "Terrified")
.Build();
auto commit_result =
client.Commit(spanner::Mutations{insert_singers, insert_albums});
if (!commit_result) throw std::move(commit_result).status();
std::cout << "Insert was successful [spanner_insert_data]\n";
}
C#
您可以使用 connection.CreateInsertCommand()
方法插入資料,該方法會建立新的 SpannerCommand
,以便將資料列插入資料表。SpannerCommand.ExecuteNonQueryAsync()
方法會在資料表中新增資料列。
此程式碼顯示如何插入資料:
using Google.Cloud.Spanner.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
public class InsertDataAsyncSample
{
public class Singer
{
public int SingerId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Album
{
public int SingerId { get; set; }
public int AlbumId { get; set; }
public string AlbumTitle { get; set; }
}
public async Task InsertDataAsync(string projectId, string instanceId, string databaseId)
{
string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";
List<Singer> singers = new List<Singer>
{
new Singer { SingerId = 1, FirstName = "Marc", LastName = "Richards" },
new Singer { SingerId = 2, FirstName = "Catalina", LastName = "Smith" },
new Singer { SingerId = 3, FirstName = "Alice", LastName = "Trentor" },
new Singer { SingerId = 4, FirstName = "Lea", LastName = "Martin" },
new Singer { SingerId = 5, FirstName = "David", LastName = "Lomond" },
};
List<Album> albums = new List<Album>
{
new Album { SingerId = 1, AlbumId = 1, AlbumTitle = "Total Junk" },
new Album { SingerId = 1, AlbumId = 2, AlbumTitle = "Go, Go, Go" },
new Album { SingerId = 2, AlbumId = 1, AlbumTitle = "Green" },
new Album { SingerId = 2, AlbumId = 2, AlbumTitle = "Forever Hold your Peace" },
new Album { SingerId = 2, AlbumId = 3, AlbumTitle = "Terrified" },
};
// Create connection to Cloud Spanner.
using var connection = new SpannerConnection(connectionString);
await connection.OpenAsync();
await connection.RunWithRetriableTransactionAsync(async transaction =>
{
await Task.WhenAll(singers.Select(singer =>
{
// Insert rows into the Singers table.
using var cmd = connection.CreateInsertCommand("Singers", new SpannerParameterCollection
{
{ "SingerId", SpannerDbType.Int64, singer.SingerId },
{ "FirstName", SpannerDbType.String, singer.FirstName },
{ "LastName", SpannerDbType.String, singer.LastName }
});
cmd.Transaction = transaction;
return cmd.ExecuteNonQueryAsync();
}));
await Task.WhenAll(albums.Select(album =>
{
// Insert rows into the Albums table.
using var cmd = connection.CreateInsertCommand("Albums", new SpannerParameterCollection
{
{ "SingerId", SpannerDbType.Int64, album.SingerId },
{ "AlbumId", SpannerDbType.Int64, album.AlbumId },
{ "AlbumTitle", SpannerDbType.String,album.AlbumTitle }
});
cmd.Transaction = transaction;
return cmd.ExecuteNonQueryAsync();
}));
});
Console.WriteLine("Data inserted.");
}
}
Go
您可以使用 Mutation
寫入資料。Mutation
是變異作業的容器。Mutation
代表一系列的插入、更新、刪除等作業,這些作業可以自動套用到 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
}
Java
您可以利用 Mutation
物件寫入資料。Mutation
物件是變異作業的容器。Mutation
代表一系列的插入、更新和刪除作業,Spanner 會以不可分割的形式,將這些作業套用到 Spanner 資料庫中不同的資料列和資料表。
Mutation
類別中的 newInsertBuilder()
方法會建構 INSERT
變異,在資料表中插入新的資料列。如果該資料列已存在,寫入就會失敗。或者,您也可以使用 newInsertOrUpdateBuilder
方法建構 INSERT_OR_UPDATE
變異,在資料列已存在的情況下更新資料欄值。
DatabaseClient
類別中的 write()
方法會寫入異動事件。系統會以不可分割的形式套用單一批次中的所有變異。
此程式碼顯示如何寫入資料:
static final List<Singer> SINGERS =
Arrays.asList(
new Singer(1, "Marc", "Richards"),
new Singer(2, "Catalina", "Smith"),
new Singer(3, "Alice", "Trentor"),
new Singer(4, "Lea", "Martin"),
new Singer(5, "David", "Lomond"));
static final List<Album> ALBUMS =
Arrays.asList(
new Album(1, 1, "Total Junk"),
new Album(1, 2, "Go, Go, Go"),
new Album(2, 1, "Green"),
new Album(2, 2, "Forever Hold Your Peace"),
new Album(2, 3, "Terrified"));
static void writeExampleData(DatabaseClient dbClient) {
List<Mutation> mutations = new ArrayList<>();
for (Singer singer : SINGERS) {
mutations.add(
Mutation.newInsertBuilder("Singers")
.set("SingerId")
.to(singer.singerId)
.set("FirstName")
.to(singer.firstName)
.set("LastName")
.to(singer.lastName)
.build());
}
for (Album album : ALBUMS) {
mutations.add(
Mutation.newInsertBuilder("Albums")
.set("SingerId")
.to(album.singerId)
.set("AlbumId")
.to(album.albumId)
.set("AlbumTitle")
.to(album.albumTitle)
.build());
}
dbClient.write(mutations);
}
Node.js
您可以利用 Table
物件寫入資料。Table.insert()
方法會在資料表中新增資料列。系統會以不可分割的形式套用單一批次中的所有插入資料。
此程式碼顯示如何寫入資料:
// Imports the Google Cloud client library
const {Spanner} = require('@google-cloud/spanner');
/**
* TODO(developer): Uncomment the following lines before running the sample.
*/
// const projectId = 'my-project-id';
// const instanceId = 'my-instance';
// const databaseId = 'my-database';
// Creates a client
const spanner = new Spanner({
projectId: projectId,
});
// Gets a reference to a Cloud Spanner instance and database
const instance = spanner.instance(instanceId);
const database = instance.database(databaseId);
// Instantiate Spanner table objects
const singersTable = database.table('Singers');
const albumsTable = database.table('Albums');
// Inserts rows into the Singers table
// Note: Cloud Spanner interprets Node.js numbers as FLOAT64s, so
// they must be converted to strings before being inserted as INT64s
try {
await singersTable.insert([
{SingerId: '1', FirstName: 'Marc', LastName: 'Richards'},
{SingerId: '2', FirstName: 'Catalina', LastName: 'Smith'},
{SingerId: '3', FirstName: 'Alice', LastName: 'Trentor'},
{SingerId: '4', FirstName: 'Lea', LastName: 'Martin'},
{SingerId: '5', FirstName: 'David', LastName: 'Lomond'},
]);
await albumsTable.insert([
{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'},
]);
console.log('Inserted data.');
} catch (err) {
console.error('ERROR:', err);
} finally {
await database.close();
}
PHP
您可以使用 Database::insertBatch
方法寫入資料。insertBatch
會在資料表中新增資料列。系統會以不可分割的形式套用單一批次中的所有插入資料。
此程式碼顯示如何寫入資料:
use Google\Cloud\Spanner\SpannerClient;
/**
* Inserts sample data into the given database.
*
* The database and table must already exist and can be created using
* `create_database`.
* Example:
* ```
* insert_data($instanceId, $databaseId);
* ```
*
* @param string $instanceId The Spanner instance ID.
* @param string $databaseId The Spanner database ID.
*/
function insert_data(string $instanceId, string $databaseId): void
{
$spanner = new SpannerClient();
$instance = $spanner->instance($instanceId);
$database = $instance->database($databaseId);
$operation = $database->transaction(['singleUse' => true])
->insertBatch('Singers', [
['SingerId' => 1, 'FirstName' => 'Marc', 'LastName' => 'Richards'],
['SingerId' => 2, 'FirstName' => 'Catalina', 'LastName' => 'Smith'],
['SingerId' => 3, 'FirstName' => 'Alice', 'LastName' => 'Trentor'],
['SingerId' => 4, 'FirstName' => 'Lea', 'LastName' => 'Martin'],
['SingerId' => 5, 'FirstName' => 'David', 'LastName' => 'Lomond'],
])
->insertBatch('Albums', [
['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']
])
->commit();
print('Inserted data.' . PHP_EOL);
}
Python
您可以利用 Batch
物件寫入資料。Batch
物件是變異作業的容器。變異代表一系列的插入、更新、刪除等作業,可以自動套用到 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.")
Ruby
您可以利用 Client
物件寫入資料。Client#commit
方法會建立並修訂寫入作業的交易,在資料庫的資料欄、資料列與資料表中,以不可分割的形式在單一邏輯點及時執行這些寫入作業。
此程式碼顯示如何寫入資料:
# project_id = "Your Google Cloud project ID"
# instance_id = "Your Spanner instance ID"
# database_id = "Your Spanner database ID"
require "google/cloud/spanner"
spanner = Google::Cloud::Spanner.new project: project_id
client = spanner.client instance_id, database_id
client.commit do |c|
c.insert "Singers", [
{ SingerId: 1, FirstName: "Marc", LastName: "Richards" },
{ SingerId: 2, FirstName: "Catalina", LastName: "Smith" },
{ SingerId: 3, FirstName: "Alice", LastName: "Trentor" },
{ SingerId: 4, FirstName: "Lea", LastName: "Martin" },
{ SingerId: 5, FirstName: "David", LastName: "Lomond" }
]
c.insert "Albums", [
{ 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" }
]
end
puts "Inserted data"
更新資料表中的資料列
假設 Albums(1, 1)
的銷售量低於預期。因此,您希望在 Albums(2, 2)
的預算中可用 $200,000 美元時,將 $200,000 美元的行銷預算從 Albums(2, 2)
移到 Albums(1, 1)
。
由於您必須讀取資料表中的資料,決定是否要寫入新的值,因此您應使用讀寫交易,以不可分割的形式執行讀取和寫入作業。
C++
使用 Transaction()
函式為用戶端執行交易。
以下是執行交易的程式碼:
void ReadWriteTransaction(google::cloud::spanner::Client client) {
namespace spanner = ::google::cloud::spanner;
using ::google::cloud::StatusOr;
// A helper to read a single album MarketingBudget.
auto get_current_budget =
[](spanner::Client client, spanner::Transaction txn,
std::int64_t singer_id,
std::int64_t album_id) -> StatusOr<std::int64_t> {
auto key = spanner::KeySet().AddKey(spanner::MakeKey(singer_id, album_id));
auto rows = client.Read(std::move(txn), "Albums", std::move(key),
{"MarketingBudget"});
using RowType = std::tuple<std::int64_t>;
auto row = spanner::GetSingularRow(spanner::StreamOf<RowType>(rows));
if (!row) return std::move(row).status();
return std::get<0>(*std::move(row));
};
auto commit = client.Commit(
[&client, &get_current_budget](
spanner::Transaction const& txn) -> StatusOr<spanner::Mutations> {
auto b1 = get_current_budget(client, txn, 1, 1);
if (!b1) return std::move(b1).status();
auto b2 = get_current_budget(client, txn, 2, 2);
if (!b2) return std::move(b2).status();
std::int64_t transfer_amount = 200000;
return spanner::Mutations{
spanner::UpdateMutationBuilder(
"Albums", {"SingerId", "AlbumId", "MarketingBudget"})
.EmplaceRow(1, 1, *b1 + transfer_amount)
.EmplaceRow(2, 2, *b2 - transfer_amount)
.Build()};
});
if (!commit) throw std::move(commit).status();
std::cout << "Transfer was successful [spanner_read_write_transaction]\n";
}
C#
針對 .NET Standard 2.0 (或 .NET 4.5) 以上版本,您可使用 .NET 架構的
TransactionScope()
來執行交易。對於所有支援的 .NET 版本,您可以將 SpannerConnection.BeginTransactionAsync
的結果設為 SpannerCommand
的 Transaction
屬性,藉此建立交易。
以下是執行交易的兩種方法:
.NET Standard 2.0
using Google.Cloud.Spanner.Data;
using System;
using System.Threading.Tasks;
using System.Transactions;
public class ReadWriteWithTransactionAsyncSample
{
public async Task<int> ReadWriteWithTransactionAsync(string projectId, string instanceId, string databaseId)
{
// This sample transfers 200,000 from the MarketingBudget
// field of the second Album to the first Album. Make sure to run
// the Add Column and Write Data To New Column samples first,
// in that order.
string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";
using TransactionScope scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled);
decimal transferAmount = 200000;
decimal secondBudget = 0;
decimal firstBudget = 0;
using var connection = new SpannerConnection(connectionString);
using var cmdLookup1 = connection.CreateSelectCommand("SELECT * FROM Albums WHERE SingerId = 2 AND AlbumId = 2");
using (var reader = await cmdLookup1.ExecuteReaderAsync())
{
while (await reader.ReadAsync())
{
// Read the second album's budget.
secondBudget = reader.GetFieldValue<decimal>("MarketingBudget");
// Confirm second Album's budget is sufficient and
// if not raise an exception. Raising an exception
// will automatically roll back the transaction.
if (secondBudget < transferAmount)
{
throw new Exception($"The second album's budget {secondBudget} is less than the amount to transfer.");
}
}
}
// Read the first album's budget.
using var cmdLookup2 = connection.CreateSelectCommand("SELECT * FROM Albums WHERE SingerId = 1 and AlbumId = 1");
using (var reader = await cmdLookup2.ExecuteReaderAsync())
{
while (await reader.ReadAsync())
{
firstBudget = reader.GetFieldValue<decimal>("MarketingBudget");
}
}
// Specify update command parameters.
using var cmdUpdate = connection.CreateUpdateCommand("Albums", new SpannerParameterCollection
{
{ "SingerId", SpannerDbType.Int64 },
{ "AlbumId", SpannerDbType.Int64 },
{ "MarketingBudget", SpannerDbType.Int64 },
});
// Update second album to remove the transfer amount.
secondBudget -= transferAmount;
cmdUpdate.Parameters["SingerId"].Value = 2;
cmdUpdate.Parameters["AlbumId"].Value = 2;
cmdUpdate.Parameters["MarketingBudget"].Value = secondBudget;
var rowCount = await cmdUpdate.ExecuteNonQueryAsync();
// Update first album to add the transfer amount.
firstBudget += transferAmount;
cmdUpdate.Parameters["SingerId"].Value = 1;
cmdUpdate.Parameters["AlbumId"].Value = 1;
cmdUpdate.Parameters["MarketingBudget"].Value = firstBudget;
rowCount += await cmdUpdate.ExecuteNonQueryAsync();
scope.Complete();
Console.WriteLine("Transaction complete.");
return rowCount;
}
}
.NET Standard 1.5
using Google.Cloud.Spanner.Data;
using System;
using System.Threading.Tasks;
public class ReadWriteWithTransactionCoreAsyncSample
{
public async Task<int> ReadWriteWithTransactionCoreAsync(string projectId, string instanceId, string databaseId)
{
// This sample transfers 200,000 from the MarketingBudget
// field of the second Album to the first Album. Make sure to run
// the Add Column and Write Data To New Column samples first,
// in that order.
string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";
decimal transferAmount = 200000;
decimal secondBudget = 0;
decimal firstBudget = 0;
using var connection = new SpannerConnection(connectionString);
await connection.OpenAsync();
using var transaction = await connection.BeginTransactionAsync();
using var cmdLookup1 = connection.CreateSelectCommand("SELECT * FROM Albums WHERE SingerId = 2 AND AlbumId = 2");
cmdLookup1.Transaction = transaction;
using (var reader = await cmdLookup1.ExecuteReaderAsync())
{
while (await reader.ReadAsync())
{
// Read the second album's budget.
secondBudget = reader.GetFieldValue<decimal>("MarketingBudget");
// Confirm second Album's budget is sufficient and
// if not raise an exception. Raising an exception
// will automatically roll back the transaction.
if (secondBudget < transferAmount)
{
throw new Exception($"The second album's budget {secondBudget} contains less than the amount to transfer.");
}
}
}
// Read the first album's budget.
using var cmdLookup2 = connection.CreateSelectCommand("SELECT * FROM Albums WHERE SingerId = 1 and AlbumId = 1");
cmdLookup2.Transaction = transaction;
using (var reader = await cmdLookup2.ExecuteReaderAsync())
{
while (await reader.ReadAsync())
{
firstBudget = reader.GetFieldValue<decimal>("MarketingBudget");
}
}
// Specify update command parameters.
using var cmdUpdate = connection.CreateUpdateCommand("Albums", new SpannerParameterCollection
{
{ "SingerId", SpannerDbType.Int64 },
{ "AlbumId", SpannerDbType.Int64 },
{ "MarketingBudget", SpannerDbType.Int64 },
});
cmdUpdate.Transaction = transaction;
// Update second album to remove the transfer amount.
secondBudget -= transferAmount;
cmdUpdate.Parameters["SingerId"].Value = 2;
cmdUpdate.Parameters["AlbumId"].Value = 2;
cmdUpdate.Parameters["MarketingBudget"].Value = secondBudget;
var rowCount = await cmdUpdate.ExecuteNonQueryAsync();
// Update first album to add the transfer amount.
firstBudget += transferAmount;
cmdUpdate.Parameters["SingerId"].Value = 1;
cmdUpdate.Parameters["AlbumId"].Value = 1;
cmdUpdate.Parameters["MarketingBudget"].Value = firstBudget;
rowCount += await cmdUpdate.ExecuteNonQueryAsync();
await transaction.CommitAsync();
Console.WriteLine("Transaction complete.");
return rowCount;
}
}
Go
使用 ReadWriteTransaction
類型,在讀寫交易的內容中執行作業主體。Client.ReadWriteTransaction()
會傳回 ReadWriteTransaction
物件。
本範例會使用 ReadWriteTransaction.ReadRow()
擷取資料列。
本範例也使用 ReadWriteTransaction.BufferWrite()
,可在交易確認時套用更新組合,並新增變異清單。
此範例也會使用 Key
類型,此類型代表 Spanner 資料表或索引中的資料列索引鍵。
import (
"context"
"fmt"
"io"
"cloud.google.com/go/spanner"
)
func writeWithTransaction(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 := func(key spanner.Key) (int64, error) {
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
}
album2Budget, err := getBudget(spanner.Key{2, 2})
if err != nil {
return err
}
const transferAmt = 200000
if album2Budget >= transferAmt {
album1Budget, err := getBudget(spanner.Key{1, 1})
if err != nil {
return err
}
album1Budget += transferAmt
album2Budget -= transferAmt
cols := []string{"SingerId", "AlbumId", "MarketingBudget"}
txn.BufferWrite([]*spanner.Mutation{
spanner.Update("Albums", cols, []interface{}{1, 1, album1Budget}),
spanner.Update("Albums", cols, []interface{}{2, 2, album2Budget}),
})
fmt.Fprintf(w, "Moved %d from Album2's MarketingBudget to Album1's.", transferAmt)
}
return nil
})
return err
}
Java
使用 TransactionRunner
介面,在讀寫交易的內容中執行作業主體。這個介面包含 run()
方法,可用來執行讀寫交易,並會視需要重新嘗試。DatabaseClient
類別的 readWriteTransaction
方法會傳回 TransactionRunner
物件,用於執行單一邏輯交易。
TransactionRunner.TransactionCallable
類別包含 run()
方法,可執行單一交易嘗試。run()
會採用 TransactionContext
物件,此物件為交易的內容。
這個範例使用 Struct
類別,方便儲存 readRow()
呼叫的結果。此範例也會使用 Key
類別,代表 Spanner 資料表或索引中的資料列索引鍵。
以下是執行交易的程式碼:
static void writeWithTransaction(DatabaseClient dbClient) {
dbClient
.readWriteTransaction()
.run(transaction -> {
// Transfer marketing budget from one album to another. We do it in a transaction to
// ensure that the transfer is atomic.
Struct row =
transaction.readRow("Albums", Key.of(2, 2), Arrays.asList("MarketingBudget"));
long album2Budget = row.getLong(0);
// 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.
long transfer = 200000;
if (album2Budget >= transfer) {
long album1Budget =
transaction
.readRow("Albums", Key.of(1, 1), Arrays.asList("MarketingBudget"))
.getLong(0);
album1Budget += transfer;
album2Budget -= transfer;
transaction.buffer(
Mutation.newUpdateBuilder("Albums")
.set("SingerId")
.to(1)
.set("AlbumId")
.to(1)
.set("MarketingBudget")
.to(album1Budget)
.build());
transaction.buffer(
Mutation.newUpdateBuilder("Albums")
.set("SingerId")
.to(2)
.set("AlbumId")
.to(2)
.set("MarketingBudget")
.to(album2Budget)
.build());
}
return null;
});
}
Node.js
使用 Database.runTransaction()
執行交易。
以下是執行交易的程式碼:
// This sample transfers 200,000 from the MarketingBudget field
// of the second Album to the first Album, as long as the second
// Album has enough money in its budget. Make sure to run the
// addColumn and updateData samples first (in that order).
// Imports the Google Cloud client library
const {Spanner} = require('@google-cloud/spanner');
/**
* TODO(developer): Uncomment the following lines before running the sample.
*/
// const projectId = 'my-project-id';
// const instanceId = 'my-instance';
// const databaseId = 'my-database';
// Creates a client
const spanner = new Spanner({
projectId: projectId,
});
// Gets a reference to a Cloud Spanner instance and database
const instance = spanner.instance(instanceId);
const database = instance.database(databaseId);
const transferAmount = 200000;
database.runTransaction(async (err, transaction) => {
if (err) {
console.error(err);
return;
}
let firstBudget, secondBudget;
const queryOne = {
columns: ['MarketingBudget'],
keys: [[2, 2]], // SingerId: 2, AlbumId: 2
};
const queryTwo = {
columns: ['MarketingBudget'],
keys: [[1, 1]], // SingerId: 1, AlbumId: 1
};
Promise.all([
// Reads the second album's budget
transaction.read('Albums', queryOne).then(results => {
// Gets second album's budget
const rows = results[0].map(row => row.toJSON());
secondBudget = rows[0].MarketingBudget;
console.log(`The second album's marketing budget: ${secondBudget}`);
// Makes sure the second album's budget is large enough
if (secondBudget < transferAmount) {
throw new Error(
`The second album's budget (${secondBudget}) is less than the transfer amount (${transferAmount}).`,
);
}
}),
// Reads the first album's budget
transaction.read('Albums', queryTwo).then(results => {
// Gets first album's budget
const rows = results[0].map(row => row.toJSON());
firstBudget = rows[0].MarketingBudget;
console.log(`The first album's marketing budget: ${firstBudget}`);
}),
])
.then(() => {
console.log(firstBudget, secondBudget);
// Transfers the budgets between the albums
firstBudget += transferAmount;
secondBudget -= transferAmount;
console.log(firstBudget, secondBudget);
// Updates the database
// Note: Cloud Spanner interprets Node.js numbers as FLOAT64s, so they
// must be converted (back) to strings before being inserted as INT64s.
transaction.update('Albums', [
{
SingerId: '1',
AlbumId: '1',
MarketingBudget: firstBudget.toString(),
},
{
SingerId: '2',
AlbumId: '2',
MarketingBudget: secondBudget.toString(),
},
]);
})
.then(() => {
// Commits the transaction and send the changes to the database
return transaction.commit();
})
.then(() => {
console.log(
`Successfully executed read-write transaction to transfer ${transferAmount} from Album 2 to Album 1.`,
);
})
.catch(err => {
console.error('ERROR:', err);
})
.then(() => {
transaction.end();
// Closes the database when finished
return database.close();
});
});
PHP
使用 Database::runTransaction
執行交易。
以下是執行交易的程式碼:
use Google\Cloud\Spanner\SpannerClient;
use Google\Cloud\Spanner\Transaction;
use UnexpectedValueException;
/**
* Performs a read-write transaction to update two sample records in the
* database.
*
* This will transfer 200,000 from the `MarketingBudget` field for the second
* Album to the first Album. If the `MarketingBudget` for the second Album is
* too low, it will raise an exception.
*
* Before running this sample, you will need to run the `update_data` sample
* to populate the fields.
* Example:
* ```
* read_write_transaction($instanceId, $databaseId);
* ```
*
* @param string $instanceId The Spanner instance ID.
* @param string $databaseId The Spanner database ID.
*/
function read_write_transaction(string $instanceId, string $databaseId): void
{
$spanner = new SpannerClient();
$instance = $spanner->instance($instanceId);
$database = $instance->database($databaseId);
$database->runTransaction(function (Transaction $t) use ($spanner) {
$transferAmount = 200000;
// Read the second album's budget.
$secondAlbumKey = [2, 2];
$secondAlbumKeySet = $spanner->keySet(['keys' => [$secondAlbumKey]]);
$secondAlbumResult = $t->read(
'Albums',
$secondAlbumKeySet,
['MarketingBudget'],
['limit' => 1]
);
$firstRow = $secondAlbumResult->rows()->current();
$secondAlbumBudget = $firstRow['MarketingBudget'];
if ($secondAlbumBudget < $transferAmount) {
// Throwing an exception will automatically roll back the transaction.
throw new UnexpectedValueException(
'The second album\'s budget is lower than the transfer amount: ' . $transferAmount
);
}
$firstAlbumKey = [1, 1];
$firstAlbumKeySet = $spanner->keySet(['keys' => [$firstAlbumKey]]);
$firstAlbumResult = $t->read(
'Albums',
$firstAlbumKeySet,
['MarketingBudget'],
['limit' => 1]
);
// Read the first album's budget.
$firstRow = $firstAlbumResult->rows()->current();
$firstAlbumBudget = $firstRow['MarketingBudget'];
// Update the budgets.
$secondAlbumBudget -= $transferAmount;
$firstAlbumBudget += $transferAmount;
printf('Setting first album\'s budget to %s and the second album\'s ' .
'budget to %s.' . PHP_EOL, $firstAlbumBudget, $secondAlbumBudget);
// Update the rows.
$t->updateBatch('Albums', [
['SingerId' => 1, 'AlbumId' => 1, 'MarketingBudget' => $firstAlbumBudget],
['SingerId' => 2, 'AlbumId' => 2, 'MarketingBudget' => $secondAlbumBudget],
]);
// Commit the transaction!
$t->commit();
print('Transaction complete.' . PHP_EOL);
});
}
Python
使用 Database
類別的 run_in_transaction()
方法執行交易。
以下是執行交易的程式碼:
def read_write_transaction(instance_id, database_id):
"""Performs a read-write transaction to update two sample records in the
database.
This will transfer 200,000 from the `MarketingBudget` field for the second
Album to the first Album. If the `MarketingBudget` is too low, it will
raise an exception.
Before running this sample, you will need to run the `update_data` sample
to populate the fields.
"""
spanner_client = spanner.Client()
instance = spanner_client.instance(instance_id)
database = instance.database(database_id)
def update_albums(transaction):
# Read the second album budget.
second_album_keyset = spanner.KeySet(keys=[(2, 2)])
second_album_result = transaction.read(
table="Albums",
columns=("MarketingBudget",),
keyset=second_album_keyset,
limit=1,
)
second_album_row = list(second_album_result)[0]
second_album_budget = second_album_row[0]
transfer_amount = 200000
if second_album_budget < transfer_amount:
# Raising an exception will automatically roll back the
# transaction.
raise ValueError("The second album doesn't have enough funds to transfer")
# Read the first album's budget.
first_album_keyset = spanner.KeySet(keys=[(1, 1)])
first_album_result = transaction.read(
table="Albums",
columns=("MarketingBudget",),
keyset=first_album_keyset,
limit=1,
)
first_album_row = list(first_album_result)[0]
first_album_budget = first_album_row[0]
# Update the budgets.
second_album_budget -= transfer_amount
first_album_budget += transfer_amount
print(
"Setting first album's budget to {} and the second album's "
"budget to {}.".format(first_album_budget, second_album_budget)
)
# Update the rows.
transaction.update(
table="Albums",
columns=("SingerId", "AlbumId", "MarketingBudget"),
values=[(1, 1, first_album_budget), (2, 2, second_album_budget)],
)
database.run_in_transaction(update_albums)
print("Transaction complete.")
Ruby
請使用 Client
類別的 transaction
方法執行交易。
以下是執行交易的程式碼:
# project_id = "Your Google Cloud project ID"
# instance_id = "Your Spanner instance ID"
# database_id = "Your Spanner database ID"
require "google/cloud/spanner"
spanner = Google::Cloud::Spanner.new project: project_id
client = spanner.client instance_id, database_id
transfer_amount = 200_000
client.transaction do |transaction|
first_album = transaction.read("Albums", [:MarketingBudget], keys: [[1, 1]]).rows.first
second_album = transaction.read("Albums", [:MarketingBudget], keys: [[2, 2]]).rows.first
raise "The second album does not have enough funds to transfer" if second_album[:MarketingBudget] < transfer_amount
new_first_album_budget = first_album[:MarketingBudget] + transfer_amount
new_second_album_budget = second_album[:MarketingBudget] - transfer_amount
transaction.update "Albums", [
{ SingerId: 1, AlbumId: 1, MarketingBudget: new_first_album_budget },
{ SingerId: 2, AlbumId: 2, MarketingBudget: new_second_album_budget }
]
end
puts "Transaction complete"
刪除資料表中的資料列
每個用戶端程式庫都會提供多種刪除資料列的方法:
- 刪除資料表中的所有資料列。
- 指定資料列的索引鍵資料欄值以刪除單一資料列。
- 建立索引鍵範圍以刪除一組資料列。
- 如果交錯式資料表的結構定義包含
ON DELETE CASCADE
,則刪除父項資料列即可刪除交錯式資料表中的資料列。
C++
使用用戶端的 DeleteMutationBuilder()
函式刪除資料列。
以下程式碼顯示如何刪除資料:
void DeleteData(google::cloud::spanner::Client client) {
namespace spanner = ::google::cloud::spanner;
// Delete the albums with key (2,1) and (2,3).
auto delete_albums = spanner::DeleteMutationBuilder(
"Albums", spanner::KeySet()
.AddKey(spanner::MakeKey(2, 1))
.AddKey(spanner::MakeKey(2, 3)))
.Build();
// Delete some singers using the keys in the range [3, 5]
auto delete_singers_range =
spanner::DeleteMutationBuilder(
"Singers", spanner::KeySet().AddRange(spanner::MakeKeyBoundClosed(3),
spanner::MakeKeyBoundOpen(5)))
.Build();
// Deletes remaining rows from the Singers table and the Albums table, because
// the Albums table is defined with ON DELETE CASCADE.
auto delete_singers_all =
spanner::MakeDeleteMutation("Singers", spanner::KeySet::All());
auto commit_result = client.Commit(spanner::Mutations{
delete_albums, delete_singers_range, delete_singers_all});
if (!commit_result) throw std::move(commit_result).status();
std::cout << "Delete was successful [spanner_delete_data]\n";
}
C#
使用 connection.CreateDeleteCommand()
方法刪除資料列,該方法會建立新的 SpannerCommand
來刪除資料列。SpannerCommand.ExecuteNonQueryAsync()
方法會從資料表刪除資料列。
此範例會個別刪除 Singers
資料表中的資料列,Albums
資料表中的資料列會遭刪除,因為 Albums
資料表在 Singers
資料表中交錯且使用 ON DELETE CASCADE
定義。
using Google.Cloud.Spanner.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
public class DeleteDataAsyncSample
{
public class Album
{
public int SingerId { get; set; }
public int AlbumId { get; set; }
public string AlbumTitle { get; set; }
}
public async Task<int> DeleteDataAsync(string projectId, string instanceId, string databaseId)
{
string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";
var albums = new List<Album>
{
new Album { SingerId = 2, AlbumId = 1, AlbumTitle = "Green" },
new Album { SingerId = 2, AlbumId = 3, AlbumTitle = "Terrified" },
};
int rowCount = 0;
using (var connection = new SpannerConnection(connectionString))
{
await connection.OpenAsync();
// Delete individual rows from the Albums table.
await Task.WhenAll(albums.Select(async album =>
{
var cmd = connection.CreateDeleteCommand("Albums", new SpannerParameterCollection
{
{ "SingerId", SpannerDbType.Int64, album.SingerId },
{ "AlbumId", SpannerDbType.Int64, album.AlbumId }
});
rowCount += await cmd.ExecuteNonQueryAsync();
}));
Console.WriteLine("Deleted individual rows in Albums.");
// Delete a range of rows from the Singers table where the column key is >=3 and <5.
var cmd = connection.CreateDmlCommand("DELETE FROM Singers WHERE SingerId >= 3 AND SingerId < 5");
rowCount += await cmd.ExecuteNonQueryAsync();
Console.WriteLine($"{rowCount} row(s) deleted from Singers.");
// Delete remaining Singers rows, which will also delete the remaining
// Albums rows since it was defined with ON DELETE CASCADE.
cmd = connection.CreateDmlCommand("DELETE FROM Singers WHERE true");
rowCount += await cmd.ExecuteNonQueryAsync();
Console.WriteLine($"{rowCount} row(s) deleted from Singers.");
}
return rowCount;
}
}
Go
使用 Mutation
刪除資料列。使用 Mutation.Delete()
方法建構 DELETE
變異,用於刪除資料列。Client.Apply()
方法會以不可分割的形式將變異套用到資料庫。
此範例會個別刪除 Albums
資料表中的資料列,接著使用 KeyRange 刪除 Singers
資料表中的所有資料列。
import (
"context"
"io"
"cloud.google.com/go/spanner"
)
func delete(w io.Writer, db string) error {
ctx := context.Background()
client, err := spanner.NewClient(ctx, db)
if err != nil {
return err
}
defer client.Close()
m := []*spanner.Mutation{
// spanner.Key can be used to delete a specific set of rows.
// Delete the Albums with the key values (2,1) and (2,3).
spanner.Delete("Albums", spanner.Key{2, 1}),
spanner.Delete("Albums", spanner.Key{2, 3}),
// spanner.KeyRange can be used to delete rows with a key in a specific range.
// Delete a range of rows where the column key is >=3 and <5
spanner.Delete("Singers", spanner.KeyRange{Start: spanner.Key{3}, End: spanner.Key{5}, Kind: spanner.ClosedOpen}),
// spanner.AllKeys can be used to delete all the rows in a table.
// Delete remaining Singers rows, which will also delete the remaining Albums rows since it was
// defined with ON DELETE CASCADE.
spanner.Delete("Singers", spanner.AllKeys()),
}
_, err = client.Apply(ctx, m)
return err
}
Java
使用 Mutation.delete()
方法刪除資料列。
這個範例會使用 KeySet.all()
方法刪除 Albums
資料表中的所有資料列。刪除 Albums
資料表中的資料列後,此範例會使用 KeySet.singleKey()
方法建立的鍵,個別刪除 Singers
資料表中的資料列。
static void deleteExampleData(DatabaseClient dbClient) {
List<Mutation> mutations = new ArrayList<>();
// KeySet.Builder can be used to delete a specific set of rows.
// Delete the Albums with the key values (2,1) and (2,3).
mutations.add(
Mutation.delete(
"Albums", KeySet.newBuilder().addKey(Key.of(2, 1)).addKey(Key.of(2, 3)).build()));
// KeyRange can be used to delete rows with a key in a specific range.
// Delete a range of rows where the column key is >=3 and <5
mutations.add(
Mutation.delete("Singers", KeySet.range(KeyRange.closedOpen(Key.of(3), Key.of(5)))));
// KeySet.all() can be used to delete all the rows in a table.
// Delete remaining Singers rows, which will also delete the remaining Albums rows since it was
// defined with ON DELETE CASCADE.
mutations.add(Mutation.delete("Singers", KeySet.all()));
dbClient.write(mutations);
System.out.printf("Records deleted.\n");
}
Node.js
使用 table.deleteRows()
方法刪除資料列。
此範例會使用 table.deleteRows()
方法刪除 Singers
資料表中的所有資料列。而 Albums
資料表中的資料列亦會遭刪除,因為 Albums
資料表在 Singers
資料表中交錯且使用 ON
DELETE CASCADE
定義。
// Imports the Google Cloud client library
const {Spanner} = require('@google-cloud/spanner');
/**
* TODO(developer): Uncomment the following lines before running the sample.
*/
// const projectId = 'my-project-id';
// const instanceId = 'my-instance';
// const databaseId = 'my-database';
// Creates a client
const spanner = new Spanner({
projectId: projectId,
});
// Gets a reference to a Cloud Spanner instance and database
const instance = spanner.instance(instanceId);
const database = instance.database(databaseId);
// Instantiate Spanner table object
const albumsTable = database.table('Albums');
// Deletes individual rows from the Albums table.
try {
const keys = [
[2, 1],
[2, 3],
];
await albumsTable.deleteRows(keys);
console.log('Deleted individual rows in Albums.');
} catch (err) {
console.error('ERROR:', err);
}
// Delete a range of rows where the column key is >=3 and <5
database.runTransaction(async (err, transaction) => {
if (err) {
console.error(err);
return;
}
try {
const [rowCount] = await transaction.runUpdate({
sql: 'DELETE FROM Singers WHERE SingerId >= 3 AND SingerId < 5',
});
console.log(`${rowCount} records deleted from Singers.`);
} catch (err) {
console.error('ERROR:', err);
}
// Deletes remaining rows from the Singers table and the Albums table,
// because Albums table is defined with ON DELETE CASCADE.
try {
// The WHERE clause is required for DELETE statements to prevent
// accidentally deleting all rows in a table.
// https://cloud.google.com/spanner/docs/dml-syntax#where_clause
const [rowCount] = await transaction.runUpdate({
sql: 'DELETE FROM Singers WHERE true',
});
console.log(`${rowCount} records deleted from Singers.`);
await transaction.commit();
} catch (err) {
console.error('ERROR:', err);
} finally {
// Close the database when finished.
await database.close();
}
});
PHP
使用 Database::delete() method
刪除資料列。Database::delete()
方法頁面包含一個範例。
Python
使用 Batch.delete()
方法刪除資料列。
此範例會使用 KeySet
物件,個別刪除 Albums
和 Singers
資料表中的所有資料列。
def delete_data(instance_id, database_id):
"""Deletes sample data from the given database.
The database, table, and data must already exist and can be created using
`create_database` and `insert_data`.
"""
spanner_client = spanner.Client()
instance = spanner_client.instance(instance_id)
database = instance.database(database_id)
# Delete individual rows
albums_to_delete = spanner.KeySet(keys=[[2, 1], [2, 3]])
# Delete a range of rows where the column key is >=3 and <5
singers_range = spanner.KeyRange(start_closed=[3], end_open=[5])
singers_to_delete = spanner.KeySet(ranges=[singers_range])
# Delete remaining Singers rows, which will also delete the remaining
# Albums rows because Albums was defined with ON DELETE CASCADE
remaining_singers = spanner.KeySet(all_=True)
with database.batch() as batch:
batch.delete("Albums", albums_to_delete)
batch.delete("Singers", singers_to_delete)
batch.delete("Singers", remaining_singers)
print("Deleted data.")
Ruby
使用 Client#delete
方法刪除資料列。Client#delete
頁面包含一個範例。
# project_id = "Your Google Cloud project ID"
# instance_id = "Your Spanner instance ID"
# database_id = "Your Spanner database ID"
require "google/cloud/spanner"
spanner = Google::Cloud::Spanner.new project: project_id
client = spanner.client instance_id, database_id
# Delete individual rows
client.delete "Albums", [[2, 1], [2, 3]]
# Delete a range of rows where the column key is >=3 and <5
key_range = client.range 3, 5, exclude_end: true
client.delete "Singers", key_range
# Delete remaining Singers rows, which will also delete the remaining
# Albums rows because Albums was defined with ON DELETE CASCADE
client.delete "Singers"