Why JVM Locks Can Deadlock with MySQL Row Locks—and How to Fix Them
This article explains how JVM synchronized locks and MySQL row locks can interact to cause deadlocks, demonstrates the problem with sample Spring code, shows the resulting lock‑wait timeout exception, and provides a refactoring solution to avoid the deadlock.
1. Introduction
In multithreaded programming, locks ensure data consistency and prevent concurrent access issues. JVM locks (synchronized) and MySQL row locks are common mechanisms that can lead to deadlocks when used together. This article examines whether they can cause deadlocks, analyzes the reasons, and offers solutions.
2. Lock Overview
JVM Lock
JVM locks are built‑in synchronization mechanisms implemented via the synchronized keyword. They protect critical sections so that only one thread can execute the protected code at a time. Each object has an associated monitor lock; a thread must acquire the monitor before entering the synchronized block.
MySQL Row Lock
MySQL row locks protect individual rows in InnoDB tables, preventing multiple transactions from modifying or deleting the same row simultaneously. The lock granularity depends on the indexes used; primary‑key indexes lock a single row, while non‑unique indexes may lock multiple rows.
3. Example Code
The following Spring service demonstrates two operations: batch deletion and single‑row update, both wrapped with @Transactional and synchronized blocks.
<code>static class PersonService {
@Resource
private JdbcTemplate jdbcTemplate;
@Resource
private DataSource dataSource;
@Resource
private DataSourceTransactionManager tm;
// batch delete
@Transactional
public void batcherOperator() {
IntStream.of(1, 2, 3).forEach(id -> {
delete(id);
});
}
private void delete(int id) {
synchronized (this) {
this.jdbcTemplate.update("delete from t_person where id = ?", id);
}
}
// update with lock
@Transactional
public synchronized void update(int id) {
// other operations
this.jdbcTemplate.update("update t_person t set t.name = 'xxx' where t.id = 1");
}
}
</code>Running two threads—one executing batcherOperator() and the other executing update(1) —may not immediately show a problem, but after adding a sleep when deleting id = 2 , a lock‑wait timeout occurs:
<code>org.springframework.dao.CannotAcquireLockException: StatementCallback; SQL [update t_person t set t.name = 'xxx' where t.id = 1]; Lock wait timeout exceeded; try restarting transaction; nested exception is com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
</code>4. Visualizing the Deadlock
The following screenshots illustrate the lock waiting in two MySQL sessions.
The timeline diagram below shows how the JVM lock held during the delete of id = 2 blocks the update transaction, leading to a deadlock between the JVM monitor and the MySQL row lock.
5. Optimizing the Code
Moving the synchronized block outside the loop eliminates the deadlock:
<code>@Transactional
public void batcherOperator() {
synchronized (this) {
IntStream.of(1, 2, 3).forEach(id -> {
delete(id);
if (id == 2) {
try { TimeUnit.SECONDS.sleep(2); } catch (Exception e) { e.printStackTrace(); }
}
});
}
}
</code>After this change, the deadlock disappears, though other concurrency issues may still exist.
6. Conclusion
Understanding the interaction between JVM synchronized locks and MySQL row locks helps developers avoid deadlocks in concurrent applications. Proper lock scope and awareness of database lock behavior are essential for building efficient, stable backend systems.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
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.