Today, I was helping a team that was getting a database deadlock in their Java Spring Boot application using Microsoft SQL Server. The stacktrace of the error was as shown below.
Exception is org.springframework.dao.CannotAcquireLockException: could not execute query; SQL ; nested exception is org.hibernate.exception.LockAcquisitionException: could not execute query] with root cause com.microsoft.sqlserver.jdbc.SQLServerException: Transaction (Process ID 132) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
The database deadlock was caused when user uploads a file that was being written to the database. While the file was being uploaded user navigated to another page where data from the same table was queried.
When we debugged SQL Server traces we discovered that row-level lock was escalated to table lock. This was causing SQL Server to stop other transactions from accessing the table.
To solve this issue we enabled
READ_COMMITTED_SNAPSHOT option for the database so that it uses
READ_COMMITTED with row versioning isolation strategy. The default database isolation level in SQL Server is
READ_COMMITTED which means a query in the current transaction cannot read data modified by another transaction that has not yet committed, thus preventing dirty reads. The isolation level uses shared locking or row versioning to prevent dirty reads, depending on whether the
READ_COMMITTED_SNAPSHOT database option is enabled. Since wee enabled
READ_COMMITTED_SNAPSHOT SQL Server will use versioning instead of locks.
ALTER DATABASE <database name> SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE;