2

I have a stored procedure with following statements in it

INSERT INTO dbo.[ResultItems] (PGM,GRP,PGMGRPSEQ,ITMID,ITMDESC,BRAND,PackSize,IsSelected,UserId)
    SELECT  SRIS.PGM,SRIS.GRP,SRIS.PGMGRPSEQ,SRIS.ITMID,SRIS.ITMDESC,SRIS.BRAND,SRIS.PackSize,SRIS.IsSelected,SRIS.UserId
    FROM @ItemResult SRIS
    LEFT OUTER JOIN [dbo].[ResultItems] SRI (NOLOCK)
        ON SRI.ITMID = SRIS.ITMID
        AND SRI.PGM = SRIS.PGM
        AND SRI.GRP = SRIS.GRP
        AND SRI.PGMGRPSEQ = SRIS.PGMGRPSEQ
        AND SRI.UserId=SRIS.UserId 
    WHERE SRI.ITMID IS NULL  ----logic to avoid duplicate
    GROUP BY SRIS.PGM,SRIS.GRP,SRIS.PGMGRPSEQ,SRIS.ITMID,SRIS.ITMDESC,SRIS.BRAND,SRIS.PackSize,SRIS.IsSelected,SRIS.UserId


UPDATE SRI
SET SRI.IsSelected = 1
FROM @ItemResult IST 
INNER JOIN [dbo].ResultItems SRI (NOLOCK)
ON SRI.PGM = IST.[PGM]
    AND SRI.GRP = IST.GRP
    AND SRI.PGMGRPSEQ = IST.PGMGRPSEQ
    AND SRI.ITMID = IST.ITMID
    AND SRI.UserId=IST.UserId
WHERE SRI.UserId=@UserId

I have following index on the ResultItems table

IF NOT EXISTS(select 1 from sys.sysindexes where name = 'IX_RESULTITEMS_USERID')
BEGIN
    CREATE NONCLUSTERED INDEX IX_RESULTITEMS_USERID
    ON [dbo].[ResultItems] ([UserId])
END
GO

Five concurrent users are calling this SP. The update statement has a filter condition on UserId. Each user will execute the sp with their own userID only. That is why I have created an index on UserId column.

The expectation was the index will help to avoid table scan and there will be no dead-lock (since each user is looking for their own records)… But 1 out of 10 tests is causing a dead-lock.

I believe this is because the escalation to a table scan when there is huge data (more than 20000 records by each user).

What is the best way to avoid deadlock here?

TABLE and INDEXES

CREATE TABLE [dbo].[ResultItems](
    [SRIID] [int] IDENTITY(1,1) NOT NULL,
    [PGM] [nvarchar](50) NULL,
    [GRP] [nvarchar](50) NULL,
    [PGMGRPSEQ] [nvarchar](50) NULL,
    [ITMID] [nvarchar](18) NULL,
    [ITMDESC] [nvarchar](255) NULL,
    [BRAND] [nchar](40) NULL,
    [PackSize] [nvarchar](max) NULL,
    [IsSelected] [bit] NULL,
    [UserId] [nvarchar](50) NULL,
 CONSTRAINT [PK_SEARCH_RESULT_ITEMS] PRIMARY KEY CLUSTERED 
(
    [SRIID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO

CREATE NONCLUSTERED INDEX [IX_PGM_GRP_PGMGRPSEQ_ITMID_UserId] ON [dbo].[ResultItems]
(
    [PGM] ASC,
    [GRP] ASC,
    [PGMGRPSEQ] ASC,
    [ITMID] ASC,
    [UserId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

CREATE NONCLUSTERED INDEX [IX_RESULTITEMS_USERID] ON [dbo].[ResultItems]
(
    [UserId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

REFERENCES

  1. Avoiding Deadlock Transaction Errors by Using ROWLOCK Hint in T-SQL
  2. INF: Analyzing and Avoiding Deadlocks in SQL Server
  3. Tips to avoid deadlocks?
LCJ
  • 22,196
  • 67
  • 260
  • 418
  • What is the isolation level? Default? Have you considered adding an index that will actually allow a seek (e.g. one that covers all of the columns in the JOIN clause)? You could also add an additional filter `AND SRI.IsSelected <> 1` because there is no reason matching rows that have already been updated. – Aaron Bertrand Dec 05 '13 at 16:04
  • can you show the CREATE TABLE including all indexes? – Aaron Bertrand Dec 05 '13 at 16:13
  • @AaronBertrand Added the table/index details – LCJ Dec 05 '13 at 16:16
  • 2
    Eek, why do your update/insert statements have NOLOCK specified on the affected table? – Aaron Bertrand Dec 05 '13 at 16:17
  • @AaronBertrand I have resolved the issue and referred your blog in my answer for `Merge` – LCJ Dec 09 '13 at 06:30

2 Answers2

1

Instead of INSERT then UPDATE, use MERGE. Also, get rid of the NOLOCK

MERGE INTO dbo.[ResultItems] SRI
USING @ItemResult SRIS ON (
      SRI.ITMID = SRIS.ITMID
  AND SRI.PGM = SRIS.PGM
  AND SRI.GRP = SRIS.GRP
  AND SRI.PGMGRPSEQ = SRIS.PGMGRPSEQ
  AND SRI.UserId=SRIS.UserId 
)
WHEN MATCHED THEN UPDATE
  SET SRI.IsSelected = 1
WHEN NOT MATCHED THEN
  INSERT (     PGM,     GRP,     PGMGRPSEQ,     ITMID,     ITMDESC,     BRAND,     PackSize,     IsSelected,     UserId)
  VALUES (SRIS.PGM,SRIS.GRP,SRIS.PGMGRPSEQ,SRIS.ITMID,SRIS.ITMDESC,SRIS.BRAND,SRIS.PackSize,SRIS.IsSelected,SRIS.UserId);
Anon
  • 10,660
  • 1
  • 29
  • 31
1

I resolved the issue by re-writing the stored procedure logic to avoid the update the statement. The update happens on a temporary table before the insert.

Other thoughts are using Dead-lock graph for analysis and using MERGE for query.

OTHER REFERENCES

  1. Merge Example (Solutions for INSERT OR UPDATE on SQL Server)
  2. https://dba.stackexchange.com/questions/23467/sql-server-2008-merge-statement-deadlocking-itself
  3. sql server deadlock case
  4. Locking with MERGE statement compared to Update/Insert in SQL Server 2005
  5. Stress-Test those Stored Procedures using C# and Nunit

MERGE BUGS

  1. Aaron Bertrand - Use Caution with SQL Server's MERGE Statement
  2. http://sqlblog.com/blogs/paul_white/archive/2013/02/06/incorrect-results-with-indexed-views.aspx
  3. http://www.sqlperformance.com/2013/02/t-sql-queries/another-merge-bug
Community
  • 1
  • 1
LCJ
  • 22,196
  • 67
  • 260
  • 418