Please start any new threads on our new site at https://forums.sqlteam.com. We've got lots of great SQL Server experts to answer whatever question you can come up with.

 All Forums
 SQL Server 2005 Forums
 Transact-SQL (2005)
 Deadlocking

Author  Topic 

simondeutsch
Aged Yak Warrior

547 Posts

Posted - 2012-01-12 : 09:59:02
The query is a reasonably complicated query for a report that compares fiscal information for two years. The problem is, that during most working hours, trying to run this report will often come up with the error that "Transaction process (process ID x) was deadlocked with another process and has been chosen as the deadlock victim etc."

The query does hit a couple of tables with INSERT and UPDATE loads, but its data sources are set to NOLOCK, so why is it still deadlocking with another process?

The data is coming out from a couple of table-valued functions as posted below.

SELECT 2012 AS CurYear, 2011 AS PastYear, COALESCE(D.InstypeName,D2.InstypeName) AS InstypeName,COALESCE(D.DeptName,D2.DeptName) AS DeptName,ISNULL(D.Visits,0) AS
VisitsPast,ISNULL(D.TotChg,0) AS ChargesPast, ISNULL(D.TotAdj,0) AS AdjustsPast,ISNULL(D2.Visits,0) AS
VisitsCur,ISNULL(D2.TotChg,0) AS ChargesCur, ISNULL(D2.TotAdj,0) AS AdjustsCur,ISNULL(D.Visits,0) + ISNULL(D2.Visits,0) AS TotVisits,
ISNULL(D.TotChg,0) + ISNULL(D2.TotChg,0) AS TotChg,ISNULL(D.TotAdj,0) + ISNULL(D2.TotAdj,0) AS TotAdj FROM
(SELECT COALESCE(VisCur.InstypeName,ChgCur.InstypeName,AdjCur.InstypeName) AS InsTypeName,
COALESCE(VisCur.DeptName,ChgCur.DeptName,AdjCur.DeptName) AS DeptName,VisCur.Visits,ChgCur.TotChg, AdjCur.TotAdj
FROM dbo.fn_ReportVisits (2011,'1/1/2012','1/12/2012') VisCur
FULL OUTER JOIN
dbo.fn_ReportCharges(2011,'1/1/2012','1/12/2012') ChgCur
ON VisCur.InsTypename = ChgCur.Instypename and VisCur.DeptName = ChgCur.Deptname FULL OUTER JOIN dbo.fn_ReportAdjusts(2011,'1/1/2012','1/12/2012') AdjCur
ON AdjCur.InsTypeName = Chgcur.InsTypeName AND ChgCur.DeptName = AdjCur.DeptName)D
FULL OUTER JOIN
(SELECT COALESCE(VisCur.InstypeName,ChgCur.InstypeName,AdjCur.InstypeName) AS InsTypeName,
COALESCE(VisCur.DeptName,ChgCur.DeptName,AdjCur.DeptName) AS DeptName,VisCur.Visits,TotChg, AdjCur.TotAdj
FROM dbo.fn_ReportVisits (2012,'1/1/2012','1/12/2012') VisCur
FULL OUTER JOIN
dbo.fn_ReportCharges(2012,'1/1/2012','1/12/2012') ChgCur
ON VisCur.InsTypename = ChgCur.Instypename and VisCur.DeptName = ChgCur.Deptname
FULL OUTER JOIN dbo.fn_ReportAdjusts(2012,'1/1/2012','1/12/2012') AdjCur
ON AdjCur.InsTypeName = Chgcur.InsTypeName AND AdjCur.DeptName = Chgcur.DeptName)D2 ON D2.Instypename = D.InstypeName AND D.DeptName
= D2.DeptName ORDER BY InstypeName,DeptName



CREATE FUNCTION dbo.fn_ReportVisits (@SvcYear SMALLINT,@FromDtEnter DATETIME,@ToDtEnter DATETIME)
RETURNS TABLE
AS
RETURN
(SELECT COUNT(DISTINCT VoucherNo) AS Visits, IName AS InstypeName, DName AS DeptName FROM [vwChgVisits] INNER JOIN [Vouchersdetail] WITH (NOLOCK) ON
[vwChgVisits].VoucherNo = [Vouchersdetail].Voucher WHERE
DATEPART(yy,[Vouchersdetail].ServiceDate) = @SvcYear AND [vwChgVisits].DateEntered BETWEEN @FromDtEnter
AND DATEADD(dd,1,@ToDtEnter) GROUP BY IName, DName)

CREATE FUNCTION dbo.fn_ReportCharges (@SvcYear SMALLINT,@FromDtEnter DATETIME,@ToDtEnter DATETIME)
RETURNS TABLE
AS
RETURN
(SELECT SUM(Charge) AS TotChg, InstypeName, DeptName FROM [vwChgCharges] WITH (NOLOCK) WHERE
DateEntered BETWEEN @FromDtEnter AND DATEADD(dd,1,@ToDtEnter) AND ProcCode <> '1609' AND
DATEPART(yy,ServiceDate) = @SvcYear
GROUP BY InstypeName, DeptName)

CREATE FUNCTION dbo.fn_ReportAdjusts (@SvcYear SMALLINT,@FromDtEnter DATETIME,@ToDtEnter DATETIME)
RETURNS TABLE
AS
RETURN
(SELECT [vwAdjPmtsReports].InsTypeName AS [InsTypeName], SUM([vwAdjPmtsReports].AdjAmount) AS [TotAdj],
[vwAdjPmtsReports].DeptName AS [DeptName] FROM [vwAdjPmtsReports] WITH (NOLOCK) WHERE
[vwAdjPmtsReports].DateEntered BETWEEN @FromDtEnter AND DATEADD(dd,1,@ToDtEnter)
AND DATEPART(yy,[vwAdjPmtsReports].ServiceDate ) = @SvcYear
GROUP BY [vwAdjPmtsReports].InsTypeName, [vwAdjPmtsReports].DeptName)

GilaMonster
Master Smack Fu Yak Hacker

4507 Posts

Posted - 2012-01-12 : 10:33:57
Switch traceflag 1222 on. That will result in a deadlock graph been written to the error log every time a deadlock occurs. Post the result of that graph here.

DBCC TRACEON(1222,-1)


p.s. Do you know what nolock does? It's not a go-faster switch See - [url]http://sqlblog.com/blogs/andrew_kelly/archive/2009/04/10/how-dirty-are-your-reads.aspx[/url]

--
Gail Shaw
SQL Server MVP
Go to Top of Page

Transact Charlie
Master Smack Fu Yak Hacker

3451 Posts

Posted - 2012-01-12 : 10:55:27
well I can see one aspect that could be causing the reports to be real slow and to cause table scans (and therefore more likely to lock).

All your functions do stuff like this in the where clause:
DATEPART(yy,ServiceDate) = @SvcYear
or DATEPART(yy,[vwAdjPmtsReports].ServiceDate ) = @SvcYear
etc

This can't use any index on the column in question which will lead to scans of the table or CI.

I don't know if it's causing your deadlock issues but it's definitely *not optimal*

Charlie
===============================================================
Msg 3903, Level 16, State 1, Line 1736
The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION
Go to Top of Page

simondeutsch
Aged Yak Warrior

547 Posts

Posted - 2012-01-12 : 11:32:57
quote:
Originally posted by Transact Charlie

well I can see one aspect that could be causing the reports to be real slow and to cause table scans (and therefore more likely to lock).

All your functions do stuff like this in the where clause:
DATEPART(yy,ServiceDate) = @SvcYear
or DATEPART(yy,[vwAdjPmtsReports].ServiceDate ) = @SvcYear
etc

This can't use any index on the column in question which will lead to scans of the table or CI.

I don't know if it's causing your deadlock issues but it's definitely *not optimal*


Would this be better?


CREATE FUNCTION dbo.fn_ReportVisits (@SvcYear SMALLINT,@FromDtEnter DATETIME,@ToDtEnter DATETIME)
RETURNS TABLE
AS
RETURN
(SELECT COUNT(DISTINCT VoucherNo) AS Visits, IName AS InstypeName, DName AS DeptName FROM [vwChgVisits] INNER JOIN [Vouchersdetail] WITH (NOLOCK) ON
[vwChgVisits].VoucherNo = [Vouchersdetail].Voucher WHERE
[Vouchersdetail].ServiceDate BETWEEN CAST('1/1/' + CAST(@SvcYear AS CHAR(4)) AS DATETIME) AND
CAST('12/31/' + CAST(@SvcYear AS CHAR(4)) AS DATETIME) AND [vwChgVisits].DateEntered BETWEEN @FromDtEnter
AND DATEADD(dd,1,@ToDtEnter) GROUP BY IName, DName)

Go to Top of Page

GilaMonster
Master Smack Fu Yak Hacker

4507 Posts

Posted - 2012-01-12 : 11:37:21
Yes, but if I may suggest for correctness...

[Vouchersdetail].ServiceDate >= CAST('1/1/' + CAST(@SvcYear AS CHAR(4)) AS DATETIME) AND [Vouchersdetail].ServiceDate < CAST('1/1/' + CAST((@SvcYear + 1) AS CHAR(4)) AS DATETIME)

With what you had, any time on the 31st Dec after midnight would have been excluded

--
Gail Shaw
SQL Server MVP
Go to Top of Page

simondeutsch
Aged Yak Warrior

547 Posts

Posted - 2012-01-12 : 11:51:21
Thanks, but this particular date column does not include a time, it's always 00:00:00, so adding a day is unnecessary.
Go to Top of Page

simondeutsch
Aged Yak Warrior

547 Posts

Posted - 2012-01-12 : 11:54:05
quote:
Originally posted by GilaMonster

Switch traceflag 1222 on. That will result in a deadlock graph been written to the error log every time a deadlock occurs. Post the result of that graph here.

DBCC TRACEON(1222,-1)


p.s. Do you know what nolock does? It's not a go-faster switch See - [url]http://sqlblog.com/blogs/andrew_kelly/archive/2009/04/10/how-dirty-are-your-reads.aspx[/url]


Thanks. Switched on, will see what happens.

Nolock does result in dirty reads. But since this report is generally not run on up-to-the-last-minute data, dirty reads aren't a problem. What it really needs is to run by reading its data without blocking other processes.
Go to Top of Page

Kristen
Test

22859 Posts

Posted - 2012-01-12 : 12:53:44
"generally not run "

I need a $ for every time I've heard that phrase used in the same sentence as "NOLOCK"

If there were no updates you wouldn't be having the problem with deadlocks ...

The trouble starts when there is a dirty read and then the report is wrong, and then management acts on what they see in the report and all sorts of hell breaks lose (unless its a report ONLY for Database Admins and they know that it was produced with NOLOCK ... that's the only time we ever use NOLOCK here)

For example: add a record that causes an index page to be split - so now you have 50% of the keys in the original page, and 50% [plus 1 extra ] in the newly created index page and in the report query, which you are running at the same time, you may get NONE of the rows from the newly created index page in your report ... or you may get them a second time [from the original fully-filled page, and again from the newly added additional "split" page] which may cause your SQL to break, because of duplicates, or your report totals to be rubbish ...)

Plus if a user ever tells you that the report was wrong you will NEVER be able to reproduce the effect ...

Just use READ_COMMITED_SNAPSHOT instead, it is probably the thing you were looking for when you/your predecessor used NOLOCK - particularly if that was for a version of SQL earlier than SQL 2005 (when READ_COMMITED_SNAPSHOT was first added). Its not 100% identical in behaviour to NOLOCK, so it is possible you will have side effects, but I would guess that most (however many "9's" that is) people will have no problem. Then go take all the NOLOCKs out of ALL of your code ...
Go to Top of Page

GilaMonster
Master Smack Fu Yak Hacker

4507 Posts

Posted - 2012-01-12 : 12:59:29
quote:
Originally posted by simondeutsch


Nolock does result in dirty reads. But since this report is generally not run on up-to-the-last-minute data, dirty reads aren't a problem. What it really needs is to run by reading its data without blocking other processes.


It can result in far more than just dirty reads. Please, take a look at that blog post. Considered snapshot isolation?

--
Gail Shaw
SQL Server MVP
Go to Top of Page
   

- Advertisement -