Database

Transaction Isolation in Oracle Database

April 26, 20235 min read
Black server racks on a room.
Photo by Manuel Geissinger

Introduction

Managing transactions is crucial for ensuring data consistency and integrity in database systems. Transactions consist of operations such as Create, Read, Update, and Delete (CRUD) that are executed together as a single unit of work.

ACID transactions can provide strong consistency guarantees, but can also be expensive and have a high overhead. Isolation levels in databases are used to provide a balance between consistency and performance in multi-user database environments, depending on the specific needs of the application.

In the next sections we will explore behaviors called transaction phenomena, and their impact on data integrity. We will also discuss transaction isolation levels in Oracle database and show some real world examples of their consequences.

Transactions Phenomena

Transaction phenomena refer to behaviors that can emerge during the concurrent execution of transactions in relational databases, potentially affecting data integrity. These phenomena are typically the result of using weaker isolation levels than those provided by standard ACID transactions. The SQL-92 standard identifies three1 such phenomena, which include:

Dirty Read

A transaction reads uncommitted data that was written by another transaction.

Dirty read sequence diagram

Non-repeatable Read

A transaction reads the same data twice, and the value changes between the reads due to another transaction.

Non-repeatable read sequence diagram

Phantom Read

A transaction reads a set of rows, and a new row appears in the set due to another transaction.

Phantom read sequence diagram

Transactional Isolation levels

In an ideal world, if transactions could execute one after another without any overlap, there would be no need for isolation levels. However, this isn't feasible in real-world scenarios due to performance constraints. The ANSI SQL standard defines four isolation levels:

  • Read Uncommitted
  • Read Committed
  • Repeatable Read
  • Serializable

Each level provides different guarantees regarding data consistency and isolation from other transactions

Read phenomena →
Isolation level ↓
Dirty read Non-repeatable Read Phantom Read
Read uncommitted yes yes yes
Read Committed no yes yes
Repeatable Read no no yes
Serializable no no no

Here are default isolation levels for some transactional databases:

  • Oracle: Read committed
  • SQL Server: Read committed
  • PostgreSQL: Read committed
  • MySQL: Repeatable Read

It is worth noting that Oracle Database supports only two isolation levels: Read Committed and Serializable, as it considers the two other levels to provide weaker consistency guarantees than those supported.

Non-intuitive examples

Examples provided in section "Transactions Phenomena" were pretty simple. In this section I will show you two sample non-intuitive examples when those phenomena may occur. Please keep in mind that those examples are a result of bad system design and should not be used in real world application! They are intended as a warning against such usage. The examples were run on Oracle database on default isolation level.

Example 1: update with nested select statement

Let's say that we want to update a user's status to 'inactive' if they haven't made any orders since a specific date:

UPDATE user 
SET status = 'inactive' 
WHERE id NOT IN (SELECT user_id 
                 FROM order 
                 WHERE order_date > :since_date);

The issue with this query is the potential for Phantom Read in the orders table. The orders table is not locked during this query in any way. This query is equivalent to the following:

SELECT user_id 
FROM order 
WHERE order_date > :since_date;

UPDATE user 
SET status = 'inactive' 
WHERE id NOT IN (:user_ids_from_previous_query);

The only difference is that, in the first case, a race condition is less likely because the entire query is executed in a single database call.

Suppose a user places an order precisely when the above query is run. We might end up with an inactive user who placed an order a while ago. In the diagram below, we'll use two separate queries (select + update), but it works exactly the same when using update with a nested select.

Nested update sequence diagram

Example 2: insert from result of select

In this example, we have a table called "work_lock" that serves as a critical section for certain processes. If a process wants to execute some work, it first needs to insert the work id into the "work_lock" table to prevent the work from being executed multiple times:

INSERT INTO work_lock (work_id, locked_by) 
SELECT id, 'my_worker' 
FROM work 
WHERE status = 'NEW' 
  AND id not in (select work_id from work_lock)

Similar to the previous example, the SELECT statement is prone to Phantom Read. There is still a possibility that two processes might attempt the same work. Additionally, there is also another issue: the query is likely to cause a deadlock. However, we will address this topic in a separate blog post.

How can we prevent that

To prevent transaction phenomena, you can use pessimistic locking, optimistic locking, or change the isolation level. However, adjusting the isolation level isn't suggested because it can affect speed and increase the risk of deadlocks. Changing the isolation level can prevent transaction phenomena, but it's generally not recommended as a first approach. Higher isolation levels provide better consistency guarantees but may result in decreased performance, increased resource usage, and a higher likelihood of deadlocks. The choice between pessimistic or optimistic locking depends on the specific use case, which will be covered in a separate blog post.


DatabaseOracleTransactionPhenomenaACID