READ COMMITTED is one of two transaction isolation levels supported on CockroachDB. By default, CockroachDB uses the isolation level, which is the strongest ANSI transaction isolation level.
READ COMMITTED isolation is appropriate in the following scenarios:
- Your application needs to maintain a high workload concurrency with minimal , and it can tolerate potential concurrency anomalies. Predictable query performance at high concurrency is more valuable than guaranteed transaction .
- You are that was built at a
READ COMMITTEDisolation level on the source database, and it is not feasible to modify your application to useSERIALIZABLEisolation.
SERIALIZABLE isolation guarantees data correctness by placing transactions into a , READ COMMITTED isolation permits some concurrency anomalies in exchange for minimizing transaction aborts, , and blocking. Compared to SERIALIZABLE transactions, READ COMMITTED transactions do not return that require client-side handling. See READ COMMITTED transaction behavior.
If your workload is already running well under SERIALIZABLE isolation, Cockroach Labs does not recommend changing to READ COMMITTED isolation unless there is a specific need.
READ COMMITTED on CockroachDB provides stronger isolation than READ COMMITTED on PostgreSQL. On CockroachDB, READ COMMITTED prevents anomalies within single statements. For complete details on how READ COMMITTED is implemented on
CockroachDB, see the Read Committed
RFC.Enable READ COMMITTED isolation
By default, the sql.txn.read_committed_isolation.enabled is true, enabling READ COMMITTED transactions. If the cluster setting is false, READ COMMITTED transactions will run as SERIALIZABLE.
To check whether any transactions are being upgraded to SERIALIZABLE, see the graph in the DB Console.
Set the default isolation level to READ COMMITTED
To set all future transactions to run at READ COMMITTED isolation, use one of the following options:
-
The statement, which applies to the current session:
-
The session variable:
At the session level:
At the :At the :
-
The
default_transaction_isolationsession variable as a with :
Set the current transaction to READ COMMITTED
To begin a transaction as a READ COMMITTED transaction, use one of the following options:
-
The statement:
-
The statement, at the beginning of the transaction:
-
The session variable, at the beginning of the transaction:
SHOW within the open transaction:
READ COMMITTED does not affect the default isolation
level, which can be different.
READ COMMITTED transaction behavior
READ COMMITTED and SERIALIZABLE transactions both serve globally consistent (“non-stale”) reads and . READ COMMITTED transactions have the following differences:
-
Writes in concurrent
READ COMMITTEDtransactions can interleave without aborting transactions, and a write can never block a non-locking read of the same row. This is becauseREAD COMMITTEDtransactions are not required to be placed into a . -
Whereas statements in
SERIALIZABLEtransactions see data that committed before the transaction began, statements inREAD COMMITTEDtransactions see data that committed before each statement began. If rows are being updated by concurrent writes, reads in aREAD COMMITTEDtransaction can return different results. For details on how this is implemented, see . -
Due to the preceding behaviors,
READ COMMITTEDtransactions permit some types of concurrency anomalies that are prevented inSERIALIZABLEtransactions. For details and examples, see Concurrency anomalies. -
You can mitigate concurrency anomalies by issuing locking reads in
READ COMMITTEDtransactions. These statements can block concurrent transactions that are issuing writes or other locking reads on the same rows. -
When using
READ COMMITTEDisolation, you do not need to implement to handle under .READ COMMITTEDtransactions never return errors, and will only return40001errors in limited cases, as described in the following points.
READ COMMITTED transactions can abort in certain scenarios:
- Transactions at all isolation levels are subject to , where a transaction attempts to lock a row that is already locked by a or locking read. In such cases, the later transaction is blocked until the earlier transaction commits or rolls back, thus releasing its lock on the row. Lock contention that produces a deadlock between two transactions will result in a transaction abort and a
40001error ( or ) returned to the client. - violations will abort transactions at all isolation levels.
- In rare cases under
READ COMMITTEDisolation, a or error can be returned to the client if a statement has already begun streaming a partial result set back to the client and cannot retry transparently. By default, the result set is buffered up to the value of the cluster setting before overflowing and being streamed to the client.
Concurrency anomalies
Statements in concurrentREAD COMMITTED transactions can interleave with each other. This can create concurrency anomalies that are not permitted under SERIALIZABLE isolation, which places concurrent transactions into a .
Non-repeatable reads and phantom reads
READ COMMITTED transactions can serve different reads over the course of a transaction.
Non-repeatable reads return different row values because a concurrent transaction updated the values in between reads:
- Transaction
Areads rowRat timestamp1. - Transaction
Bwrites to rowRand commits at timestamp2. - Transaction
Areads rowRand gets a different result at timestamp3.
- Transaction
Areads the set of rowsSat timestamp1. - Transaction
Binserts, deletes, or updates rows inSand commits at timestamp2. - Transaction
Areads the set of rowsSand gets a different result at timestamp3.
SERIALIZABLE transactions see data that committed before the transaction began, statements in READ COMMITTED transactions see data that committed before each statement began.
For details on how this is implemented, see .
Example: Non-repeatable reads and phantom reads
Session 1
Session 2
In a terminal window (Session 1), create a table and insert some values:READ COMMITTED transaction and read a table row:
READ COMMITTED transaction:
Lost update anomaly
TheREAD COMMITTED conditions that permit non-repeatable reads and phantom reads also permit lost update anomalies, where an update from a transaction appears to be “lost” because it is overwritten by a concurrent transaction:
- Transaction
Areads rowRat timestamp1. - Transaction
Bwrites to rowRand commits at timestamp2. - Transaction
Awrites to rowRand commits at timestamp3.
R has changed while transaction A is open. However, A can still write to R and commit, effectively overwriting the update from transaction B.
Under
SERIALIZABLE isolation, transaction A would have aborted with a
error, prompting the client
to retry the transaction.Example: Lost update anomaly
Session 1
Session 2
In a terminal window (Session 1), create a table and insert some values:READ COMMITTED transaction and read a table row:
READ COMMITTED transaction:
While concurrent
READ COMMITTED transactions can have their committed writes overwritten, uncommitted writes in
READ COMMITTED transactions cannot be overwritten.Write skew anomaly
The following sequence of operations on a table is possible underREAD COMMITTED isolation:
- Transaction
Areads rowRat timestamp1. - Transaction
Breads rowSat timestamp2. - Transaction
Awrites to rowSand commits at timestamp3. - Transaction
Bwrites to rowRand commits at timestamp4.
A updates the value of S based on the R value it reads at timestamp 1. Transaction B updates the value of R based on the S value it reads at timestamp 2. The value of S has changed while transaction B is open, but B can still write and commit instead of aborting, since READ COMMITTED transactions do not require serializability. This is the basis of potential write skew anomalies where two concurrent transactions each read values that the other subsequently updates.
For details on why this is allowed, see .
Example: Write skew anomaly
For an example of how a write skew anomaly can occur, see Demonstrate interleaved statements inREAD COMMITTED transactions.
Locking reads
To reduce the occurrence of concurrency anomalies inREAD COMMITTED isolation, you can strengthen the isolation of individual reads by using or to issue locking reads on specific rows. Locking reads behave similarly to : they lock qualifying rows to prevent concurrent writes from modifying them until the transaction commits. Conversely, if a locking read finds that a row is exclusively locked by a concurrent transaction, it waits for the other transaction to commit or rollback before proceeding. A locking read in a transaction will always have the latest version of a row when the transaction commits.
The clause used with the SELECT statement determines the lock strength of a locking read:
SELECT FOR UPDATEobtains an exclusive lock on each qualifying row, blocking concurrent writes and locking reads on the row. Only one transaction can hold an exclusive lock on a row at a time, and only the transaction holding the exclusive lock can write to the row. For an example, see Reserve rows for updates using exclusive locks.SELECT FOR SHAREobtains a shared lock on each qualifying row, blocking concurrent writes and exclusive locking reads on the row. Multiple transactions can hold a shared lock on a row at the same time. When multiple transactions hold a shared lock on a row, none can write to the row. A shared lock grants transactions mutual read-only access to a row, and ensures that they read the latest version of the row. For an example, see Reserve values using shared locks.
SELECT FOR UPDATE or SELECT FOR SHARE read is issued on a row, only the latest version of the row is returned to the client. Under READ COMMITTED isolation, neither statement will block concurrent, non-locking reads.
When to use locking reads
Use locking reads in your application if certainREAD COMMITTED transactions must guarantee that the data they access will not be changed by intermediate writes.
Non-locking reads can allow intermediate writes to update rows before READ COMMITTED transactions commit, potentially creating concurrency anomalies. Locking reads prevent such anomalies, but increase the amount of lock contention that if latency becomes too high. Note that locking reads do not prevent phantom reads that are caused by the insertion of new rows, since only existing rows can be locked.
Locking reads are not effective for emulating
SERIALIZABLE transactions, which can avoid locking reads because they
always . As a
result, READ COMMITTED transactions that use locking reads will perform differently than SERIALIZABLE transactions
at various levels of concurrency.- If you need to read and later update a row within a transaction, use
SELECT... FOR UPDATEto acquire an exclusive lock on the row. This guarantees data integrity between the transaction’s read and write operations. - If you need to read the latest version of a row, and later update a different row within a transaction, use
SELECT... FOR SHAREto acquire a shared lock on the row. This blocks all concurrent writes on the row without unnecessarily blocking concurrent reads or otherSELECT... FOR SHAREqueries.
Examples
In this scenario:- A hospital has an application for doctors to manage their on-call shifts.
- The hospital has a rule that at least one doctor must be on call at any one time.
- Two doctors are on call for a particular shift, and both of them try to request leave for the shift in two concurrent transactions.
- Under the
READ COMMITTEDisolation level, the write skew anomaly can potentially result in both doctors successfully booking leave and the hospital having no doctors on call for that particular shift.
- Observe that
READ COMMITTEDtransactions can serve different reads. - Use exclusive locks to strengthen isolation for
READ COMMITTEDtransactions. - Use shared locks to reserve values in
READ COMMITTEDtransactions.
Before you begin
- Open the SQL shell using .
-
Enable
READ COMMITTEDtransactions: -
Create the
doctorstable: -
Create the
schedulestable: -
Add two doctors to the
doctorstable: -
Insert one week’s worth of data into the
schedulestable:
Demonstrate interleaved statements in READ COMMITTED transactions
Before proceeding, reset the example scenario:
Session 1
Session 2
Doctor 1, Abe, starts to request leave for2023-12-05 using the hospital’s schedule management application.
Start a transaction:
2023-12-05:
2023-12-05:
2023-12-05. Update the schedule to put Abe on leave:
2023-12-05. Session 1 sees that only Abe is on leave once its transaction commits:
2023-12-05. Update the schedule to put Betty on leave:
2023-12-05. Session 2 sees that only Betty is on leave once its transaction commits:
SELECT query) has changed due to the concurrent transaction in Session 2.
In Session 2, read the rows for 2023-12-05 again:
on_call value for doctor 1, thus changing the read result for the transaction in Session 2.
If the transaction in Session 2 commits and updates the on_call value for Betty, this will create a write skew anomaly. The result would be that neither Abe nor Betty is scheduled to be on call on 2023-12-05.
Instead, the transaction should rollback so that the write skew anomaly does not commit:
Reserve rows for updates using exclusive locks
Before proceeding, reset the example scenario:Session 1
Session 2
Doctor 1, Abe, starts to request leave for2023-12-05 using the hospital’s schedule management application.
Start a transaction:
2023-12-05. Use to lock the rows so that only the current transaction can update them:
2023-12-05. Use FOR UPDATE to lock the rows so that only the current transaction can update them:
2023-12-05. Update the schedule to put Abe on leave:
2023-12-05, which show that Abe has already been put on leave for that day:
Reserve row values using shared locks
Before proceeding, reset the example scenario:Session 1
Session 2
Doctor 1, Abe, starts to request leave for2023-12-05 using the hospital’s schedule management application.
Start a transaction:
2023-12-05. Use to lock the rows so that they cannot be updated by another transaction:
2023-12-05. Use FOR SHARE to lock the rows so that they cannot be updated by another transaction:
2023-12-05 and confirm that Betty is still on call:
Known limitations
- Mixed-isolation-level workloads must enable foreign-key check locking for
SERIALIZABLEtransactions to avoid race conditions. - Schema changes (e.g., , , ) cannot be performed within explicit
READ COMMITTEDtransactions when the is set tooff, and will cause transactions to abort. As a workaround, toSERIALIZABLE. - Multi-column-family checks during updates are not supported under
READ COMMITTEDisolation. - Because locks acquired by checks, , and are fully replicated under
READ COMMITTEDisolation, some queries experience a delay for Raft replication. - checks are not performed in parallel under
READ COMMITTEDisolation. - statements are less optimized under
READ COMMITTEDisolation than underSERIALIZABLEisolation. UnderREAD COMMITTEDisolation,SELECT FOR UPDATEandSELECT FOR SHAREusually perform an extra lookup join for every locked table when compared to the same queries underSERIALIZABLE. In addition, some optimization steps (such as de-correlation of correlated ) are not currently performed on these queries. - Regardless of isolation level, statements in CockroachDB do not prevent insertion of new rows matching the search condition (i.e., ). This matches PostgreSQL behavior at all isolation levels.

