Databases 11 min read

Why Serializability Matters: Single‑Threaded Transactions and Stored Procedures

The article explains how RC and snapshot isolation fail to prevent write skew and phantom reads, advocates serializable isolation, and examines three implementation techniques—strict serial execution, two‑phase locking, and optimistic concurrency—detailing their trade‑offs, stored‑procedure usage, partitioning, and practical constraints for achieving true serializable isolation.

JavaEdge
JavaEdge
JavaEdge
Why Serializability Matters: Single‑Threaded Transactions and Stored Procedures

Isolation Levels and Serializable Isolation

Read‑committed (RC) and snapshot isolation prevent many concurrency anomalies but cannot eliminate write‑skew or phantom reads. Isolation levels are often confusing and differ across DBMS implementations, making it hard for application code to guarantee safety, especially in large systems. Static analysis tools only partially help, and testing concurrent behavior is timing‑dependent.

Isolation semantics vary (e.g., the meaning of RR differs between vendors).

Application‑level code cannot reliably ensure correctness under a specific isolation level without deep knowledge of the workload.

There is a lack of robust tools for detecting race conditions; testing is fragile.

Since the 1970s weaker isolation levels have been used, but research recommends the strongest level—serializable isolation—because it guarantees that the result of concurrently executed transactions is identical to some serial order, eliminating all concurrency bugs.

Techniques for Providing Serializable Isolation

Strict serial execution of transactions (single‑threaded loop).

Two‑phase locking (2PL), the classic pessimistic approach.

Optimistic concurrency control, e.g., serializable snapshot isolation.

Strict Serial Execution

The simplest way to achieve serializable isolation is to run every transaction on a single thread in order, thereby avoiding conflict detection entirely. This became practical around 2007 when RAM became cheap enough to hold the entire active dataset in memory and OLTP transactions were short enough to be processed without disk I/O.

Systems that adopt this model include VoltDB , H‑Store , Redis (when used as a single‑threaded server) and Datomic . Single‑threaded execution can outperform traditional concurrent systems by eliminating lock overhead, but throughput is limited to a single CPU core.

Encapsulating Transactions in Stored Procedures

To avoid the network round‑trips of interactive, multi‑statement transactions, single‑threaded databases require the entire transaction logic to be submitted ahead of time as a stored procedure. Early databases wrapped whole user workflows (e.g., flight booking) in a single transaction, but modern OLTP applications keep transactions short and submit them within a single HTTP request.

Each vendor historically provided its own proprietary language (PL/SQL, T‑SQL, PL/pgSQL), which lacks modern libraries.

Code running inside the DB is harder to debug, version‑control, test, and monitor.

Poorly written procedures can consume excessive memory or CPU, affecting all tenants.

Modern systems mitigate these issues by allowing stored procedures to be written in general‑purpose languages: VoltDB supports Java or Groovy, Datomic supports Java or Clojure, and Redis supports Lua. When procedures are deterministic, they can also be used for replication—VoltDB runs the same deterministic procedure on each node.

Partitioning to Scale Single‑Threaded Execution

Because a single thread limits throughput to one core, data can be partitioned so that each partition runs its own transaction thread, achieving linear scaling with the number of CPU cores. A transaction that touches only one partition can be processed by the dedicated thread for that partition.

Cross‑partition transactions require coordination across all involved partitions, incurring significant overhead. VoltDB reports roughly 1 000 cross‑partition writes per second, orders of magnitude lower than single‑partition rates. Whether a workload can be partitioned depends on the data model; simple key‑value data partitions easily, while secondary indexes often force cross‑partition coordination.

Constraints for Serial Execution to Provide Serializable Isolation

Transactions must be short and efficient; a single slow transaction degrades the performance of all others.

The entire active dataset must fit in memory; occasional disk‑resident data should be avoided or handled via anti‑caching.

Write throughput must be low enough for a single core, or the workload must be partitionable so that each transaction accesses only one partition.

Cross‑partition transactions are allowed only if they constitute a small fraction of the workload.

Diagram of interactive transaction vs stored procedure
Diagram of interactive transaction vs stored procedure
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Single‑threadeddatabasesserializabletransaction isolationPartitioningStored Procedures
JavaEdge
Written by

JavaEdge

First‑line development experience at multiple leading tech firms; now a software architect at a Shanghai state‑owned enterprise and founder of Programming Yanxuan. Nearly 300k followers online; expertise in distributed system design, AIGC application development, and quantitative finance investing.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.