Backend Development 21 min read

Does Spring Commit Before Unlock? Unraveling Transaction Timing in High‑Concurrency

This article dissects the exact moment a Spring @Transactional method commits relative to a surrounding lock, explains why committing after unlock can cause overselling, and provides source‑level debugging techniques to verify transaction start, commit, and rollback behavior in MySQL under high concurrency.

macrozheng
macrozheng
macrozheng
Does Spring Commit Before Unlock? Unraveling Transaction Timing in High‑Concurrency

Recently a question on SegmentFault presented a code snippet where a

@Transactional

method performs two database operations: checking product stock and, if stock exists, decrementing it and inserting an order.

Query product inventory.

If inventory > 0, decrease stock.

Insert order record.

Both steps modify the database and therefore must be atomic. The author wrapped the whole method with a

lock

to ensure only one thread can execute the stock‑decrease logic at a time, and added

@Transactional

to manage the transaction.

When Does the Transaction Actually Commit?

The crucial question is whether the transaction is committed before the lock is released (

unlock

) or after. If the commit occurs after

unlock

, another thread may read stale inventory and cause overselling.

Assuming MySQL’s isolation level is REPEATABLE READ, the following scenario illustrates the problem:

Two threads A and B request the same product when only one item is left. Thread A acquires the lock, reads stock = 1, decrements it to 0, and releases the lock before the transaction is committed. Thread B then acquires the lock, reads the same stock value (still 1 because A’s transaction hasn’t been committed), and also creates an order, resulting in overselling.

Transaction Start Timing

In Spring, the transaction actually starts when the first SQL statement that touches an InnoDB table is executed, not when the

@Transactional

annotation is processed. The log line

Switching JDBC Connection [...] to manual commit

shows that Spring sets

autoCommit

to

false

, which only prepares the connection; the transaction begins with the first DML statement.

Relevant source code (simplified):

<code>con.setAutoCommit(false);</code>

Only after the first

SELECT/INSERT/UPDATE/DELETE

does MySQL consider the transaction active.

Debugging Tips

To verify the exact point where the transaction starts, set a breakpoint in

org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin

. The call stack will show the lock acquisition, then the first SQL execution, confirming that the transaction begins after the lock.

Similarly, to see when the commit happens, place a breakpoint in

org.springframework.transaction.support.AbstractPlatformTransactionManager#commit

. The commit is performed after the method returns, unless the transaction has been marked

rollback‑only

.

Solution

The safe approach is to ensure the transaction’s commit occurs before the lock is released. This can be achieved by moving the lock to surround the entire transactional block or by using a distributed lock that is released only after the transaction successfully commits.

Alternatively, raising the isolation level to SERIALIZABLE guarantees that no other transaction can read the stale stock value, eliminating overselling without explicit locking (though at a performance cost).

For cases where you need finer control, you can use programmatic transaction management to explicitly begin, commit, or roll back the transaction around the critical section.

Rollback‑Only Scenario

If an inner transactional method throws a

RuntimeException

, Spring marks the outer transaction as

rollback‑only

. The outer method will then roll back in the

finally

block, even if it completes without further exceptions.

Understanding Spring’s default rollback rules (RuntimeException or Error) and the

shouldCommitOnGlobalRollbackOnly

flag helps avoid unexpected rollbacks.

By aligning lock scope, transaction boundaries, and isolation levels, you can prevent the overselling issue demonstrated in the original code.

JavatransactionDatabaseConcurrencySpringLock
macrozheng
Written by

macrozheng

Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.

0 followers
Reader feedback

How this landed with the community

login 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.