views:

505

answers:

2

I have 2 batch programs, 1 is a program that sends email and another one sends fax. They both access a table named QUEUE.

In the email sender program, this is what happens in relation to QUEUE. For each record in QUEUE that satisfies criteria:

  1. Locks record 1 in QUEUE table:
    select 1 from QUEUE with (UPDLOCK) where id = 1
  2. Process sending out of email
  3. Delete record 1 in QUEUE table:
    delete from QUEUE where id = 1
  4. commit transaction (transaction is not auto-commit)

In the fax sender program, similar steps also happen, except that in step 2, we send out fax (of course).

The problem is that sometimes the delete from QUEUE throws out an exception that it is locked. Thereby, re-sending of the emails/fax happens. I am sure that the group of records processed by these programs don't intersect.

It seems that delete tries to get an Update (U) lock on other records in the table although only one record is to be deleted. So exception happens when other transactions have locks on other records in that same table.

With that, I need to know if there's an option to make "delete" operation not acquire locks on records other than for the record it will delete. Because it seems that the problem is with the "delete" operation taking locks on other records in the table.

By the way, here are some information about the database (I'm not sure if they will help):

  • Read Committed Snapshot is turned on
  • Snapshot Isolation State is on
A: 

Sometimes a lock escalades from locking a single row into locking a section of a table or locking an entire table. That is most likely why you are getting locks on records that you are not actually using in the transaction.

Instead of using a transaction to lock the records, you could use a status field to flag the records that are processed.

Example:

Lock some records, then get the record that was successfully locked:

update queue set status = 'email_processing' where status is null and id = 1
select email, message from queue where status = 'email_processing'

When you are not explicitly using a transaction each query runs in it's own transaction, so the update query can safely change the status as it's verifying the current status in the same query.

When done sending, delete the records:

delete from queue where status = 'email_processing'

The fax sender would of course use a different status (like 'fax_processing') so that the flagged records are isolated.

Guffa
With the suggestion, what I worry for is if I encounter locking error during the time I'm deleting. Even if I remove the locks, I am still prone to encountering locking error during delete (it locks other records). What I'm thinking is on that part is make code try to delete until successful.
rizza
Yes, you could do that. However without a transaction you are not very likely to experience locks. The database will normally just wait for one update/delete to complete before running the next. It's mostly when you have a lengthy process in a transaction or a deadlock situation that it's a problem.
Guffa
Actually what we experience is lock timeout period exceeded and deadlock. Those are the two errors that I have found in our error log. Thanks for the suggestion though I think Nick's suggestion might work (though I use XLOCK instead of UPDLOCK).
rizza
Yes, those are the errors that you get when transactions conflict. With a different type or transaction you might reduce the number of conflicts, but you will most likely still have them as long as you are using the transactions.
Guffa
+2  A: 

Have you tried using the WITH ROWLOCK or WITH NOLOCK hint on the delete statement?

Have you read this article? it suggests you use (UPDLOCK, READPAST) to prevent the issue your encountering

Nick Kavadias
do you mean on the delete or the select statement (I have no update statement in my pseudo-code)?
rizza
I think using XLOCK in the select and XLOCK, READPAST on the delete might work (tested it, the delete is not blocked by the locking on other tables and the select statement locks the record against other statement trying to lock the record. Thanks a lot for your suggestion.
rizza