Skip to content

Avoid MySQL and MariaDB chat memory deadlocks during concurrent saves#6649

Open
seeun0210 wants to merge 1 commit into
spring-projects:mainfrom
seeun0210:avoid-mysql-chat-memory-deadlocks
Open

Avoid MySQL and MariaDB chat memory deadlocks during concurrent saves#6649
seeun0210 wants to merge 1 commit into
spring-projects:mainfrom
seeun0210:avoid-mysql-chat-memory-deadlocks

Conversation

@seeun0210

@seeun0210 seeun0210 commented Jul 20, 2026

Copy link
Copy Markdown

Fixes #6655

Thank you for taking the time to contribute to Spring AI!

Problem

JdbcChatMemoryRepository.saveAll() deletes the existing conversation rows and then inserts the replacement messages in one transaction. With MySQL or MariaDB's default REPEATABLE_READ isolation, concurrent saves for different, previously unseen conversation IDs can acquire overlapping gap locks during the deletes and deadlock when the inserts begin.

The new MySQL integration test deterministically reproduces this by pausing four concurrent saves after their deletes. It fails on main with Deadlock found when trying to get lock.

Changes

  • Use READ_COMMITTED for the repository-created DataSourceTransactionManager when the resolved dialect is MySQL or MariaDB. This prevents the missing-row deletes from taking the conflicting gap locks.
  • Keep ISOLATION_DEFAULT when an application supplies its own PlatformTransactionManager, preserving outer-transaction participation and compatibility with transaction managers that do not support JDBC isolation levels.
  • Add a MySQL integration test for concurrent saves to different conversations.
  • Add a unit test that protects the explicit transaction-manager isolation contract.

Design rationale

When only a DataSource is supplied, the repository creates its own transaction manager and therefore owns the transaction boundary. Selecting a safe isolation level for its delete-then-insert algorithm avoids requiring every MySQL and MariaDB user to reconfigure an application-wide connection pool. The override applies only to repository-created transactions; it does not change the DataSource default or unrelated application transactions. READ_COMMITTED preserves the atomicity of the delete and inserts, while saveAll() does not require a repeatable-read snapshot.

Trade-off

When an application supplies its own PlatformTransactionManager, the repository does not override that manager's isolation level. If it starts or joins a MySQL or MariaDB REPEATABLE_READ transaction, concurrent saves can still encounter the same gap-lock deadlock. Applications using an explicit transaction manager remain responsible for selecting READ_COMMITTED or handling deadlock retries; forcing an isolation level here would break outer-transaction semantics and can be unsupported by non-JDBC transaction managers.

Verification

  • ./mvnw clean package
  • ./mvnw -pl memory-repositories/spring-ai-model-chat-memory-repository-jdbc -Pintegration-tests -Dit.test=JdbcChatMemoryRepositoryMysqlIT verify
  • ./mvnw -pl memory-repositories/spring-ai-model-chat-memory-repository-jdbc -Dtest=JdbcChatMemoryRepositoryBuilderTests test
  • ./mvnw -pl memory-repositories/spring-ai-model-chat-memory-repository-jdbc process-sources
  • The same four-save barrier scenario was also executed against MariaDB 10.3.39, PostgreSQL 17, H2, and SQL Server 2022; all database-specific test suites passed.

Contributor checklist

  • Every commit contains a Signed-off-by line (git commit -s) per the DCO.
  • The branch is based on the latest main.
  • Unit and integration tests cover the change.
  • The full build passes.

Signed-off-by: seeun0210 <cocobell3@naver.com>
@seeun0210 seeun0210 changed the title Avoid MySQL chat memory deadlocks during concurrent saves Avoid MySQL and MariaDB chat memory deadlocks during concurrent saves Jul 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JdbcChatMemoryRepository.saveAll() deadlocks on MySQL/MariaDB under concurrent saves to different conversations (REPEATABLE_READ gap locks)

2 participants