Skip to content

TxnKV Basic

Ping Yu edited this page Nov 17, 2022 · 5 revisions

Basic Transactional API

The txnkv package provides a transactional API against TiKV cluster.

Creating Client

Information about a TiKV cluster can be found by the address of PD server. After starting a TiKV cluster successfully, we can use PD's address list to create a client to interact with it. The following code demonstrate the process of creating a txnkv client.

import "github.com/tikv/client-go/v2/txnkv"

client, err := txnkv.NewClient([]string{"127.0.0.1:2379"})

Closing Client

When you are done with a client, you need to gracefully close the client to finish pending tasks and terminate all background jobs.

Example:

// ... create a client as described above ...
// ... do something with the client ...
if err := client.Close(); err != nil {
    // ... handle error ...
}

Starting Transaction

When using the transactional API, almost all read and write operations are done within a transaction (or a snapshot). You can use Begin to start a transaction.

txn, err := client.Begin(opts...)
if err != nil {
    // ... handle error ...
}
// ... use txn for read and write ...

The Begin process gets a timestamp from PD as the StartTS of the transaction and does some necessary initialization of the transaction object, such as allocating MemBuffer for buffering subsequent writes.

Begin currently supports two options. WithTxnScope is used to specify the scope of the transaction, for more details please refer to Advanced Operations. WithStartTS is used to specify the StartTS of the transaction, this option is usually used to create read-only transactions that read historical versions.

In this section we will only discuss the case where optimistic locking is used. All writes are buffered on the client until the transaction is committed.

Reads

TxnKV provides Get, BatchGet, Iter and IterReverse methods to query TiKV.

Get retrives a key-value record from TiKV. If this transaction has buffered (pending commit) writes, Get will read the data in the MemBuffer first. Specially, Get returns ErrNotExists if there is a buffered delete operation for this record.

import tikverr "github.com/tikv/client-go/v2/error"

v, err := txn.Get(context.TODO(), []byte("foo"))
if tikverr.IsErrNotFound(err) {
    // ... handle not found ...
}
if err != nil {
    // ... handle other errors ...
}
// ... handle value v ...

When reading multiple keys from TiKV, BatchGet can be used. It returns a key-value map, and when a key does not exist, it does not appear in the map.

values, err := txn.BatchGet(context.TODO(), keys)
if err != nil {
    // ... handle error ...
}
for _, k := range keys {
    if v, ok := values[string(k)]; ok {
        // ... handle record k:v ...
    } else {
        // ... k does not exist ...
    }
}

All key-value records are logically arranged in sorted order in TiKV. The iterators allow applications to do range scans on TiKV. The iterator yields records in the range [start, end). Start key and end key are arbitrary bytes. Nil key stands for "" when used as start key, and stands for +inf when used as end key.

Like Get and BatchGet, if the transaction has buffered writes, the iterator will read the data in the MemBuffer first.

iter, err := txn.Iter(start, end)
if err != nil {
    // ... handle error ...
}
defer iter.Close()
for iter.Valid() {
    k, v := iter.Key(), iter.Value()
    // ... handle record k:v
    if err := iter.Next(); err != nil {
        // ... handle error ...
    }
}
// ... iteration stops ...

IterReverse also creates an iterator instance, but it iterates in reverse order. Note in particular that the end key is exclusive. That is, the first record returned by iterator is the last record in the range [start, end).

Writes

You can use Set and Delete methods to write data into the transaction. Currently, due to the limitation of MemBuffer implementation, empty value is not allowed. If you try to set empty value, ErrCannotSetNilValue will be returned.

Example:

if err := txn.Set([]byte("foo"), []byte("bar")); err != nil {
    // ... handle error ...
}
if err := txn.Delete([]byte("foo")); err != nil {
    // ... handle error ...
}

Committing or Rolling Back Transaction

To actually commit the transaction to TiKV, you need to call Commit to trigger the two-phase commit process.

If the transaction does not need to commit, for optimistic transactions, you can just discard the transaction instance, for pessimistic transactions you need to actively call the Rollback() method to clean up the data previously sent to TiKV.

if err := txn.Commit(context.TODO()); err != nil {
    // ... handle error ...
}
// ... commit success ...

Snapshots (Read-Only Transactions)

If you want to create a read-only transaction, you can use GetSnapshot method to create a snapshot. A Snapshot is more lightweight than a transaction. It does not have an embeded MemBuffer and all reads are done directly from TiKV.

ts, err := client.CurrentTimestamp("global")
if err != nil {
    // ... handle error ...
}
snapshot := client.GetSnapshot(ts)
v, err := snapshot.Get(context.TODO(), []byte("foo"))
// ... handle Get result ...

Snapshot can also be extracted from a existed transaction.

snapshot := txn.GetSnapshot()
// ... use snapshot ...