or run

tessl search
Log in

Version

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
golangpkg:golang/cloud.google.com/go/spanner@v1.87.0

docs

client.mddml.mdindex.mdkeys.mdlow-level.mdprotobuf-types.mdreads.mdtesting.mdtransactions.mdtypes.mdwrites.md
tile.json

tessl/golang-cloud-google-com--go--spanner

tessl install tessl/golang-cloud-google-com--go--spanner@1.87.2

Official Google Cloud Spanner client library for Go providing comprehensive database operations, transactions, and admin functionality

transactions.mddocs/

Transactions

Comprehensive guide to Spanner transactions including read-only, read-write, batch, and statement-based transactions.

Transaction Types

Spanner supports multiple transaction types:

  1. Single Read: One-time read operations (Client.Single())
  2. ReadOnlyTransaction: Multiple consistent reads at same timestamp
  3. ReadWriteTransaction: Automatic retry with read-modify-write operations
  4. BatchReadOnlyTransaction: Partitioned reads for parallel processing
  5. ReadWriteStmtBasedTransaction: Manual transaction control

Read-Only Transactions

Single Read Transactions

For one-shot operations:

func (c *Client) Single() *ReadOnlyTransaction

Example:

row, err := client.Single().ReadRow(ctx, "Users", key, columns)

Multi-Read Transactions

For multiple consistent reads:

func (c *Client) ReadOnlyTransaction() *ReadOnlyTransaction

ReadOnlyTransaction Methods:

func (t *ReadOnlyTransaction) Read(ctx context.Context, table string, keys KeySet, columns []string) *RowIterator
func (t *ReadOnlyTransaction) ReadRow(ctx context.Context, table string, key Key, columns []string) (*Row, error)
func (t *ReadOnlyTransaction) ReadUsingIndex(ctx context.Context, table, index string, keys KeySet, columns []string) *RowIterator
func (t *ReadOnlyTransaction) ReadRowUsingIndex(ctx context.Context, table, index string, key Key, columns []string) (*Row, error)
func (t *ReadOnlyTransaction) ReadWithOptions(ctx context.Context, table string, keys KeySet, columns []string, opts *ReadOptions) *RowIterator
func (t *ReadOnlyTransaction) ReadRowWithOptions(ctx context.Context, table string, key Key, columns []string, opts *ReadOptions) (*Row, error)
func (t *ReadOnlyTransaction) Query(ctx context.Context, statement Statement) *RowIterator
func (t *ReadOnlyTransaction) QueryWithOptions(ctx context.Context, statement Statement, opts QueryOptions) *RowIterator
func (t *ReadOnlyTransaction) QueryWithStats(ctx context.Context, statement Statement) *RowIterator
func (t *ReadOnlyTransaction) AnalyzeQuery(ctx context.Context, statement Statement) (*sppb.QueryPlan, error)
func (t *ReadOnlyTransaction) Close()
func (t *ReadOnlyTransaction) Timestamp() (time.Time, error)
func (t *ReadOnlyTransaction) WithTimestampBound(tb TimestampBound) *ReadOnlyTransaction
func (t *ReadOnlyTransaction) WithBeginTransactionOption(option BeginTransactionOption) *ReadOnlyTransaction

Example:

txn := client.ReadOnlyTransaction()
defer txn.Close()

// All reads at same timestamp
row1, err := txn.ReadRow(ctx, "Users", key1, cols)
row2, err := txn.ReadRow(ctx, "Accounts", key2, cols)

ts, _ := txn.Timestamp()
fmt.Printf("Read at: %v\n", ts)

Read-Write Transactions

Client.ReadWriteTransaction

Automatic retry for read-modify-write operations:

func (c *Client) ReadWriteTransaction(ctx context.Context, f func(context.Context, *ReadWriteTransaction) error) (commitTimestamp time.Time, err error)
func (c *Client) ReadWriteTransactionWithOptions(ctx context.Context, f func(context.Context, *ReadWriteTransaction) error, options TransactionOptions) (CommitResponse, error)

ReadWriteTransaction Methods:

// Read operations
func (t *ReadWriteTransaction) Read(ctx context.Context, table string, keys KeySet, columns []string) *RowIterator
func (t *ReadWriteTransaction) ReadRow(ctx context.Context, table string, key Key, columns []string) (*Row, error)
func (t *ReadWriteTransaction) ReadUsingIndex(ctx context.Context, table, index string, keys KeySet, columns []string) *RowIterator
func (t *ReadWriteTransaction) ReadRowUsingIndex(ctx context.Context, table, index string, key Key, columns []string) (*Row, error)
func (t *ReadWriteTransaction) ReadWithOptions(ctx context.Context, table string, keys KeySet, columns []string, opts *ReadOptions) *RowIterator
func (t *ReadWriteTransaction) ReadRowWithOptions(ctx context.Context, table string, key Key, columns []string, opts *ReadOptions) (*Row, error)
func (t *ReadWriteTransaction) Query(ctx context.Context, statement Statement) *RowIterator
func (t *ReadWriteTransaction) QueryWithOptions(ctx context.Context, statement Statement, opts QueryOptions) *RowIterator
func (t *ReadWriteTransaction) QueryWithStats(ctx context.Context, statement Statement) *RowIterator
func (t *ReadWriteTransaction) AnalyzeQuery(ctx context.Context, statement Statement) (*sppb.QueryPlan, error)

// Write operations
func (t *ReadWriteTransaction) BufferWrite(ms []*Mutation) error
func (t *ReadWriteTransaction) Update(ctx context.Context, stmt Statement) (rowCount int64, err error)
func (t *ReadWriteTransaction) UpdateWithOptions(ctx context.Context, stmt Statement, opts QueryOptions) (rowCount int64, err error)
func (t *ReadWriteTransaction) BatchUpdate(ctx context.Context, stmts []Statement) ([]int64, error)
func (t *ReadWriteTransaction) BatchUpdateWithOptions(ctx context.Context, stmts []Statement, opts QueryOptions) ([]int64, error)

Example:

_, err := client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
    // Read current balance
    row, err := txn.ReadRow(ctx, "Accounts", spanner.Key{"alice"}, []string{"balance"})
    if err != nil {
        return err
    }
    
    var balance int64
    if err := row.Column(0, &balance); err != nil {
        return err
    }
    
    // Check funds
    if balance < 100 {
        return fmt.Errorf("insufficient funds")
    }
    
    // Deduct amount
    balance -= 100
    m := spanner.Update("Accounts",
        []string{"user", "balance"},
        []interface{}{"alice", balance})
    
    return txn.BufferWrite([]*spanner.Mutation{m})
})

TransactionOptions

type TransactionOptions struct {
    CommitOptions               CommitOptions
    TransactionTag              string
    CommitPriority              sppb.RequestOptions_Priority
    ReadLockMode                sppb.TransactionOptions_ReadWrite_ReadLockMode
    ExcludeTxnFromChangeStreams bool
    IsolationLevel              sppb.TransactionOptions_IsolationLevel
    BeginTransactionOption      BeginTransactionOption
}

type CommitOptions struct {
    ReturnCommitStats bool
    MaxCommitDelay    *time.Duration
}

type CommitResponse struct {
    CommitTs    time.Time
    CommitStats *sppb.CommitResponse_CommitStats
}

Example:

opts := spanner.TransactionOptions{
    TransactionTag: "transfer-funds",
    CommitOptions: spanner.CommitOptions{
        ReturnCommitStats: true,
    },
    CommitPriority: sppb.RequestOptions_PRIORITY_HIGH,
}

resp, err := client.ReadWriteTransactionWithOptions(ctx, 
    func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
        // Transaction logic
        return txn.BufferWrite(mutations)
    }, opts)

if err == nil {
    fmt.Printf("Commit stats: %v\n", resp.CommitStats)
}

Statement-Based Transactions

For manual control (advanced users):

func NewReadWriteStmtBasedTransaction(ctx context.Context, c *Client) (*ReadWriteStmtBasedTransaction, error)
func NewReadWriteStmtBasedTransactionWithOptions(ctx context.Context, c *Client, options TransactionOptions) (*ReadWriteStmtBasedTransaction, error)
func NewReadWriteStmtBasedTransactionWithCallbackForOptions(ctx context.Context, c *Client, opts TransactionOptions, callback func() TransactionOptions) (*ReadWriteStmtBasedTransaction, error)

ReadWriteStmtBasedTransaction Methods:

// All ReadWriteTransaction methods plus:
func (t *ReadWriteStmtBasedTransaction) Commit(ctx context.Context) (time.Time, error)
func (t *ReadWriteStmtBasedTransaction) CommitWithReturnResp(ctx context.Context) (CommitResponse, error)
func (t *ReadWriteStmtBasedTransaction) Rollback(ctx context.Context)
func (t *ReadWriteStmtBasedTransaction) ResetForRetry(ctx context.Context) (*ReadWriteStmtBasedTransaction, error)

Example:

txn, err := spanner.NewReadWriteStmtBasedTransaction(ctx, client)
if err != nil {
    return err
}

// Manually manage transaction
row, err := txn.ReadRow(ctx, "Users", key, cols)
if err != nil {
    txn.Rollback(ctx)
    return err
}

// Perform updates
rowCount, err := txn.Update(ctx, stmt)
if err != nil {
    // Check if aborted
    if spanner.ErrCode(err) == codes.Aborted {
        // Manually retry
        newTxn, err := txn.ResetForRetry(ctx)
        if err != nil {
            return err
        }
        txn = newTxn
        // Retry logic...
    }
    txn.Rollback(ctx)
    return err
}

// Commit
commitTS, err := txn.Commit(ctx)
if err != nil {
    return err
}

Batch Read-Only Transactions

For partitioned reads across machines:

func (c *Client) BatchReadOnlyTransaction(ctx context.Context, tb TimestampBound) (*BatchReadOnlyTransaction, error)
func (c *Client) BatchReadOnlyTransactionFromID(tid BatchReadOnlyTransactionID) *BatchReadOnlyTransaction

type BatchReadOnlyTransaction struct {
    ReadOnlyTransaction
    ID BatchReadOnlyTransactionID
}

Partition Methods:

func (t *BatchReadOnlyTransaction) PartitionRead(ctx context.Context, table string, keys KeySet, columns []string, opt PartitionOptions) ([]*Partition, error)
func (t *BatchReadOnlyTransaction) PartitionReadUsingIndex(ctx context.Context, table, index string, keys KeySet, columns []string, opt PartitionOptions) ([]*Partition, error)
func (t *BatchReadOnlyTransaction) PartitionReadWithOptions(ctx context.Context, table string, keys KeySet, columns []string, opt PartitionOptions, readOptions ReadOptions) ([]*Partition, error)
func (t *BatchReadOnlyTransaction) PartitionQuery(ctx context.Context, statement Statement, opt PartitionOptions) ([]*Partition, error)
func (t *BatchReadOnlyTransaction) PartitionQueryWithOptions(ctx context.Context, statement Statement, opt PartitionOptions, qOpts QueryOptions) ([]*Partition, error)
func (t *BatchReadOnlyTransaction) Execute(ctx context.Context, p *Partition) *RowIterator
func (t *BatchReadOnlyTransaction) Cleanup(ctx context.Context)

type PartitionOptions struct {
    PartitionBytes int64
    MaxPartitions  int64
}

type Partition struct {
    // Has unexported fields
}

func (p *Partition) MarshalBinary() (data []byte, err error)
func (p *Partition) UnmarshalBinary(data []byte) error
func (p *Partition) GetPartitionToken() []byte

Example:

txn, err := client.BatchReadOnlyTransaction(ctx, spanner.StrongRead())
if err != nil {
    return err
}
defer txn.Cleanup(ctx)

stmt := spanner.NewStatement("SELECT * FROM Users")
partitions, err := txn.PartitionQuery(ctx, stmt, spanner.PartitionOptions{
    PartitionBytes: 100000000,
})
if err != nil {
    return err
}

// Process partitions in parallel
for _, p := range partitions {
    go func(partition *spanner.Partition) {
        iter := txn.Execute(ctx, partition)
        defer iter.Stop()
        
        for {
            row, err := iter.Next()
            if err == iterator.Done {
                break
            }
            // Process row
        }
    }(p)
}

Timestamp Bounds

Control read timestamps:

func StrongRead() TimestampBound
func ReadTimestamp(t time.Time) TimestampBound
func ExactStaleness(d time.Duration) TimestampBound
func MinReadTimestamp(t time.Time) TimestampBound
func MaxStaleness(d time.Duration) TimestampBound

Examples:

// Strong read (default)
txn := client.Single().WithTimestampBound(spanner.StrongRead())

// Exact staleness
txn := client.Single().WithTimestampBound(spanner.ExactStaleness(15 * time.Second))

// Max staleness
txn := client.Single().WithTimestampBound(spanner.MaxStaleness(10 * time.Second))

// Specific timestamp
ts := time.Now().Add(-1 * time.Hour)
txn := client.Single().WithTimestampBound(spanner.ReadTimestamp(ts))

// Min read timestamp
minTs := time.Now().Add(-5 * time.Minute)
txn := client.Single().WithTimestampBound(spanner.MinReadTimestamp(minTs))

Transaction Error Handling

Common Errors

import "google.golang.org/grpc/codes"

_, err := client.ReadWriteTransaction(ctx, f)
if err != nil {
    switch spanner.ErrCode(err) {
    case codes.Aborted:
        // Automatically retried by ReadWriteTransaction
    case codes.DeadlineExceeded:
        // Transaction timeout
    case codes.FailedPrecondition:
        // Invalid state or constraint
    case codes.InvalidArgument:
        // Bad input
    default:
        // Other error
    }
}

TransactionOutcomeUnknownError

type TransactionOutcomeUnknownError struct {
    // Has unexported fields
}

func (e *TransactionOutcomeUnknownError) Error() string
func (e *TransactionOutcomeUnknownError) Unwrap() error

Indicates transaction outcome is unknown (timeout/cancel after commit sent).

Best Practices

  1. Prefer ReadWriteTransaction: Automatic retry handling
  2. Keep transactions short: Minimize lock duration
  3. Read then write: Complete reads before writes
  4. Use timestamp bounds: Stale reads for non-critical data
  5. Avoid long-running transactions: May be aborted
  6. Handle aborts gracefully: Let framework retry
  7. Set transaction tags: For tracking and debugging
  8. Use batch operations: Group reads/writes when possible
  9. Monitor transaction metrics: Detect patterns
  10. Test retry logic: Ensure idempotency