> Markdown version of [Transactions](https://vaadin.com/docs/next/building-apps/forms-data/consistency/transactions). Section index: [llms.txt](https://vaadin.com/docs/next/building-apps/llms.txt)

# Transactions

Database transactions ensure Atomicity, Consistency, Isolation, and Durability (ACID) of database write operations.

Atomicity means that the transaction is treated as a single unit. Either all updates succeed, or none of them. Even if only a single update fails, the transaction is rolled back, and all other updates are undone.

Consistency means that the database is in a valid state after each committed transaction. All integrity constraints — like unique keys, foreign keys, and check constraints — are met after each transaction.

Isolation means that transactions are isolated from each other so that they don’t interfere. The end result should be the same regardless of whether two transactions execute in parallel — or one after the other. This is also useful for queries that only read data from the database, without making any changes to it.

Durability means that once a transaction is committed, the changes are permanent. They should even survive a system crash. In practice, this means writing the changes to a durable storage medium, such as a hard drive.

> **Note:** Although transactions are not specific to relational databases, they are discussed from the point of view of a relational database on this page.

## <a id="transaction-isolation"></a>Transaction Isolation

Databases achieve isolation in two main ways: by locking data, and by keeping multiple versions of it. Many databases combine the two.

With locking, a transaction has to get a lock on the data before it can read or modify it. The database uses two kinds of locks for this:

A *read lock*, or *shared* lock, is used when a transaction reads data. Several transactions can hold a read lock on the same data at the same time, but while they do, no transaction can get a write lock on it. The data can be read concurrently, but not modified.

A *write lock*, or *exclusive* lock, is used when a transaction modifies data. Only one transaction at a time can hold it, and while it does, no other transaction can get either a read lock or a write lock on the same data. Other transactions can neither read nor modify the data until the lock is released.

Depending on the implementation and configuration of the database, and the needs of a transaction, locks can be applied at different levels of granularity. For example, a database may be able to lock individual cells, rows, pages, tables, or even the entire database.

With *multi-version concurrency control* (MVCC), the database keeps several versions of the same data. When a transaction reads data, it gets a *snapshot*: the data as it was committed at a certain point in time. Reading a snapshot doesn’t require a read lock, unless the transaction explicitly locks the data it reads, as in [pessimistic locking](https://vaadin.com/docs/next/building-apps/forms-data/consistency/pessimistic-locking.md). Because of this, a write lock doesn’t block it. The transaction holding the write lock is working on a newer version of the data, while the reading transaction sees the committed version. Readers don’t have to wait for writers, and writers don’t have to wait for readers. Transactions that modify the same data still need write locks, so they have to wait for each other. PostgreSQL, Oracle, MySQL, and MariaDB all use MVCC. Microsoft SQL Server uses locks by default, but can be configured to use row versioning instead.

Isolation has an impact on performance. A database that uses locks has to hold more locks, for longer, the stronger the isolation. A database that uses MVCC may have to make transactions wait, or roll them back, when they conflict with each other. Because of this, there are different levels of isolation. They allow you to relax the isolation requirements in favor of improved performance. To understand what these levels mean, you first need to understand some phenomena that can happen when you’re reading and writing data at the same time.

- Dirty Reads

  A dirty read happens when a transaction reads changes made by another, before that second transaction has been committed. The problem is that should the second transaction be rolled back, the first one has read data that no longer exists.

- Non-Repeatable Reads

  A non-repeatable read happens when a transaction reads the same data twice, but gets different results because a second transaction has updated or deleted the data, and committed, between the reads.

- Phantom Reads

  A phantom read is a special kind of non-repeatable read. It happens when a transaction runs the same query twice, and the second result includes data that a second transaction has inserted, and committed, between the queries.

The ANSI/ISO SQL standard defines four isolation levels: read uncommitted, read committed, repeatable reads, and serializable. These are described in detail here:

- Read Uncommitted

  This is the lowest isolation level, where transactions can see changes that other transactions have not yet committed. It allows dirty reads, non-repeatable reads, and phantom reads. Avoid this level, as your application could act on data that is later rolled back. Not all databases support it. For example, PostgreSQL treats it as read committed, and Oracle doesn’t support it at all.

- Read Committed

  This is the second-lowest isolation level. It prevents dirty reads, but allows non-repeatable reads and phantom reads.

- Repeatable Reads

  This is the second-highest isolation level. It prevents dirty reads and non-repeatable reads, but allows phantom reads.

- Serializable

  This is the highest, but also the most expensive isolation level. Transactions on this level behave as if they were executed sequentially. It prevents dirty reads, non-repeatable reads, and phantom reads.

A database that uses locks implements the higher isolation levels by holding read locks for longer, and by locking more than the rows a transaction has read, such as ranges of rows or entire tables. A database that uses MVCC implements them by choosing which snapshot a transaction sees. On the read committed level, every statement sees the data committed before that statement started. On the higher levels, the entire transaction sees the data committed before its first statement.

Database implementations may also define their own isolation levels, and choose to implement the standard isolation levels in different ways. For instance, a specific database may prevent certain bad behavior on a lower isolation level, even though it would be possible in theory.

Furthermore, their default isolation levels may be different. The database systems PostgreSQL, Microsoft SQL Server, H2, and Oracle use *read committed* as the default isolation level, whereas MariaDB and MySQL use *repeatable reads*.

On the higher isolation levels, some databases roll back a transaction instead of making it wait, if it conflicts with another one. For example, PostgreSQL rolls back a repeatable read transaction that tries to update a row that another transaction has updated and committed after the first transaction started. Your application then has to execute the transaction again.

Check the documentation for your database if you’re not familiar with how it handles transaction isolation.

## <a id="deadlocks"></a>Deadlocks

Whenever you work with locks, there is always a risk of deadlocks. A deadlock occurs when a transaction waits for a lock that a second transaction holds, and that second transaction waits for a lock that the first transaction holds. The following example demonstrates this:

| Transaction A                                 | Transaction B                                 |
| --------------------------------------------- | --------------------------------------------- |
| Try lock row X                                | Try lock row Y                                |
| **Lock X acquired**                           | **Lock Y acquired**                           |
| Try lock row Y                                | Try lock row X                                |
| *Waiting for transaction B to release lock Y* | *Waiting for transaction A to release lock X* |

One way of avoiding deadlocks is to acquire and release locks in the same order. This is demonstrated in the following example:

| Transaction A         | Transaction B                                 |
| --------------------- | --------------------------------------------- |
| Try lock row X        | Try lock row X                                |
| **Lock X acquired**   | *Waiting for transaction A to release lock X* |
| Try lock row Y        | …​                                            |
| **Lock Y acquired**   | …​                                            |
| Release locks X and Y | …​                                            |
|                       | **Lock X acquired**                           |
|                       | Try lock Y                                    |
|                       | **Lock Y acquired**                           |
|                       | Release locks X and Y                         |

It’s not always clear why transaction deadlocks occur. In some databases, even read-only queries can cause deadlocks. You may be able to fix some deadlocks by changing the isolation level of your transaction, but this may have other negative consequences on the data consistency.

When you can’t avoid deadlocks, you have to be prepared to deal with them. Most databases are able to detect when a deadlock occurs. When this happens, they pick a victim transaction and roll it back, allowing the other transaction to proceed. Your application then has to execute the victim transaction again. You should check your database documentation to see how it handles deadlocks.

A deadlock exception is a form of pessimistic locking exception. For more information about handling those, see the [Pessimistic Locking](https://vaadin.com/docs/next/building-apps/forms-data/consistency/pessimistic-locking.md#resolving-conflicts) documentation page.

## <a id="transaction-propagation"></a>Transaction Propagation

Transaction propagation controls how Spring manages transactions across multiple methods in an application. A method can run inside a *transactional context*. If one such method calls another method that also runs inside a transactional context, the propagation decides how the called method should behave. It could, for instance, join the existing transaction, start a new one, or fail.

Spring supports the following propagation levels:

- `REQUIRED`

  When there is an active transaction, Spring executes the method inside it. Otherwise, Spring creates a new transaction. This is the default propagation level.

- `REQUIRES_NEW`

  When there is an active transaction, Spring suspends it and creates a new one. Once the new transaction has completed, Spring resumes the earlier one.

- `MANDATORY`

  If there is an active transaction, Spring executes the method inside it. Otherwise, Spring throws an exception and doesn’t execute the method.

- `SUPPORTS`

  For an active transaction, Spring executes the method inside it. Otherwise, the method is executed without a transaction.

- `NOT_SUPPORTED`

  When there’s an active transaction, Spring suspends it. The method is then executed without a transaction. Once the method has completed, Spring resumes the earlier one.

- `NEVER`

  If there is an active transaction, Spring throws an exception and doesn’t execute the method.

Spring also has a `NESTED` propagation level, but it has some limitations. For more information on it, see the [Spring Documentation](https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/tx-propagation.html).

## <a id="transaction-management"></a>Transaction Management

- [Declarative Transactions](https://vaadin.com/docs/next/building-apps/forms-data/consistency/transactions/declarative.md): How to manage transactions declaratively in Vaadin applications.
- [Programmatic Transactions](https://vaadin.com/docs/next/building-apps/forms-data/consistency/transactions/programmatic.md): How to manage transactions programmatically in Vaadin applications.
