BizTalk SQL Adapter Advice Requested
So I may be demonstrating my SQL Server ignorance, but I need some advice from you all.
Let's say I have the scenario I've drawn out below:
That is, I have the SQL adapter running on two BizTalk Servers, each executing a stored procedure every 2 seconds. That procedure needs to return them the TOP50 rows in a defined table. The problem that two of my customers have bumped into, and to which I don't feel I've given a rock-star answer for, centers around making sure that one SQL adapter doesn't pull the same data that the other adapter has already pulled. Now, of course you have to change a flag after you've read the data, to make sure it doesn't get polled again, but let's say that the adapters get into a cycle where one polls for data less than a second after another. If the first adapter hasn't switched the flag yet, the second adapter may pull that data again.
One possible answer is to create a transaction in the stored procedure which contains both the SELECT and UPDATE commands. However, this could potentially introduce table locking issues and prevent additional data from being inserted into said table. So, I'm not in love with that solution.
So for those of you who are either (a) SQL gurus, or (b) successful implementers of this scenario, what thoughts do you have? Maybe I'm just brain-cramping for the holidays, but I still think this is a useful discussion.
Comments
- Anonymous
November 22, 2005
Without being a sql adapter guru, my question would be: why have the two servers poll the same data in the same database?
If both BTS servers are part of the same biztalk group, I would then prefer to separate the receive side to a single server and maybe let both servers process the data then in orchestrations or whatever (this can be done fairly simply by creating a new host with a single host instance and then binding the sql adapter and your receive location to the new host). - Anonymous
November 22, 2005
Ahh, concurrency lives. I don't think your getting around this without some sort of locking. - Anonymous
November 22, 2005
The comment has been removed - Anonymous
November 22, 2005
Here's some snippet. May be overkill, but it'll do the job. It's a bit convoluted to overcome fact that "begin transaction" by itself doesn't prevent "select" from returning same row to multiple sessions at same time ...
create table dbo.Test (
[id] int identity(1,1) not null,
status char(1) not null,
value float not null,
[bit] bit not null default 0
)
go
insert into dbo.Test ( status, value )
values ( 'N', rand() );
insert into dbo.Test ( status, value )
values ( 'N', rand() );
insert into dbo.Test ( status, value )
values ( 'N', rand() );
insert into dbo.Test ( status, value )
values ( 'N', rand() );
insert into dbo.Test ( status, value )
values ( 'N', rand() );
go
create procedure dbo.usp_NextTest
as begin
declare @table table (
[id] int not null )
begin transaction
update dbo.Test
set status = 'I', [bit] = 1
where [id] in (
select top 2 [id] from dbo.Test
where status = 'N' )
insert into @table
select [id] from dbo.Test
where [bit] = 1;
update dbo.Test
set [bit] = 0;
commit work
select [id], status, value from dbo.Test
where [id] in
( select [id] from @table )
for xml auto
end;
You could get away w/ just 'I'/'N' if you could be absolutely certain that there were no 'N' rows when you started.
By using / updating [bit] column to 1 then back to 0 in transaction, only current session "sees" the 1's.
Anyway, maybe there's a better way, but this is what I did and it seems to behave. - Anonymous
November 22, 2005
How about simply having a control table. Part of the stored procedure that retrieves the data looks in the control table to see if anything is working on the data at present. If so, then ignore this time. When the other proc has finished working with the data, update the control table. - Anonymous
November 22, 2005
The comment has been removed - Anonymous
November 28, 2005
One SQL solution would be to have a "BatchId" column. Then your stored procedure first sets BatchId to a pre-defined Batch or a RAND for the new rows then SELECTS * where BatchId=theBatch. This way any row will be assigned to a Batch and each Batch is processed completely by a single Adapter transaction.
This also gives you the tracability you want in a reliable message model as well. - Anonymous
November 28, 2005
Good call Kevin. That's not bad. I've also received some feedback offline about alternate ways to handle it. Specifically, using a "control" table that the stored proc accesses to check whether another SQL adapter is currently processing records, and if so, skips its turn. However, I think I like your batch ID more. - Anonymous
November 29, 2005
Well how about this one
declare @currentguid uniqueidentifier
set @currentguid = newid()
begin tran
set rowcount 50
update testpickup set processed = @currentguid where processed is NULL
set rowcount 0
select * from testpickup where processed = @currentguid
commit tran
Make your processed field a uniqueidentifier. - Anonymous
February 01, 2006
Hi, the way I solved this and implemented it on a number of projects was to initiate the call to the SQL store proc from inside the orchestration rather than depend on the adaptor auto update interval. I used a task scheduler (similar to the one available on gotdonet) to start this orchestration, then the orchestration will make the call through a sql port to a store proc, then have another orchestration that subscribes to the message type correlating to a single record. This worked perfectly everytime; don't have any concurrency issues, and you have your scalability. Hope this helps - Anonymous
August 28, 2006
There are many typical scenarios in which Service Broker is the ideal solution for interactions between - Anonymous
August 29, 2006
Here's a query that's worked for us @ eBI Solutions...
-- CREATE AN IN-MEMORY TEMPORARY TABLE
DECLARE @tmpTableOfKeyFields TABLE
(
record_id int
)
-- PUT THE RECORDS THAT WE'RE GOING TO PROCESS INTO A TEMPORARY TABLE
INSERT INTO @tmpTableOfKeyFields
SELECT
record_id
FROM Table1
WHERE status = 'READY'
-- RETURN THE RESULT SET
SELECT
record_id,
status,
data
FROM Table1 WITH (ROWLOCK)
WHERE record_id in
(
SELECT DISTINCT record_id FROM @tmpTableOfKeyFields
)
FOR XML AUTO, ELEMENTS
-- UPDATE THE RECORD SET TO PENDING
UPDATE Table1
SET status = 'PEND',
modified = getdate()
FROM Table1
INNER JOIN @tmpTableOfKeyFields TableOfKeyFields ON Table1.record_id = TableOfKeyFields.record_id
Lucas and I just demo'd this on today's BizTalk MVP Webcast. ;)
Jay Lee
eBI Solutions, LLC - Anonymous
August 30, 2006
Ladies and gentlemen, Jay Lee. Thanks Jay for the comment. - Anonymous
June 16, 2009
PingBack from http://topalternativedating.info/story.php?id=8218