You'll want to change your code to something like this.
DECLARE @AssetIssueId INT
INSERT INTO [Issue] ([IssueId], [AssetId]) VALUES (?, ?)
SET @AssetIssueID = scope_identity()
INSERT INTO [IssuePropertyValues]
@AssetIssueID, ?, ?
As to the why...
If Java is starting a transaction for you automatically, and the insert of 10k+ rows is taking a few moments, then this thread is taking an IX on the Issue table. Another thread is then taking an IX on the issue table (which is blocked). This thread is then attempting to take an S on the issue table (for the INSERT SELECT FROM statement) which is blocked waiting for the other thread, and we now have a deadlock.
Nothing can prevent deadlocks in a RDBMS. However, you can greatly reduce the chance of the occurring by following a few simple rules:
- Make sure your queries are tuned well, using appropriate indexes.
- If you have to take concurrent locks on multiple tables, all your statements and procedures should access those tables in the same order.
- Keep your transactions as short as possible.
That will make it a lot less likely for you to encounter deadlocks. However, you cannot completely avoid them, so your code should be resilient and just automatically wait a random time interval (a few milliseconds) and then try again. Only if after three or so retries you still cant get the statement executed log the error.
Now to your question, of how to reproduce the deadlock: There are several types of deadlocks. The simplest one is the deadlock on two resources:
- connection A takes lock on resource 1
- connection B takes lock on resource 2
- connection A tries to take conflicting lock on resource 2 and has to wait
- connection B tries to take conflicting lock on resource 1 and has to wait
Now both connections wait for each other causing a deadlock. This type of deadlock can be fairly easily reproduced by stepping through the code one statement at a time on two connections. Other deadlocks are not always as easy to reproduce because e.g. they might only occur on memory pressure situations or similar.
The easiest way to get more information about deadlocks is, to set up a deadlock-graph trace. The deadlock graph contains a lot of information that can be helpful understanding the captured deadlock. As your system (hopefully) has deadlock only relatively infrequent, you might have to run the trace for a while. But if you are capturing only that one event, it won't use a lot of resources. Once you captured the deadlock graph look at it as XML. The graphical representation given by the profiler does reveal only a limited amount of information, but it is a good starting point.
Best Answer
Generally, this pattern is more efficient and far less likely to lead to deadlocks or other concurrency issues:
...simply because you only have to check for the row once. You might also write the
UPDATE
as:...which may be more index-friendly in some cases.
Exclusive locks for write operations are not taken until just before a row to modify is actually updated. While SQL Server is searching for rows to update, it uses update locks, which do not conflict with concurrent reads. The update lock is released immediately if SQL Server determines that the row being checked does not qualify for the update.
The only reason I can think of using the
if exists
method is if there areUPDATE/DELETE
triggers in the table that you want to avoid being fired, especially if you haveINSTEAD OF
triggers which can take some action before any update or delete is actually attempted.It is normally best to write DML such that only rows that require a change are affected.