Setting up the transaction execution mode
To run your queries, first you need to specify the transaction execution mode in the YDB SDK.
Below are code examples showing the YDB SDK built-in tools to create an object for the transaction execution mode.
ImplicitTx
ImplicitTx mode allows executing a single query without explicit transaction control. The query is executed in its own implicit transaction that automatically commits if successful.
package main
import (
"context"
"fmt"
"os"
"github.com/ydb-platform/ydb-go-sdk/v3"
"github.com/ydb-platform/ydb-go-sdk/v3/query"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
db, err := ydb.Open(ctx,
os.Getenv("YDB_CONNECTION_STRING"),
ydb.WithAccessTokenCredentials(os.Getenv("YDB_TOKEN")),
)
if err != nil {
panic(err)
}
defer db.Close(ctx)
row, err := db.Query().QueryRow(ctx, "SELECT 1",
query.WithTxControl(query.ImplicitTxControl()),
)
if err != nil {
fmt.Printf("unexpected error: %v", err)
}
// work with row
_ = row
}
package main
import (
"context"
"database/sql"
"fmt"
"os"
"github.com/ydb-platform/ydb-go-sdk/v3"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
nativeDriver, err := ydb.Open(ctx,
os.Getenv("YDB_CONNECTION_STRING"),
ydb.WithAccessTokenCredentials(os.Getenv("YDB_TOKEN")),
)
if err != nil {
panic(err)
}
defer nativeDriver.Close(ctx)
connector, err := ydb.Connector(nativeDriver)
if err != nil {
panic(err)
}
defer connector.Close()
db := sql.OpenDB(connector)
defer db.Close()
// ImplicitTx — query without an explicit transaction (auto-commit)
row := db.QueryRowContext(ctx, "SELECT 1")
var result int
if err := row.Scan(&result); err != nil {
fmt.Printf("unexpected error: %v", err)
}
}
import tech.ydb.query.QueryClient;
import tech.ydb.query.tools.QueryReader;
import tech.ydb.query.tools.SessionRetryContext;
// ...
try (QueryClient queryClient = QueryClient.newClient(transport).build()) {
SessionRetryContext retryCtx = SessionRetryContext.create(queryClient).build();
QueryReader reader = retryCtx.supplyResult(
session -> QueryReader.readFrom(session.createQuery("SELECT 1", TxMode.NONE))
);
// work with reader
}
On the JDBC side, implicit mode corresponds to auto-commit (Connection.setAutoCommit(true) by default): each standalone statement runs as its own transaction and commits automatically. With setAutoCommit(false), boundaries are defined by explicit commit / rollback.
In the current YDB JDBC driver, for standalone read calls, snapshot mode is used when setReadOnly(true) is set on the Connection. Write queries use Serializable Read/Write (the connection must not be read-only).
In Spring, the @Transactional(readOnly = true) attribute triggers Connection.setReadOnly(true) when the transaction starts, so snapshot is selected automatically for read-only flows. Hibernate, JOOQ, and other JDBC wrappers use the same connection — set read-only the same way (or via framework transaction settings if they propagate it to Connection).
dbapi
import ydb_dbapi as dbapi
with dbapi.connect(host="localhost", port="2136", database="/local") as connection:
connection.set_isolation_level(dbapi.IsolationLevel.AUTOCOMMIT)
import ydb
def execute_query(pool: ydb.QuerySessionPool):
pool.execute_with_retries("SELECT 1")
auto result = session.ExecuteQuery(
"SELECT 1",
NYdb::NQuery::TTxControl::NoTx()
).GetValueSync();
ADO.NET
using Ydb.Sdk.Ado;
await using var connection = await dataSource.OpenRetryableConnectionAsync();
// Execute without explicit transaction (auto-commit)
await using var command = new YdbCommand(connection) { CommandText = "SELECT 1" };
await command.ExecuteNonQueryAsync();
Entity Framework
using Microsoft.EntityFrameworkCore;
await using var context = await dbContextFactory.CreateDbContextAsync();
// Entity Framework auto-commit mode (no explicit transaction)
var result = await context.SomeEntities.FirstOrDefaultAsync();
linq2db
using LinqToDB;
using LinqToDB.Data;
using var db = new DataConnection(
new DataOptions().UseConnectionString(
"YDB",
"Host=localhost;Port=2136;Database=/local;UseTls=false"
)
);
// linq2db auto-commit mode (no explicit transaction)
var result = db.GetTable<Employee>().FirstOrDefault(e => e.Id == 1);
using Ydb.Sdk.Services.Query;
// ImplicitTx - single query without explicit transaction
var response = await queryClient.Exec("SELECT 1");
import { sql } from '@ydbjs/query';
// ...
// ImplicitTx - single query without explicit transaction
const result = await sql`SELECT 1`;
ImplicitTx mode is not supported.
<?php
use YdbPlatform\Ydb\Ydb;
$config = [
// YDB config
];
$ydb = new Ydb($config);
$result = $ydb->table()->query('SELECT 1;');
Serializable
package main
import (
"context"
"fmt"
"os"
"github.com/ydb-platform/ydb-go-sdk/v3"
"github.com/ydb-platform/ydb-go-sdk/v3/query"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
db, err := ydb.Open(ctx,
os.Getenv("YDB_CONNECTION_STRING"),
ydb.WithAccessTokenCredentials(os.Getenv("YDB_TOKEN")),
)
if err != nil {
panic(err)
}
defer db.Close(ctx)
row, err := db.Query().QueryRow(ctx, "SELECT 1",
query.WithTxControl(query.SerializableReadWriteTxControl(query.CommitTx())),
)
if err != nil {
fmt.Printf("unexpected error: %v", err)
}
// work with row
_ = row
}
package main
import (
"context"
"database/sql"
"fmt"
"os"
"github.com/ydb-platform/ydb-go-sdk/v3"
"github.com/ydb-platform/ydb-go-sdk/v3/retry"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
nativeDriver, err := ydb.Open(ctx,
os.Getenv("YDB_CONNECTION_STRING"),
ydb.WithAccessTokenCredentials(os.Getenv("YDB_TOKEN")),
)
if err != nil {
panic(err)
}
defer nativeDriver.Close(ctx)
connector, err := ydb.Connector(nativeDriver)
if err != nil {
panic(err)
}
defer connector.Close()
db := sql.OpenDB(connector)
defer db.Close()
err = retry.DoTx(ctx, db,
func(ctx context.Context, tx *sql.Tx) error {
row := tx.QueryRowContext(ctx, "SELECT 1")
var result int
return row.Scan(&result)
},
retry.WithIdempotent(true),
// Serializable Read-Write mode is used by default for transactions.
// Alternatively, set it explicitly as shown below.
retry.WithTxOptions(&sql.TxOptions{
Isolation: sql.LevelSerializable,
ReadOnly: false,
}),
)
if err != nil {
fmt.Printf("unexpected error: %v", err)
}
}
import tech.ydb.query.QueryClient;
import tech.ydb.query.TxMode;
import tech.ydb.query.tools.QueryReader;
import tech.ydb.query.tools.SessionRetryContext;
// ...
try (QueryClient queryClient = QueryClient.newClient(transport).build()) {
SessionRetryContext retryCtx = SessionRetryContext.create(queryClient).build();
QueryReader reader = retryCtx.supplyResult(
session -> QueryReader.readFrom(session.createQuery("SELECT 1", TxMode.SERIALIZABLE_RW))
);
// work with reader
}
For standalone JDBC calls, the current driver implementation uses Serializable Read/Write — including from Spring Boot, ORMs, and other JDBC wrappers. Snapshot for reads is enabled via setReadOnly(true) (see ImplicitTx). Auto-commit semantics and explicit transactions are described there as well.
dbapi
import ydb_dbapi as dbapi
with dbapi.connect(host="localhost", port="2136", database="/local") as connection:
connection.set_isolation_level(dbapi.IsolationLevel.SERIALIZABLE)
import ydb
def execute_query(pool: ydb.QuerySessionPool):
# Serializable Read-Write mode is used by default
def callee(session: ydb.QuerySession):
with session.transaction(ydb.QuerySerializableReadWrite()).execute(
"SELECT 1",
commit_tx=True,
) as result_sets:
pass # work with result_sets
pool.retry_operation_sync(callee)
auto settings = NYdb::NQuery::TTxSettings::SerializableRW();
auto result = session.ExecuteQuery(
"SELECT 1",
NYdb::NQuery::TTxControl::BeginTx(settings).CommitTx()
).GetValueSync();
ADO.NET
using Ydb.Sdk.Ado;
// Serializable Read-Write mode is used by default
await _ydbDataSource.ExecuteInTransactionAsync(async ydbConnection =>
{
var ydbCommand = ydbConnection.CreateCommand();
ydbCommand.CommandText = """
UPSERT INTO episodes (series_id, season_id, episode_id, title, air_date)
VALUES (2, 5, 13, "Test Episode", Date("2018-08-27"))
""";
await ydbCommand.ExecuteNonQueryAsync();
ydbCommand.CommandText = """
INSERT INTO episodes(series_id, season_id, episode_id, title, air_date)
VALUES
(2, 5, 21, "Test 21", Date("2018-08-27")),
(2, 5, 22, "Test 22", Date("2018-08-27"))
""";
await ydbCommand.ExecuteNonQueryAsync();
}
);
Entity Framework
var strategy = db.Database.CreateExecutionStrategy();
// Serializable Read-Write mode is used by default
strategy.ExecuteInTransaction(
db,
ctx =>
{
ctx.Users.AddRange(
new User { Name = "Alex", Email = "alex@example.com" },
new User { Name = "Kirill", Email = "kirill@example.com" }
);
ctx.SaveChanges();
var users = ctx.Users.OrderBy(u => u.Id).ToList();
Console.WriteLine("Users in database:");
foreach (var user in users)
Console.WriteLine($"- {user.Id}: {user.Name} ({user.Email})");
},
ctx => ctx.Users.Any(u => u.Email == "alex@example.com")
&& ctx.Users.Any(u => u.Email == "kirill@example.com")
);
linq2db
using LinqToDB;
using LinqToDB.Data;
// linq2db uses Serializable isolation by default
using var db = new DataConnection(
new DataOptions().UseConnectionString(
"YDB",
"Host=localhost;Port=2136;Database=/local;UseTls=false"
)
);
// Serializable Read-Write mode is used by default
await using var tr = await db.BeginTransactionAsync();
await db.InsertAsync(new Episode
{
SeriesId = 2, SeasonId = 5, EpisodeId = 13, Title = "Test Episode", AirDate = new DateTime(2018, 08, 27)
});
await db.InsertAsync(new Episode
{ SeriesId = 2, SeasonId = 5, EpisodeId = 21, Title = "Test 21", AirDate = new DateTime(2018, 08, 27) });
await db.InsertAsync(new Episode
{ SeriesId = 2, SeasonId = 5, EpisodeId = 22, Title = "Test 22", AirDate = new DateTime(2018, 08, 27) });
await tr.CommitAsync();
using Ydb.Sdk.Services.Query;
// Serializable Read-Write mode is used by default
var response = await queryClient.Exec("SELECT 1");
import { sql } from '@ydbjs/query';
// ...
// Serializable Read-Write mode is used by default
await sql.begin({ idempotent: true }, async (tx) => {
return await tx`SELECT 1`;
});
// Or explicitly specify transaction mode
await sql.begin({ isolation: 'serializableReadWrite', idempotent: true }, async (tx) => {
return await tx`SELECT 1`;
});
use ydb::{TransactionOptions};
let tx_options = TransactionOptions::default().with_mode(
ydb::Mode::SerializableReadWrite
);
let table_client = db.table_client().clone_with_transaction_options(tx_options);
let result = table_client.retry_transaction(|mut tx| async move {
let res = tx.query("SELECT 1".into()).await?;
return Ok(res)
}).await?;
<?php
use YdbPlatform\Ydb\Ydb;
$config = [
// YDB config
];
$ydb = new Ydb($config);
$result = $ydb->table()->retryTransaction(
function (Session $session) {
return $session->query('SELECT 1 AS value;');
},
true,
null,
['tx_mode' => 'serializable_read_write']
);
Online Read-Only
package main
import (
"context"
"fmt"
"os"
"github.com/ydb-platform/ydb-go-sdk/v3"
"github.com/ydb-platform/ydb-go-sdk/v3/query"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
db, err := ydb.Open(ctx,
os.Getenv("YDB_CONNECTION_STRING"),
ydb.WithAccessTokenCredentials(os.Getenv("YDB_TOKEN")),
)
if err != nil {
panic(err)
}
defer db.Close(ctx)
row, err := db.Query().QueryRow(ctx, "SELECT 1",
query.WithTxControl(
query.OnlineReadOnlyTxControl(query.WithInconsistentReads()),
),
)
if err != nil {
fmt.Printf("unexpected error: %v", err)
}
// work with row
_ = row
}
package main
import (
"context"
"database/sql"
"fmt"
"os"
"github.com/ydb-platform/ydb-go-sdk/v3"
"github.com/ydb-platform/ydb-go-sdk/v3/retry"
"github.com/ydb-platform/ydb-go-sdk/v3/table"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
nativeDriver, err := ydb.Open(ctx,
os.Getenv("YDB_CONNECTION_STRING"),
ydb.WithAccessTokenCredentials(os.Getenv("YDB_TOKEN")),
)
if err != nil {
panic(err)
}
defer nativeDriver.Close(ctx)
connector, err := ydb.Connector(nativeDriver)
if err != nil {
panic(err)
}
defer connector.Close()
db := sql.OpenDB(connector)
defer db.Close()
err = retry.Do(
ydb.WithTxControl(ctx, table.OnlineReadOnlyTxControl(table.WithInconsistentReads())),
db,
func(ctx context.Context, conn *sql.Conn) error {
row := conn.QueryRowContext(ctx, "SELECT 1")
var result int
return row.Scan(&result)
},
retry.WithIdempotent(true),
)
if err != nil {
fmt.Printf("unexpected error: %v", err)
}
}
import tech.ydb.query.QueryClient;
import tech.ydb.query.TxMode;
import tech.ydb.query.tools.QueryReader;
import tech.ydb.query.tools.SessionRetryContext;
// ...
try (QueryClient queryClient = QueryClient.newClient(transport).build()) {
SessionRetryContext retryCtx = SessionRetryContext.create(queryClient).build();
QueryReader reader = retryCtx.supplyResult(
session -> QueryReader.readFrom(session.createQuery("SELECT 1", TxMode.ONLINE_RO))
);
// work with reader
}
Online Read-Only from the Query API is not configured separately for standalone JDBC calls. For snapshot reads, call Connection.setReadOnly(true) (in Spring — @Transactional(readOnly = true)). For writes, see ImplicitTx.
dbapi
import ydb_dbapi as dbapi
with dbapi.connect(host="localhost", port="2136", database="/local") as connection:
connection.set_isolation_level(dbapi.IsolationLevel.ONLINE_READONLY)
import ydb
def execute_query(pool: ydb.QuerySessionPool):
def callee(session: ydb.QuerySession):
with session.transaction(ydb.QueryOnlineReadOnly()).execute(
"SELECT 1",
commit_tx=True,
) as result_sets:
pass # work with result_sets
pool.retry_operation_sync(callee)
auto settings = NYdb::NQuery::TTxSettings::OnlineRO();
auto result = session.ExecuteQuery(
"SELECT 1",
NYdb::NQuery::TTxControl::BeginTx(settings).CommitTx()
).GetValueSync();
ADO.NET
using Ydb.Sdk.Ado;
await using var connection = await dataSource.OpenConnectionAsync();
await using var transaction = await connection.BeginTransactionAsync(TransactionMode.OnlineRo);
await using var command = new YdbCommand(connection) { CommandText = "SELECT 1" };
await using var reader = await command.ExecuteReaderAsync();
await transaction.CommitAsync();
Entity Framework
Entity Framework does not support OnlineRo mode directly.
Use ydb-dotnet-sdk or ADO.NET for this isolation level.
linq2db
linq2db does not support OnlineRo mode directly.
Use ydb-dotnet-sdk or ADO.NET for this isolation level.
using Ydb.Sdk.Ado;
using Ydb.Sdk.Services.Query;
var response = await queryClient.ReadAllRows("SELECT 1", txMode: TransactionMode.OnlineRo);
import { sql } from '@ydbjs/query';
// ...
await sql.begin({ isolation: 'onlineReadOnly', idempotent: true }, async (tx) => {
return await tx`SELECT 1`;
});
let tx_options = TransactionOptions::default().with_mode(
ydb::Mode::OnlineReadonly,
).with_autocommit(true);
let table_client = db.table_client().clone_with_transaction_options(tx_options);
let result = table_client.retry_transaction(|mut tx| async move {
let res = tx.query("SELECT 1".into()).await?;
return Ok(res)
}).await?;
<?php
use YdbPlatform\Ydb\Ydb;
$config = [
// YDB config
];
$ydb = new Ydb($config);
$result = $ydb->table()->retrySession(function (Session $session) {
$query = $session->newQuery('SELECT 1 AS value;');
$query->beginTx('online_read_only');
return $query->execute();
}, true);
Stale Read-Only
package main
import (
"context"
"fmt"
"os"
"github.com/ydb-platform/ydb-go-sdk/v3"
"github.com/ydb-platform/ydb-go-sdk/v3/query"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
db, err := ydb.Open(ctx,
os.Getenv("YDB_CONNECTION_STRING"),
ydb.WithAccessTokenCredentials(os.Getenv("YDB_TOKEN")),
)
if err != nil {
panic(err)
}
defer db.Close(ctx)
row, err := db.Query().QueryRow(ctx, "SELECT 1",
query.WithTxControl(query.StaleReadOnlyTxControl()),
)
if err != nil {
fmt.Printf("unexpected error: %v", err)
}
// work with row
_ = row
}
package main
import (
"context"
"database/sql"
"fmt"
"os"
"github.com/ydb-platform/ydb-go-sdk/v3"
"github.com/ydb-platform/ydb-go-sdk/v3/retry"
"github.com/ydb-platform/ydb-go-sdk/v3/table"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
nativeDriver, err := ydb.Open(ctx,
os.Getenv("YDB_CONNECTION_STRING"),
ydb.WithAccessTokenCredentials(os.Getenv("YDB_TOKEN")),
)
if err != nil {
panic(err)
}
defer nativeDriver.Close(ctx)
connector, err := ydb.Connector(nativeDriver)
if err != nil {
panic(err)
}
defer connector.Close()
db := sql.OpenDB(connector)
defer db.Close()
err = retry.Do(
ydb.WithTxControl(ctx, table.StaleReadOnlyTxControl()),
db,
func(ctx context.Context, conn *sql.Conn) error {
row := conn.QueryRowContext(ctx, "SELECT 1")
var result int
return row.Scan(&result)
},
retry.WithIdempotent(true),
)
if err != nil {
fmt.Printf("unexpected error: %v", err)
}
}
import tech.ydb.query.QueryClient;
import tech.ydb.query.TxMode;
import tech.ydb.query.tools.QueryReader;
import tech.ydb.query.tools.SessionRetryContext;
// ...
try (QueryClient queryClient = QueryClient.newClient(transport).build()) {
SessionRetryContext retryCtx = SessionRetryContext.create(queryClient).build();
QueryReader reader = retryCtx.supplyResult(
session -> QueryReader.readFrom(session.createQuery("SELECT 1", TxMode.STALE_RO))
);
// work with reader
}
Stale Read-Only from the Query API is not configured separately for standalone JDBC calls. For snapshot reads, call Connection.setReadOnly(true) (in Spring — @Transactional(readOnly = true)). For writes, see ImplicitTx.
dbapi
import ydb_dbapi as dbapi
with dbapi.connect(host="localhost", port="2136", database="/local") as connection:
connection.set_isolation_level(dbapi.IsolationLevel.STALE_READONLY)
import ydb
def execute_query(pool: ydb.QuerySessionPool):
def callee(session: ydb.QuerySession):
with session.transaction(ydb.QueryStaleReadOnly()).execute(
"SELECT 1",
commit_tx=True,
) as result_sets:
pass # work with result_sets
pool.retry_operation_sync(callee)
auto settings = NYdb::NQuery::TTxSettings::StaleRO();
auto result = session.ExecuteQuery(
"SELECT 1",
NYdb::NQuery::TTxControl::BeginTx(settings).CommitTx()
).GetValueSync();
ADO.NET
using Ydb.Sdk.Ado;
using Ydb.Sdk.Services.Query;
await using var connection = await dataSource.OpenConnectionAsync();
await using var transaction = await connection.BeginTransactionAsync(TransactionMode.StaleRo);
await using var command = new YdbCommand(connection) { CommandText = "SELECT 1", Transaction = transaction };
await using var reader = await command.ExecuteReaderAsync();
await transaction.CommitAsync();
Entity Framework
Entity Framework does not support StaleRo mode directly.
Use ydb-dotnet-sdk or ADO.NET for this isolation level.
linq2db
linq2db does not support StaleRo mode directly.
Use ydb-dotnet-sdk or ADO.NET for this isolation level.
using Ydb.Sdk.Ado;
using Ydb.Sdk.Services.Query;
var response = await queryClient.ReadAllRows("SELECT 1", txMode: TransactionMode.StaleRo);
import { sql } from '@ydbjs/query';
// ...
await sql.begin({ isolation: 'staleReadOnly', idempotent: true }, async (tx) => {
return await tx`SELECT 1`;
});
Stale Read-Only mode is not supported in the Rust SDK.
<?php
use YdbPlatform\Ydb\Ydb;
$config = [
// YDB config
];
$ydb = new Ydb($config);
$result = $ydb->table()->retrySession(function (Session $session) {
$query = $session->newQuery('SELECT 1 AS value;');
$query->beginTx('stale_read_only');
return $query->execute();
}, true);
Snapshot Read-Only
package main
import (
"context"
"fmt"
"os"
"github.com/ydb-platform/ydb-go-sdk/v3"
"github.com/ydb-platform/ydb-go-sdk/v3/query"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
db, err := ydb.Open(ctx,
os.Getenv("YDB_CONNECTION_STRING"),
ydb.WithAccessTokenCredentials(os.Getenv("YDB_TOKEN")),
)
if err != nil {
panic(err)
}
defer db.Close(ctx)
row, err := db.Query().QueryRow(ctx, "SELECT 1",
query.WithTxControl(query.SnapshotReadOnlyTxControl()),
)
if err != nil {
fmt.Printf("unexpected error: %v", err)
}
// work with row
_ = row
}
package main
import (
"context"
"database/sql"
"fmt"
"os"
"github.com/ydb-platform/ydb-go-sdk/v3"
"github.com/ydb-platform/ydb-go-sdk/v3/retry"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
nativeDriver, err := ydb.Open(ctx,
os.Getenv("YDB_CONNECTION_STRING"),
ydb.WithAccessTokenCredentials(os.Getenv("YDB_TOKEN")),
)
if err != nil {
panic(err)
}
defer nativeDriver.Close(ctx)
connector, err := ydb.Connector(nativeDriver)
if err != nil {
panic(err)
}
defer connector.Close()
db := sql.OpenDB(connector)
defer db.Close()
// Snapshot Read-Only — consistent reads at a point in time
err = retry.DoTx(ctx, db, func(ctx context.Context, tx *sql.Tx) error {
row := tx.QueryRowContext(ctx, "SELECT 1")
var result int
return row.Scan(&result)
}, retry.WithIdempotent(true), retry.WithTxOptions(&sql.TxOptions{
Isolation: sql.LevelSnapshot,
ReadOnly: true,
}))
if err != nil {
fmt.Printf("unexpected error: %v", err)
}
}
import tech.ydb.query.QueryClient;
import tech.ydb.query.TxMode;
import tech.ydb.query.tools.QueryReader;
import tech.ydb.query.tools.SessionRetryContext;
// ...
try (QueryClient queryClient = QueryClient.newClient(transport).build()) {
SessionRetryContext retryCtx = SessionRetryContext.create(queryClient).build();
QueryReader reader = retryCtx.supplyResult(
session -> QueryReader.readFrom(session.createQuery("SELECT 1", TxMode.SNAPSHOT_RO))
);
// work with reader
}
For standalone read-only JDBC calls, snapshot mode is enabled when Connection.setReadOnly(true) is set on the connection (in Spring, @Transactional(readOnly = true) propagates this to the driver when the transaction starts). See ImplicitTx for details.
dbapi
import ydb_dbapi as dbapi
with dbapi.connect(host="localhost", port="2136", database="/local") as connection:
connection.set_isolation_level(dbapi.IsolationLevel.SNAPSHOT_READONLY)
import ydb
def execute_query(pool: ydb.QuerySessionPool):
def callee(session: ydb.QuerySession):
with session.transaction(ydb.QuerySnapshotReadOnly()).execute(
"SELECT 1",
commit_tx=True,
) as result_sets:
pass # work with result_sets
pool.retry_operation_sync(callee)
auto settings = NYdb::NQuery::TTxSettings::SnapshotRO();
auto result = session.ExecuteQuery(
"SELECT 1",
NYdb::NQuery::TTxControl::BeginTx(settings).CommitTx()
).GetValueSync();
ADO.NET
using Ydb.Sdk.Ado;
await using var connection = await dataSource.OpenConnectionAsync();
await using var transaction = await connection.BeginTransactionAsync(TransactionMode.SnapshotRo);
await using var command = new YdbCommand(connection) { CommandText = "SELECT 1" };
await using var reader = await command.ExecuteReaderAsync();
await transaction.CommitAsync();
Entity Framework
Entity Framework does not support Snapshot Read-Only mode directly.
Use ydb-dotnet-sdk or ADO.NET for this isolation level.
linq2db
linq2db does not support Snapshot Read-Only mode directly.
Use ydb-dotnet-sdk or ADO.NET for this isolation level.
using Ydb.Sdk.Ado;
using Ydb.Sdk.Services.Query;
var response = await queryClient.ReadAllRows("SELECT 1", TransactionMode.SnapshotRo);
import { sql } from '@ydbjs/query';
// ...
await sql.begin({ isolation: 'snapshotReadOnly', idempotent: true }, async (tx) => {
return await tx`SELECT 1`;
});
Snapshot Read-Only mode is not supported in the Rust SDK.
<?php
use YdbPlatform\Ydb\Ydb;
$config = [
// YDB config
];
$ydb = new Ydb($config);
$result = $ydb->table()->retryTransaction(
function (Session $session) {
return $session->query('SELECT 1 AS value;');
},
true,
null,
['tx_mode' => 'snapshot_read_only']
);
Snapshot Read-Write
package main
import (
"context"
"fmt"
"os"
"github.com/ydb-platform/ydb-go-sdk/v3"
"github.com/ydb-platform/ydb-go-sdk/v3/query"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
db, err := ydb.Open(ctx,
os.Getenv("YDB_CONNECTION_STRING"),
ydb.WithAccessTokenCredentials(os.Getenv("YDB_TOKEN")),
)
if err != nil {
panic(err)
}
defer db.Close(ctx)
row, err := db.Query().QueryRow(ctx, "SELECT 1",
query.WithTxControl(query.SnapshotReadWriteTxControl(query.CommitTx())),
)
if err != nil {
fmt.Printf("unexpected error: %v", err)
}
// work with row
_ = row
}
package main
import (
"context"
"database/sql"
"fmt"
"os"
"github.com/ydb-platform/ydb-go-sdk/v3"
"github.com/ydb-platform/ydb-go-sdk/v3/retry"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
nativeDriver, err := ydb.Open(ctx,
os.Getenv("YDB_CONNECTION_STRING"),
ydb.WithAccessTokenCredentials(os.Getenv("YDB_TOKEN")),
)
if err != nil {
panic(err)
}
defer nativeDriver.Close(ctx)
connector, err := ydb.Connector(nativeDriver)
if err != nil {
panic(err)
}
defer connector.Close()
db := sql.OpenDB(connector)
defer db.Close()
// Snapshot Read-Write — consistent reads at a point in time with writes allowed
err = retry.DoTx(ctx, db, func(ctx context.Context, tx *sql.Tx) error {
row := tx.QueryRowContext(ctx, "SELECT 1")
var result int
return row.Scan(&result)
}, retry.WithIdempotent(true), retry.WithTxOptions(&sql.TxOptions{
Isolation: sql.LevelSnapshot,
ReadOnly: false,
}))
if err != nil {
fmt.Printf("unexpected error: %v", err)
}
}
import tech.ydb.query.QueryClient;
import tech.ydb.query.TxMode;
import tech.ydb.query.tools.QueryReader;
import tech.ydb.query.tools.SessionRetryContext;
// ...
try (QueryClient queryClient = QueryClient.newClient(transport).build()) {
SessionRetryContext retryCtx = SessionRetryContext.create(queryClient).build();
QueryReader reader = retryCtx.supplyResult(
session -> QueryReader.readFrom(session.createQuery("SELECT 1", TxMode.SNAPSHOT_RW))
);
// work with reader
}
Snapshot Read-Write from the Query API is not set separately for standalone JDBC calls; for writes, the current driver implementation uses Serializable Read/Write (see ImplicitTx and Serializable).
dbapi
import ydb_dbapi as dbapi
with dbapi.connect(host="localhost", port="2136", database="/local") as connection:
connection.set_isolation_level(dbapi.IsolationLevel.SNAPSHOT_READWRITE)
import ydb
def execute_query(pool: ydb.QuerySessionPool):
def callee(session: ydb.QuerySession):
with session.transaction(ydb.QuerySnapshotReadWrite()).execute(
"SELECT 1",
commit_tx=True,
) as result_sets:
pass # work with result_sets
pool.retry_operation_sync(callee)
auto settings = NYdb::NQuery::TTxSettings::SnapshotRW();
auto result = session.ExecuteQuery(
"SELECT 1",
NYdb::NQuery::TTxControl::BeginTx(settings).CommitTx()
).GetValueSync();
ADO.NET
await _ydbDataSource.ExecuteInTransactionAsync(async ydbConnection =>
{
var ydbCommand = ydbConnection.CreateCommand();
ydbCommand.CommandText = """
UPSERT INTO episodes (series_id, season_id, episode_id, title, air_date)
VALUES (2, 5, 13, "Test Episode", Date("2018-08-27"))
""";
await ydbCommand.ExecuteNonQueryAsync();
ydbCommand.CommandText = """
INSERT INTO episodes(series_id, season_id, episode_id, title, air_date)
VALUES
(2, 5, 21, "Test 21", Date("2018-08-27")),
(2, 5, 22, "Test 22", Date("2018-08-27"))
""";
await ydbCommand.ExecuteNonQueryAsync();
}, TransactionMode.SnapshotRw
);
EF
var strategy = db.Database.CreateExecutionStrategy();
strategy.Execute(() =>
{
using var ctx = new AppDbContext(options);
using var tr = ctx.Database.BeginTransaction(IsolationLevel.Snapshot);
ctx.Users.AddRange(
new User { Name = "Alex", Email = "alex@example.com" },
new User { Name = "Kirill", Email = "kirill@example.com" }
);
ctx.SaveChanges();
var users = ctx.Users.OrderBy(u => u.Id).ToList();
Console.WriteLine("Users in database:");
foreach (var user in users)
Console.WriteLine($"- {user.Id}: {user.Name} ({user.Email})");
}
);
linq2db
await using var db = new MyYdb(BuildOptions());
await using var tr = await db.BeginTransactionAsync(IsolationLevel.Snapshot);
await db.InsertAsync(new Episode
{
SeriesId = 2, SeasonId = 5, EpisodeId = 13, Title = "Test Episode", AirDate = new DateTime(2018, 08, 27)
});
await db.InsertAsync(new Episode
{ SeriesId = 2, SeasonId = 5, EpisodeId = 21, Title = "Test 21", AirDate = new DateTime(2018, 08, 27) });
await db.InsertAsync(new Episode
{ SeriesId = 2, SeasonId = 5, EpisodeId = 22, Title = "Test 22", AirDate = new DateTime(2018, 08, 27) });
await tr.CommitAsync();
using Ydb.Sdk.Ado;
using Ydb.Sdk.Services.Query;
var response = await queryClient.ReadAllRows("SELECT 1", TransactionMode.SnapshotRw);
import { sql } from '@ydbjs/query';
// ...
await sql.begin({ isolation: 'snapshotReadWrite', idempotent: true }, async (tx) => {
return await tx`SELECT 1`;
});
Snapshot Read-Write mode is not supported in the Rust SDK.
Snapshot Read-Write mode is not supported in the PHP SDK.