Reputation: 15032
I havent found info on this subject yet, but maybe someone have tried doing this earlier. Imagine a heavily loaded web application (.NET/SQL Server 2008). The application serves clients that first request 'resources' and after a while 'submit' them. The resources are limited, so there is high risk of concurrent database access by several clients. The aim is to optimize the "request resource" mechanism in such way so that even when concurrent database access occurs the clients won't request overlapping database records and therefore the operation will be done as parallel as it is possible...
Conceptual database structure:
CREATE TABLE [dbo].[Resources] (
[ID] [uniqueidentifier] NOT NULL,
[Data] [xml] NOT NULL,
[Lock] [uniqueidentifier] NULL,
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[ResourceRequests] (
[ID] [uniqueidentifier] NOT NULL,
[ResourceID] [uniqueidentifier] NOT NULL,
[DateCreated] [datetime] NOT NULL,
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[ResourceSubmits](
[ResourceRequestID] [uniqueidentifier] NOT NULL,
[NewData] [xml] NOT NULL,
) ON [PRIMARY]
GO
To request data, first I need to "reserve" it using "UPDATE" statement, this is the point of synchronization, because updates are executed sequentially
UPDATE T
SET T.Lock = (@lockID)
FROM
(
SELECT TOP (@lockCount) R.*
FROM [dbo].[Resources] AS R
WHERE R.Lock IS NULL
) AS T
(I'm not sure but...) As far as I know "UPDATE" operations use internal database locks and therefore it is not possible for two or more concurrent "UPDATE" operations will change the same record when using above query.
But when concurrent access occurs due to fixed order of "SELECT" only one of the clients will perform UPDATE at a time:
This is the result of concurrent execution, done by the following C#/Linq2Sql code:
Thread[] threads = new Thread[20];
for (int threadIndex = 0; threadIndex < 20; threadIndex++)
{
threads[threadIndex] = new Thread
(
new ThreadStart
(
delegate
{
using (var context = new L2S.l2sDataContext())
{
for (int i = 0; i < 20; i++)
{
context.LockRoots(Guid.NewGuid(), 5);
}
}
}
)
);
}
foreach (var thread in threads)
{
thread.Start();
}
foreach (var thread in threads)
{
thread.Join();
}
As you can see the locks are distributed sequentially the order of Lock column values is not mixed, this means that the update operations were executed sequentially.
So the question, how to execute them on parallel and would it change something (except performance) if the "UPDATE" query will be changed the following way:
UPDATE T
SET T.Lock = (@lockID)
FROM
(
SELECT TOP (@lockCount) R.*
FROM [dbo].[Resources] AS R
WHERE R.Lock IS NULL
ORDER BY NEWID() <--------------- The order is random now, but will the updates get executed concurrently?
) AS T
Upvotes: 1
Views: 223
Reputation: 239794
You should be able to run your query as:
UPDATE top (@lockCount) T
SET Lock = @lockID
FROM
[dbo].[Resources] T WITH (READPAST)
WHERE Lock IS NULL
Which will cause the UPDATE to skip rows which are already locked for update whilst searching for @lockCount rows for itself to update. Note that due to the nature of the way this works, the locks will still appear to be allocated in blocks of @lockCount
at a time, unless, possibly, there are a lot of transactions in flight simultaneously. In fact, I'm struggling to think of a way to demo to your satisfaction that this is about as concurrent as you'll be able to get, but it is.
More on READPAST
Upvotes: 1