| Author |
Topic |
|
jfmenard
Starting Member
18 Posts |
Posted - 2005-07-08 : 10:15:14
|
| I use an UpdateDT datetime field on all tables I want to track the last modification date. This field is updated by a trigger in my Sql 2000 database:CREATE TRIGGER Trig_MyTable_Update ON dbo.MyTable FOR INSERT, UPDATEAS IF @@ROWCOUNT=0 RETURNUPDATE U SET UpdateDT = GETDATE()FROM MyTable U INNER JOIN INSERTED I ON U.Id = I.IdThis looked to me as a simple way of accomplishing what I needed. However this trigger is source of deadlocks. Yes, this is the only trigger on that table and it causes deadlocks!Just to be sure, I made a simple .NET application that start 2 threads that each repetedly update the same row in MyTable using this statement:UPDATE MyTable SET Field1 = 123 WHERE Id=1It takes a few seconds and a deadlock appears.My understanding is:- Thread-1: Call Update on a row.- Thread-2: Call Update on same row, but waits that Thread-1 finishes.- Thread-1: Trigger gets called to update same row, waits that Thread-2 finishes.- Sql Server detects deadlock and terminate one of the queries (let say Thread-2).- Thread-2: Aborts and raise an error.- Thread-1: Completes trigger Update statement.- Thread-1: Commits main Update and terminates.I successfully solved this issue by using a pair of INSTEAD OF triggers like this (only UPDATE shown below):CREATE TRIGGER Trig_MyTable_Update ON dbo.MyTable INSTEAD OF UPDATEAS IF @@ROWCOUNT=0 RETURNUPDATE U SET U.Field1 = I.Field1,U.Field2 = I.Field2,U.Field3 = I.Field3,U.Field4 = I.Field4,U.Field5 = I.Field5,U.Field6 = I.Field6,U.Field7 = I.Field7,U.Field8 = I.Field8,U.Field9 = I.Field9,U.UpdateDT = GETDATE()FROM MyTable U INNER JOIN INSERTED I ON U.Id = I.IdBut this becomes just too complicated for a simple timestamping thing:- Other (AFTER) triggers will think that all columns are updated when querying the UPDATED(field) function.- If we change the table structure, we must not forget to update the INSTEAD OF trigger which complicates maintenance.Is there something I missed?Thanks,J-F |
|
|
Kristen
Test
22859 Posts |
Posted - 2005-07-08 : 13:15:03
|
| UPDATE U SET UpdateDT = GETDATE()FROM MyTable U INNER JOIN INSERTED I ON U.Id = I.IdWe do this all the time and I've never had a deadlock with it - I doubt we've been lucky, but in our case simultaneous updating of the SAME record from 2 workstations is very unlikely ... except that we probably have some large UPDATE statements and thus the trigger will be processing lots of records, and then one or more of those is quite likely to be the subject of a simultaneous update / trigger.I appreciate that's no help, but it might suggest that it is something special about your circumstances, rather than the code per-se.Kristen |
 |
|
|
KLang23
Posting Yak Master
115 Posts |
Posted - 2005-07-08 : 14:18:07
|
| Hi,See if you can capure the deadlock with Profiler, and find which objects are involved (ie: index or data). You may be surprised.Good luck with this. |
 |
|
|
jfmenard
Starting Member
18 Posts |
Posted - 2005-07-08 : 16:00:22
|
quote: Originally posted by Kristen UPDATE U SET UpdateDT = GETDATE()FROM MyTable U INNER JOIN INSERTED I ON U.Id = I.IdWe do this all the time and I've never had a deadlock with it - I doubt we've been lucky...
Hi Kristen, I followed KLang23's suggestion and it seems to confirm you where lucky. That's surprising me also because I was doing this all the time. But it's the first time I have a multi-threading system that sollicitate my database extensively.I had a look at the profiler when reproducing the deadlock. Here is what I see:- SPID=57: Reset connection.- SPID=58: Reset connection.- SPID=57: Starting UPDATE MyTable SET Field1 = 123 WHERE Id=1.- SPID=58: Starting UPDATE MyTable SET Field1 = 123 WHERE Id=1.- SPID=57: Exclusive Lock Acquired on MyTable (*A*).- SPID=58: Exclusive Lock Acquire Timeout on MyTable (*B*).- SPID=57: Starting UPDATE U SET UpdateDT = GETDATE() FROM MyTable U INNER JOIN INSERTED I ON U.Id = I.Id (trigger).- SPID=57: Exclusive Lock Acquire Timeout (because of *B*).- SPID=58: Exclusive Lock Acquire Timeout (because of *A*).- Deadlock Chain between SPID=57 and SPID=58Any comments anyone? |
 |
|
|
elwoos
Master Smack Fu Yak Hacker
2052 Posts |
Posted - 2005-07-11 : 03:16:58
|
| I have some very similar triggers that haven't been causing deadlocks, these are on fairly low usage tables though, essentially I would say the situation I have in this regard is very similar to that of Kristen.steveAlright Brain, you don't like me, and I don't like you. But lets just do this, and I can get back to killing you with beer. |
 |
|
|
TG
Master Smack Fu Yak Hacker
6065 Posts |
Posted - 2005-07-11 : 11:12:27
|
I think something else must be going on here. I also set up a small .net test where 3 different threads updated the same record simultaneously 20 times each (60 total updates in less than 200 milliseconds). no deadlocking. Do you create an explicit transaction from the application? Or perhaps your ado.net objects are colliding somehow? I don't know.I think the easiest solution is to use SPs to perform your updates. You could let the SP code handle what your trigger is doing to avoid this problem.Below is my sql code. I'll post the .net test code if you want:drop table junkgocreate table junk (rowid int, threadID int, TotalCallCount int, updatedDate datetime)goinsert junk values (1, 0, 0, getdate())insert junk values (2, 0, 0, getdate())gocreate trigger trJunk on junk for updateasbegin update a set a.updatedDate = getdate() ,a.TotalCallCount = isNull(a.TotalCallCount,0) + 1 from junk a join inserted b on a.rowid = b.rowidendgo--after running the test:select * from junkrowid threadID TotalCallCount updatedDate ----------- ----------- -------------- ------------------------1 0 0 2005-07-11 11:04:28.3702 3 60 2005-07-11 11:04:35.480 Be One with the OptimizerTG |
 |
|
|
Kristen
Test
22859 Posts |
Posted - 2005-07-11 : 12:48:58
|
| Yup, I concur. I'm struggling to see how:UPDATE U SET SomeColumn = 'foo' FROM MyTable U WHERE MyPK = 'BAR'which in turn fires a trigger which doesUPDATE U SET MyUpdateDate = GetDate() FROM MyTable U JOIN inserted i ON U.MyPK = i.MyPKcan trigger a deadlock.Kristen |
 |
|
|
jfmenard
Starting Member
18 Posts |
Posted - 2005-07-11 : 16:16:15
|
| Ok I got news for everyone.Here is the test application I did to reproduce the deadlock constantly (C# .Net Win Form application with sources):[url]http://www.streamload.com/wizik/temp/DbDeadlockTester.zip[/url]Feel free to test this on your database to test your UPDATE triggers (you supply the command to execute and the connection string).I discovered that the problem arise when the clustered index is not the same as the primary key. i.e. I have a large table with an ID (identity) primary key field (where clustered index has been unchecked) and I created a separate clustered index on a datetime field for faster queries. So when the initial UPDATE and the trigger UPDATE where clauses are both NOT on the clustered index, then the deadlock is almost immediate with DbDeadlockTester.That is my conclusion for the moment. Any comments?Thanks,J-F |
 |
|
|
Kristen
Test
22859 Posts |
Posted - 2005-07-11 : 18:42:09
|
| What service pack are you using on SQL 2000 - SP4?Kristen |
 |
|
|
eyechart
Master Smack Fu Yak Hacker
3575 Posts |
Posted - 2005-07-11 : 18:55:10
|
| two suggestions:1. try using a rowlock hint2. Assign the value of getdata() to a variable before your update. Make sure to include a SET NOCOUNT statement if you do this.Are you running SQL2K SP4 by chance?-ec |
 |
|
|
Kristen
Test
22859 Posts |
Posted - 2005-07-12 : 05:30:09
|
"Are you running SQL2K SP4 by chance"hehehehe .... GreatMindItis Kristen |
 |
|
|
jfmenard
Starting Member
18 Posts |
Posted - 2005-07-12 : 09:27:16
|
| No I am using SQL 2000 SP3.Do I understand no one was able to reproduce my experiment? |
 |
|
|
Kristen
Test
22859 Posts |
Posted - 2005-07-12 : 09:51:49
|
| Sorry, don't know anything about your "C# .Net Win Form" combination to be able to run the testKristen |
 |
|
|
jfmenard
Starting Member
18 Posts |
Posted - 2005-07-12 : 10:14:49
|
quote: Originally posted by eyechart two suggestions:1. try using a rowlock hint2. Assign the value of getdata() to a variable before your update. Make sure to include a SET NOCOUNT statement if you do this.
I am astonished!Using the ROWLOCK hint in the trigger did nothing.Using a variable for the GETDATE() in the trigger did nothing.BUT, using SET NOCOUNT ON in the trigger SOLVES THE PROBLEM!Now my trigger does not cause deadlocks anymore:CREATE TRIGGER Trig_Dead_UpdateON dbo.DeadTableFOR INSERT, UPDATEAS SET NOCOUNT ON -- Solves deadlock issuesIF @@ROWCOUNT=0 RETURNUPDATE U SET Updated = GETDATE()FROM DeadTable U INNER JOIN INSERTED I ON U.AutoId = I.AutoId Can someone explain me why? |
 |
|
|
jfmenard
Starting Member
18 Posts |
Posted - 2005-07-12 : 10:19:43
|
quote: Originally posted by Kristen Sorry, don't know anything about your "C# .Net Win Form" combination to be able to run the testKristen
It is very easy. You don't need any programming knowledge. Just unzip the file and launch the \bin\Debug\DbDeadLock.exe. You'll be prompted with a connection string and a command to execute. Press start and it will start the number of threads specified with your command. I included the source just to let know there is nothing malicious in my code.J-F |
 |
|
|
Kristen
Test
22859 Posts |
Posted - 2005-07-12 : 10:23:50
|
| "Can someone explain me why?"Without NOCOUNT SQL would be sending an additional recordset to your application. Possibly your application was not processing it (you would specifically have to iterate all resultsets in order to process it). So maybe that was creating a further timedelay, and that was causing the timeout.Best to have the NOCOUNT stuff at the top of ALL your Sprocs and Triggers.Kristen |
 |
|
|
jfmenard
Starting Member
18 Posts |
Posted - 2005-07-12 : 10:43:54
|
quote: Originally posted by KristenWithout NOCOUNT SQL would be sending an additional recordset to your application. Possibly your application was not processing it (you would specifically have to iterate all resultsets in order to process it). So maybe that was creating a further timedelay, and that was causing the timeout.Best to have the NOCOUNT stuff at the top of ALL your Sprocs and Triggers.
My understanding is NOCOUNT OFF does not produce an additional recordset. It only keep track of modified rows. The only way of returning a recordset to an application is thru a SELECT command. And it is certainly not a question of timing, because my sample application generates more than 1000 commands per second in 3 threads so when letting it run for a minute I assume all timing issues where covered. |
 |
|
|
TG
Master Smack Fu Yak Hacker
6065 Posts |
Posted - 2005-07-12 : 11:15:34
|
| hmmm, I got slightly different results. After running your test code as well as mine, I found that what seems to consistently be the difference is having a nonclustered primary key on the table. If the PK is clustered, no deadlocking. If no PK, no deadlocking. Set nocount made no difference for me. Having a clustered index on a date column didn't matter either for me. I can't explain it. I'll have to do a little more digging.EDIT:I still would suggest that you replace your in-line statements with insert/update SPs. Let the SPs handle the UpdatedDate column and get rid of the trigger.Be One with the OptimizerTG |
 |
|
|
jfmenard
Starting Member
18 Posts |
Posted - 2005-07-12 : 11:40:22
|
quote: Originally posted by TG hmmm, I got slightly different results. After running your test code as well as mine, I found that what seems to consistently be the difference is having a nonclustered primary key on the table. If the PK is clustered, no deadlocking. If no PK, no deadlocking. Set nocount made no difference for me. Having a clustered index on a date column didn't matter either for me. I can't explain it. I'll have to do a little more digging.
Maybe I forgot to re-quote all the steps. The deadlock condition only appear on a table where:- the PK is non clustered- a clustered index is on another field (not the PK, not the updated field)- there is an after update trigger that update a field on the same table and the WHERE is on the PK field (non clustered)- there is no SET NOCOUNT ON statementTHEN:- If I change the PK to clustered => no deadlockor- If I use an INSTEAD OF trigger => no deadlockor- If I change the trigger WHERE to use the clustered index => no deadlockor- If I add SET NOCOUNT ON in the trigger => no deadlockI think I understand the first 3 solutions, but I don't understand the last one. How come adding SET NOCOUNT ON prevents the deadlock? That's my preferred solution for now because it has the less impact on our database design but I want to make sure it solve deadlocks definitively. |
 |
|
|
rockmoose
SQL Natt Alfen
3279 Posts |
Posted - 2005-07-12 : 12:13:12
|
This is from BOL (SET NOCOUNT ON : Processing Results)quote: Each INSERT, UPDATE, and DELETE statement returns a result set containing only the number of rows affected by the modification. This count is made available when application calls SQLRowCount. ODBC 3.x applications must either call SQLRowCount to retrieve the result set or SQLMoreResults to cancel it. When an application executes a batch or stored procedure containing multiple INSERT, UPDATE, or DELETE statements, the result set from each modification statement must be processed using SQLRowCount or cancelled using SQLMoreResults. These counts can be cancelled by including a SET NOCOUNT ON statement in the batch or stored procedure
I am curious about this statement in Your trigger:IF @@ROWCOUNT=0 RETURNCouldn't that reset the @@ROWCOUNT for another thread in the trigger ?quote: This variable is set to 0 by any statement that does not return rows, such as an IF statement
Is this something that people usually do in a trigger ?Because I never do it... !So I was wondering, if it has any efect on things.rockmoose |
 |
|
|
TG
Master Smack Fu Yak Hacker
6065 Posts |
Posted - 2005-07-12 : 12:50:43
|
| >>IF @@ROWCOUNT=0 RETURNI don't do that in a trigger either. As long as its the first statement in the trigger, I don't think it would hurt anything. I just don't think its necessary.Like I said earlier, the only conditions I needed to cause the dealock were:1. nonclustered PK2. (regular) trigger for UPDATE or INSERT,UPDATE with an update to the trigger's table.if those conditions are met then the "set nocount on" does NOT solve the problem.Be One with the OptimizerTG |
 |
|
|
Next Page
|