I'm using Azure SQL + Java + Spring Boot 2. Currently I'm trying to understand the reason why deadlocks occur.
In transaction I'm doing insert and later update on the same table by different rows.
As far as I understood, SQL Server by default uses rowlock and the read_commited isolation. Here are deadlock details:
<deadlock>
<victim-list>
<victimProcess id="process1991308a4e8"/>
</victim-list>
<process-list>
<process id="process1991308a4e8" taskpriority="0" logused="1104"
waitresource="KEY: 11:72057594043891712 (45e31f8447de)" waittime="3092" ownerId="5354390"
transactionname="implicit_transaction" lasttranstarted="2019-02-13T15:22:55.570" XDES="0x19918794420"
lockMode="U" schedulerid="1" kpid="85796" status="suspended" spid="147" sbid="0" ecid="0" priority="0"
trancount="2" lastbatchstarted="2019-02-13T15:22:56.450" lastbatchcompleted="2019-02-13T15:22:56.340"
lastattention="1900-01-01T00:00:00.340" clientapp="Microsoft JDBC Driver for SQL Server"
hostname="xxx-service-7ff786b7c7-kklgm" hostpid="0" loginname="xxx_core_user"
isolationlevel="read committed (2)" xactid="5354390" currentdb="11" currentdbname="payment"
lockTimeout="4294967295" clientoption1="671088672" clientoption2="128058">
<executionStack>
<frame procname="unknown" queryhash="0x49c416d3b96f3c35" queryplanhash="0x6ebf9e54f1242ad5" line="1"
stmtstart="510" stmtend="1096"
sqlhandle="0x02000000e96bb62c3a246f941a8767e52b870246866daefc0000000000000000000000000000000000000000">
unknown
</frame>
<frame procname="unknown" queryhash="0x0000000000000000" queryplanhash="0x0000000000000000" line="1"
sqlhandle="0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000">
unknown
</frame>
</executionStack>
<inputbuf>
(@P0 datetime2,@P1 datetime2,@P2 bigint,@P3 nvarchar(4000),@P4 nvarchar(4000),@P5 nvarchar(4000),@P6
nvarchar(4000),@P7 bigint,@P8 int,@P9 nvarchar(4000),@P10 nvarchar(4000),@P11 bigint,@P12
nvarchar(4000),@P13 nvarchar(4000),@P14 nvarchar(4000),@P15 int)update payment set created_at=@P0,
modified_at=@P1, amount=@P2, currency=@P3, description=@P4, email=@P5, external_id=@P6, fee_amount=@P7,
lock_version=@P8, payment_method_provider_type=@P9, payment_method_type=@P10, pos_id=@P11,
pricing_id=@P12, status=@P13 where id=@P14 and lock_version=@P15
</inputbuf>
</process>
<process id="process199130a5088" taskpriority="0" logused="1400"
waitresource="KEY: 11:72057594043891712 (22e8948179bd)" waittime="2842" ownerId="5354401"
transactionname="implicit_transaction" lasttranstarted="2019-02-13T15:22:55.887" XDES="0x19911ddc420"
lockMode="U" schedulerid="1" kpid="8708" status="suspended" spid="156" sbid="0" ecid="0" priority="0"
trancount="2" lastbatchstarted="2019-02-13T15:22:56.557" lastbatchcompleted="2019-02-13T15:22:56.540"
lastattention="1900-01-01T00:00:00.540" clientapp="Microsoft JDBC Driver for SQL Server"
hostname="xxx-service-7ff786b7c7-kklgm" hostpid="0" loginname="xxx_core_user"
isolationlevel="read committed (2)" xactid="5354401" currentdb="11" currentdbname="payment"
lockTimeout="4294967295" clientoption1="671088672" clientoption2="128058">
<executionStack>
<frame procname="unknown" queryhash="0x49c416d3b96f3c35" queryplanhash="0x6ebf9e54f1242ad5" line="1"
stmtstart="510" stmtend="1096"
sqlhandle="0x02000000e96bb62c3a246f941a8767e52b870246866daefc0000000000000000000000000000000000000000">
unknown
</frame>
<frame procname="unknown" queryhash="0x0000000000000000" queryplanhash="0x0000000000000000" line="1"
sqlhandle="0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000">
unknown
</frame>
</executionStack>
<inputbuf>
(@P0 datetime2,@P1 datetime2,@P2 bigint,@P3 nvarchar(4000),@P4 nvarchar(4000),@P5 nvarchar(4000),@P6
nvarchar(4000),@P7 bigint,@P8 int,@P9 nvarchar(4000),@P10 nvarchar(4000),@P11 bigint,@P12
nvarchar(4000),@P13 nvarchar(4000),@P14 nvarchar(4000),@P15 int)update payment set created_at=@P0,
modified_at=@P1, amount=@P2, currency=@P3, description=@P4, email=@P5, external_id=@P6, fee_amount=@P7,
lock_version=@P8, payment_method_provider_type=@P9, payment_method_type=@P10, pos_id=@P11,
pricing_id=@P12, status=@P13 where id=@P14 and lock_version=@P15
</inputbuf>
</process>
</process-list>
<resource-list>
<keylock hobtid="72057594043891712" dbid="11" objectname="56ae82fa-8057-4f9d-b4a8-290dc2ce3dec.dbo.payment"
indexname="pk_payment_id" id="lock19923e5d980" mode="X" associatedObjectId="72057594043891712">
<owner-list>
<owner id="process199130a5088" mode="X"/>
</owner-list>
<waiter-list>
<waiter id="process1991308a4e8" mode="U" requestType="wait"/>
</waiter-list>
</keylock>
<keylock hobtid="72057594043891712" dbid="11" objectname="56ae82fa-8057-4f9d-b4a8-290dc2ce3dec.dbo.payment"
indexname="pk_payment_id" id="lock19928078f80" mode="X" associatedObjectId="72057594043891712">
<owner-list>
<owner id="process1991308a4e8" mode="X"/>
</owner-list>
<waiter-list>
<waiter id="process199130a5088" mode="U" requestType="wait"/>
</waiter-list>
</keylock>
</resource-list>
</deadlock>
Also I'm using a clustered index on my id column and there are no issues with random operations order.
Moreover when I only do insert inside transaction everything works fine, without a deadlock.
So why am I getting a deadlock?
I cannot share source code but this pseudocode completely represents all logic:
@Transactional
public void processMessage(Message message) {
Entity entity =
repository.findById(message.getId())
.orElseGet(() -> {
Entity entity = converter.convertFrom(message);
return repository.insert(entity); // insert
});
if (canUpdateStatus(entity)) { //let's say it's always true
entity.setStatus(newStatus);
respository.save(entity); // update
}
}
Best Answer
Analysis
If you examine your deadlock XML, you will quickly find that these are two separate sessions (
spid
) involved:...and:
You'll also notice that the
kpid
(aka Windows Thread ID) differs:...and:
The individual transactions can be analysed by reading the individual
<process ...> ... </process>
blocks. You have two.Both processes are trying to
update
thepayment
table. This can be observed by extracting the statements form the relevant<process></process>
sections which contain individual<inputbuffer>
blocks:For SPID 147:
... and for SPID 156:
Because of the updates to the same table at approximately the same time (
2019-02-13 15:22:55.570
for both transactions) one of them will have to be automatically rolled back to resolve the deadlock.In this case the deadlock resolver decided that the Victim SPID would be number 147.
...which resolves to the SPID 147:
Root Cause
The actual blocking is on the index
pk_payment_id
which can be retrieved from the<resource-list>
section. It contains a list of affected objects and the corresponding<owner id="process.... mode="X"/>
which allows you to trace it back to the actual session id (spid).Here your
<resource-list>
:Both processes have an exclusive lock (
X
) which they would like to change to an upgrade lock (U
). Now two processes can't have an update lock at the same time and so one session gets rolled back.Reference: Transaction Locking and Row Versioning Guide (Microsoft | Docs | SQL)
Conclusion
There seems to be some form of timing issue in your code/framework or the insert/updates is being performed twice, instead of just once.
It's hard to determine which case you are observing. You would have to manually debug the program and watch for new SPIDs on the database.
Reference Links
sys.sysprocesses (Transact-SQL)