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.
Recently a question on SegmentFault presented a code snippet where a
@Transactionalmethod 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
lockto ensure only one thread can execute the stock‑decrease logic at a time, and added
@Transactionalto 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
@Transactionalannotation is processed. The log line
Switching JDBC Connection [...] to manual commit
shows that Spring sets
autoCommitto
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/DELETEdoes 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
finallyblock, even if it completes without further exceptions.
Understanding Spring’s default rollback rules (RuntimeException or Error) and the
shouldCommitOnGlobalRollbackOnlyflag helps avoid unexpected rollbacks.
By aligning lock scope, transaction boundaries, and isolation levels, you can prevent the overselling issue demonstrated in the original code.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.