March 31, 2004 at 12:26 pm
Hi,
I am having the following issue:
I have a table that has a list of customers (millions) that need contacting. Agents for my comany need to access this table, get a customer record, and call that customer. The agents get the customer record they need to call from a VB application that executes a stored procedure at the database server. The stored procedure does the following:
1) begins a transaction
2) selects the appropriate customer record to call
3) updates a flag in the customer record so they won't be contacted again
4) transaction is complete
5) return customer id so the agent can call this customer
the problem is is that the same customer id is getting returned if two agents happen to execute the stored procedure at the same time. I can run two instances of the stored proc debugger see what is happening. The select statements are both executed, then one does the update of the flag, and the other does the update again once the first one finishes the transaction. Customers are getting called twice.
The question is: How can I guarantee that an agent will get an unique customer id. My thought was a COM component that is single threaded.
Thanks for any thoughts on this.
- Ken Otto
here is a copy of the actual stored proc in case it helps...
-------------------------------------------
CREATE procedure usp_getnewrecord
@agent varchar(15)
as
declare @lowZone int, @highZone int
SET NOCOUNT ON
select @lowZone = 1
select @highZone = 1
declare @shippernumber varchar(6), @datestamp datetime, @shipperid int, @dailyticketid int
Select @datestamp = getdate()
Begin Transaction
select top 1 @dailyticketid = d.dailyticketid, @shipperid = d.shipperid, @shippernumber = d.shippernumber
from
dailyticket d with (rowlock)
--dailyticket d with (holdlock)
join
shipper s
on
d.shippernumber = s.shippernumber
where
s.zone <= @highZone
and
d.activeflag = 1
order by packagecount desc
update
dailyticket with (rowlock)
--dailyticket with (holdlock)
set
activeflag = 0
where
dailyticketid = @dailyticketid
Commit Transaction
update shipper
set lastupdate = @datestamp
where shipperid = @shipperid
declare @ticketid int
select
@ticketid =
(
select
ticketid
from
ticket
where
shippernumber = @shippernumber
and
status = 1
 
if
(@ticketid is null and @ShipperNumber IS NOT NULL)
begin
insert into
ticket
(
shippernumber,
status,
statusdate
 
values
(
@shippernumber,
1,
getdate()
 
select @ticketid = @@identity
end
select
(
select
shipperid
from
shipper
where
shippernumber = @shippernumber
  as 'shipperid',
@ticketid as 'ticketid'
SET NOCOUNT OFF
GO
March 31, 2004 at 1:30 pm
Have a look at
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/tsqlref/ts_set-set_74bw.asp
Choose an isolation level such as read uncommitted and that should allow the second query to read the updated flag whilst the first transaction is still waiting to be committed.
Also have a look at using scope_identity() instead of @@identity and add some error checking to rollback if there are problems ...
cheers
dbgeezer
March 31, 2004 at 4:52 pm
Steve,
Thanks for the reply. I see what you mean, however, there is still a window of opportunity for process #1 and process #2 to end up with the same record. For example, assume process #1 and process #2 both enter the stored procedure at almost the same time. They both execute the select query and get a record ID back (the same record ID). At this point, neither process #1 or process #2 has done the update to set the flag. Now, assume process #1 "wins" and the flag is set. Process #2 has already checked the flag during the initial select statement, so now it is just waiting to set the flag. Process #2 never knew it "lost", so it carries on processing the record. You might be saying "what are the chances of both processes hitting at that moment", but believe me, it's happening quite often.
Also, thanks for the scope_identity() tip! I am implementing it right away.
Cheers - Ken
April 1, 2004 at 6:05 am
You need a sequential aproach:
because you start a transaction and do all the processing in the same transaction, the flag is being set at the end of your process...(commit)
other calls start on the same record & have this chance since your entire process is waiting to release the update (after commit)
do an update to the flag first & commit, then continue your transaction, making sure your next reads are checking this flag prior to starting the update again, since it's already flagged and now viewable within the next process call.
Hope this helps!
Coach James
April 1, 2004 at 7:26 am
spot on
glad to be of help
cheers
dbgeezer
April 1, 2004 at 10:32 am
What you can do is add an extra column to that table of datatype timestamp.
Whenever you read that row, collect the timestamp data aswell .
Then when you update the row use a where condition where "timestamp column = timestamp value".
Then you can use the row count to check the condition. If the rowcount =0 then that row or a customer record is already picked up by some agent. So you can skip calling the customer.
if rowcount = 1 then agent can place the call to the customer.
Hope this helps
April 1, 2004 at 4:14 pm
sivakumar,
That was exactly what I was looking for! Works like a champ. Thanks to you and to all the others who replied to this thread, I was actually able to use bits from every post to improve my stored procedure (and knowledge of SQL Server). Cheers - Ken
Viewing 7 posts - 1 through 6 (of 6 total)
You must be logged in to reply to this topic. Login to reply