Skip to main content
Supported databases: MySQL, PostgreSQL, Oracle, SQL Server, TiDB, Redshift
Bytebase provides transaction management to ensure safe database changes. By default, all SQL statements are wrapped in a transaction, allowing automatic rollback if errors occur during execution.

Enable/Disable Transactions

When creating a plan, you can control whether SQL statements are executed within a transaction:
  • Enabled (default): All statements run within a transaction boundary for safety
  • Disabled: Statements execute directly without transaction wrapping
Transaction mode toggle

Statements that cannot run in a transaction block

Some PostgreSQL statements cannot run inside an explicit transaction block. When such a statement runs after BEGIN — or when a migration tool wraps it in a transaction — PostgreSQL rejects it with an error like:
ERROR: CREATE INDEX CONCURRENTLY cannot run inside a transaction block
For example, this fails because the statement is wrapped in an explicit transaction block:
BEGIN;
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);
COMMIT;
While the same statement runs fine on its own under autocommit (no BEGIN):
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);
You do not need to run these statements manually outside Bytebase. Bytebase automatically detects the most common ones and runs them outside the transaction — even in the default, transaction-enabled mode:
  • CREATE INDEX CONCURRENTLY
  • DROP INDEX CONCURRENTLY
  • VACUUM
  • DROP DATABASE
Include them in your change like any other statement. Bytebase executes them after the surrounding transaction commits, so they never hit the error above — the plan UI notes this as “Non-transactional statements will be executed last.”
For other statements that cannot run in a transaction block but are not auto-detected — such as REINDEX ... CONCURRENTLY, CREATE DATABASE, CREATE TABLESPACE, ALTER SYSTEM, and some ALTER TYPE ... ADD VALUE cases — disable transaction mode so Bytebase runs the statements directly:
-- txn-mode = off

REINDEX INDEX CONCURRENTLY idx_users_email;
-- txn-mode = off disables transaction wrapping for the entire script — if a later statement fails, earlier ones stay committed. Put a non-transactional statement in its own migration so the rest of your changes keep transactional, all-or-nothing safety.

MySQL-Specific Settings

For MySQL databases, Bytebase offers additional control over transaction isolation levels to manage concurrent access and data consistency: MySQL transaction isolation levels

Available Isolation Levels

  • READ UNCOMMITTED: Lowest isolation, allows dirty reads
  • READ COMMITTED: Prevents dirty reads, allows non-repeatable reads
  • REPEATABLE READ: Default MySQL isolation, prevents dirty and non-repeatable reads
  • SERIALIZABLE: Highest isolation, prevents all phenomena but may impact performance

GitOps

In the GitOps workflow there is no UI toggle, so transaction mode and isolation level are controlled via comment directives at the top of the migration file:
-- txn-mode = off
-- txn-isolation = READ COMMITTED

ALTER TABLE users ADD COLUMN email VARCHAR(255);
Supported directives:
  • -- txn-mode = on|off — wrap the script in a transaction, or run statements directly without transaction wrapping
  • -- txn-isolation = READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE — MySQL only
Rules:
  • Directives must appear at the top of the file, before any non-comment SQL
  • Empty lines between directives are allowed; scanning stops at the first non-comment line
  • The order of the two directives does not matter
  • Whitespace around = is optional — -- txn-mode = off and -- txn-mode=off are both accepted; the spaced form shown above is the canonical style

Best Practices

  • Keep transactions enabled for DDL and DML operations that modify data
  • Consider disabling transactions only for:
  • Choose appropriate isolation levels based on your concurrency requirements and performance needs