The quick answer is that you have two options to fix this problem:
- Fix your stored procedure so that is behaves correctly in concurrent situations.
- Place your SQL polling receive handler within a clustered BizTalk host.
Below is an explaination of what is going on, and under that details of implementations to fix the issue:
Explaination
This is due to the way BizTalk receive locations work when running on multiple host instances (that is, that the receive handler for the adapter specified in the receive location is running on a host that has multiple host instances).
In this situation both of the host instances will run their receive handler.
This is usually not a problem - most of the receive adapters can manage this and give you the behaviour you would expect. For example, the file adapter places a lock on files while they are being read, preventing double reads.
The main place where this is a problem is exactly what you are seeing - when a polling SQL receive location is hitting a stored procedure. In this case BizTalk has no option other than to trust the SQL procedure to give the correct results.
It is hard to tell without seeing your procedure, but the way you are querying your records is not guaranteeing unique reads.
Perhaps you have something like:
Select * From Record
Where Status = 'Unread'
Update Record
Set Status = 'Read'
Where Status = 'Unread'
The above procedure can give duplicate records because between the select and the update, another call of the select is able to sneak in and select the records that have not been updated yet.
Implementing a solution
Fixing the procedure
One simple fix to the procedure is to update with a unique id first:
Update Record
Set UpdateId = @@SPID, Status = 'Reading'
Where Status = 'Unread'
Select * From Record
Where UpdateId = @@SPID
And Status = 'Reading'
Update Record
Set Status = 'Read'
Where UpdateId = @@SPID
And Status = 'Reading'
@@SPID should be unique, but if it proves not to be you could use newid()
Using a clustered host
Within the BizTalk server admin console when creating a new host it is possible to specify that that host is clustered. Details on doing this are in this post by Kent Weare.
Essentially you create a host as normal, with host instances on each server, then right click the host and select cluster.
You then create a SQL receive handler for the polling that works under that host and use this handler in your receive location.
A BizTalk clustered host ensures that all items that are members of that host will run on one and only one host instance at a time. This will include your SQL receive location, so you will not have any chance of race conditions when calling your procedure.