Molloch
Molloch

Reputation: 2381

Trigger on Audit Table failing due to update conflict

I have a number of tables that get updated through my app which return a lot of data or are difficult to query for changes. To get around this problem, I have created a "LastUpdated" table with a single row and have a trigger on these complex tables which just sets GetDate() against the appropriate column in the LastUpdated table:

CREATE TRIGGER [dbo].[trg_ListItem_LastUpdated] ON [dbo].[tblListItem] 
FOR INSERT, UPDATE, DELETE 
AS
UPDATE LastUpdated SET ListItems = GetDate()
GO

This way, the clients only have to query this table for the last updated value and then can decided whether or not they need to refresh their data from the complex tables. The complex tables are using snapshot isolation to prevent dirty reads.

In busy systems, around once a day we are getting errors writing or updating data in the complex tables due to update conflicts in "LastUpdated". Because this occurs in the statement executed by the trigger, the affected complex table fails to save data. The following error is logged:

Snapshot isolation transaction aborted due to update conflict. You cannot use snapshot isolation to access table 'dbo.tblLastUpdated' directly or indirectly in database 'devDB' to update, delete, or insert the row that has been modified or deleted by another transaction. Retry the transaction or change the isolation level for the update/delete statement.

What should I be doing here in the trigger to prevent this failure? Can I use some kind of query hints on the trigger to avoid this - or can I just ignore errors in the trigger? Updating the data in LastUpdated is not critical, but saving the data correctly into the complex tables is.

This is probably something very simple that I have overlooked or am not aware of. As always, thanks for any info.

Upvotes: 4

Views: 1903

Answers (4)

coge.soft
coge.soft

Reputation: 1674

The schema needs to change. If you have to keep your update table, make a row for every table. That would greatly reduce your locks because each table could update their very own row and not competing for the sole row in a table.

LastUpdated

table_name (varchar(whatever)) pk
modified_date (datetime)

New Trigger for tblListItem

CREATE TRIGGER [dbo].[trg_ListItem_LastUpdated] ON [dbo].[tblListItem] 
FOR INSERT, UPDATE, DELETE 
AS
UPDATE LastUpdated SET modified_date = GetDate() WHERE table_name = 'tblListItem'
GO

Another option that I use a lot is having a modified_date column in every table. Then people know exactly which records to update/insert to sync with your data rather than dropping and reloading everything in the table each time one record changes or is inserted.

Upvotes: 1

muhmud
muhmud

Reputation: 4604

I would say that you should look into using Change Tracking (http://msdn.microsoft.com/en-gb/library/cc280462%28v=sql.100%29.aspx), which is lightweight builtin SQL Server functionality that you can use to monitor the fact that a table has changed, as opposed to logging each individual change (which you can also do with Change Data Capture). It needs Snapshot Isolation, which you are already using.

Because your trigger is running in your parent transaction, and your snapshot has become out of date, your whole transaction would need to start again. If this is a complex workload, maintaining this last updated data in this way would be costly.

Upvotes: 3

Zo Has
Zo Has

Reputation: 13038

Alternatively, you can update the log table inside the same transaction which you use to update your complex tables inside your application & avoid the trigger altogether.

Update You can also opt for inserting a new row instead of updating the same row in LastUpdated table. You can then query max timestamp for latest update. However, with this approach your LastUpdated table would grow each day which you need to take care of if volume of transactions is high.

Upvotes: 0

nad2000
nad2000

Reputation: 4945

Short answer - don't do that! Making the updated transactions dependent on one single shared row makes it prone to deadlocks and and update conflicts whole gammut of nasty things.

You can either use views to determine last update, e.g.:

SELECT
    t.name
    ,user_seeks
    ,user_scans
    ,user_lookups
    ,user_updates
    ,last_user_seek
    ,last_user_scan
    ,last_user_lookup
    ,last_user_update
FROM sys.dm_db_index_usage_stats i JOIN sys.tables t 
    ON (t.object_id = i.object_id)
WHERE database_id = db_id()

Or, if you really insist on the solution with LastUpdate, you can implement it's update from the trigger in an autonomous transactions. Even though SQL Server doesn't support autonomous transactions, it could done using liked servers: How to create an autonomous transaction in SQL Server 2008

Upvotes: 1

Related Questions