6 KiB
| title | sort | section-id | keywords | description | language |
|---|---|---|---|---|---|
| Transactions | 140 | query-language | transactions, ACID, isolation levels, MVCC, BEGIN, COMMIT, ROLLBACK | ACID transactions in NeuralDB — isolation levels, MVCC, savepoints, and advisory locks | en |
Transactions
NeuralDB provides full ACID transactions with MVCC (Multi-Version Concurrency Control). Unlike most vector databases, NeuralDB guarantees atomicity across both relational and vector data within a single transaction.
ACID Guarantees
| Property | Guarantee |
|---|---|
| Atomicity | All operations in a transaction succeed or all are rolled back — including vector index updates |
| Consistency | Constraints (foreign keys, unique indexes, not null) are enforced at commit time |
| Isolation | Concurrent transactions do not see each other's uncommitted changes |
| Durability | Committed transactions survive crashes via the WAL |
Basic Transaction Syntax
BEGIN;
-- Your operations here
INSERT INTO documents (content, embedding) VALUES ($1, $2);
UPDATE document_stats SET total_count = total_count + 1;
INSERT INTO audit_log (action, data) VALUES ('insert', $3);
COMMIT;
On error, roll back:
BEGIN;
INSERT INTO documents (content, embedding) VALUES ($1, $2);
-- Something went wrong
ROLLBACK;
Isolation Levels
NeuralDB supports four isolation levels. Set them with SET TRANSACTION ISOLATION LEVEL:
BEGIN;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- ... your queries ...
COMMIT;
Read Committed (Default)
Each statement sees only rows committed before that statement began. Two successive reads within the same transaction may see different data if another transaction commits between them.
BEGIN;
-- Sees all rows committed before this SELECT
SELECT COUNT(*) FROM documents; -- Returns 1000
-- Another transaction inserts and commits a row here
-- Sees the new row (non-repeatable read)
SELECT COUNT(*) FROM documents; -- Returns 1001
COMMIT;
Repeatable Read
A transaction sees only rows committed before the transaction began. Reads are stable throughout the transaction.
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT COUNT(*) FROM documents; -- Returns 1000
-- Another transaction inserts and commits
SELECT COUNT(*) FROM documents; -- Still 1000 — repeatable read
COMMIT;
Serializable
The strictest level. Transactions execute as if they ran serially one after another. NeuralDB uses Serializable Snapshot Isolation (SSI) — it allows concurrent execution but detects and aborts transactions that would produce a non-serializable outcome.
BEGIN ISOLATION LEVEL SERIALIZABLE;
-- ... complex read-modify-write patterns ...
COMMIT;
-- May raise: ERROR: could not serialize access — retry the transaction
Read Uncommitted
NeuralDB maps this to Read Committed (it does not implement dirty reads).
Retry Logic
Serializable transactions can fail with serialization errors. Always retry:
from psycopg2 import errors
MAX_RETRIES = 5
for attempt in range(MAX_RETRIES):
try:
with conn.cursor() as cur:
cur.execute("BEGIN ISOLATION LEVEL SERIALIZABLE")
# ... your operations ...
cur.execute("COMMIT")
break
except errors.SerializationFailure:
conn.rollback()
if attempt == MAX_RETRIES - 1:
raise
time.sleep(0.1 * (2 ** attempt)) # exponential backoff
Savepoints
Savepoints allow partial rollbacks within a transaction:
BEGIN;
INSERT INTO documents (content, embedding) VALUES ($1, $2);
SAVEPOINT after_insert;
-- Risky operation
UPDATE document_stats SET count = count + 1 WHERE id = $3;
-- Oh no, something went wrong — roll back to the savepoint
ROLLBACK TO SAVEPOINT after_insert;
-- The INSERT is still pending — we can try a different approach
UPDATE document_stats SET count = count + 1 WHERE id = $4;
COMMIT;
Vector Transactions
Vector index updates are transactional in NeuralDB. An HNSW index entry is added atomically with the row:
BEGIN;
-- Both the row and the vector index entry are inserted atomically
INSERT INTO documents (id, content, embedding) VALUES ($1, $2, $3);
-- If we ROLLBACK, neither the row nor the index entry will exist
ROLLBACK;
-- After rollback, a similarity search will NOT find $1
SELECT id FROM documents ORDER BY embedding <=> $3 LIMIT 1;
-- $1 is not returned
Long-Running Transactions
Avoid long-running transactions — they:
- Hold row-level locks, blocking other writes
- Prevent VACUUM from reclaiming dead rows (bloat)
- Increase the risk of deadlocks
Set a statement timeout to kill runaway queries:
SET statement_timeout = '30s';
Set a transaction timeout:
SET idle_in_transaction_session_timeout = '5min';
Deadlock Detection
NeuralDB automatically detects deadlocks and aborts one of the transactions:
ERROR: deadlock detected
DETAIL: Process 12345 waits for ShareLock on transaction 67890; blocked by process 99999.
Hint: See server log for query details.
Minimise deadlock risk by always acquiring locks in the same order across all transactions.
Advisory Locks
For application-level locking (e.g., ensuring only one worker processes a job):
-- Acquire a session-level advisory lock (blocks until acquired)
SELECT pg_advisory_lock(42);
-- Try to acquire (returns false if already held)
SELECT pg_try_advisory_lock(42); -- returns boolean
-- Release
SELECT pg_advisory_unlock(42);
-- Transaction-level (auto-released at commit/rollback)
SELECT pg_advisory_xact_lock(42);
Two-Phase Commit (2PC)
For distributed transactions spanning multiple systems:
-- Phase 1: Prepare
BEGIN;
-- ... operations ...
PREPARE TRANSACTION 'my-distributed-txn-id';
-- Phase 2: Commit or rollback
COMMIT PREPARED 'my-distributed-txn-id';
-- or
ROLLBACK PREPARED 'my-distributed-txn-id';
Check pending prepared transactions:
SELECT gid, prepared, owner, database
FROM pg_prepared_xacts;