Блокировка строки в режиме совместного использования не помогает в том, чтобы несколько потоков не могли прочитать одну и ту же строку. Вы хотите заблокировать строку только с подсказкой XLOCK
. Также вы используете маркер очень низкой точности, определяющий строки-кандидаты (GETDATE
имеет точность 3 мс), поэтому вы получите много ложных срабатываний. Вы должны использовать точное поле, например, бит (processing
0 или 1).
В конечном итоге вы воспринимаете LoginsInfo
как очередь, поэтому я предлагаю вам прочитать Использование таблиц в качестве очередей . Способ достижения того, что вы хотите, это использовать UPDATE ... WITH OUTPUT
. Но у вас есть дополнительное требование, чтобы выбрать случайный логин, который выбрасывает все, что не получилось. Вы действительно на 100% уверены, что вам нужна случайность? Это чрезвычайно необычное требование, и вам будет трудно найти решение, которое будет правильным и эффективным. Вы получите дубликаты, и вы будете в тупике до следующего дня.
Первая попытка будет выглядеть примерно так:
with cte as (
select top 1 ...
from [LoginInfos] with (readpast)
where processing = 0 and ...
order by newid())
update cte
set processing = 1
output cte...
Но поскольку для заказа NEWID
требуется полное сканирование таблицы и сортировка для выбора 1 строки счастливчика, вы будете 1) крайне неработоспособным и 2) постоянно тупиком.
Теперь вы можете взять эту случайную дискуссию на форуме, но так получилось, что я уже несколько лет работаю с очередями, поддерживаемыми SQL Server, и я знаю то, что вы хотите, не будет работать. Вы должны изменить свое требование, в частности случайность, и затем вы можете вернуться к статье, указанной выше, и использовать одну из проверенных и проверенных схем.
Редактировать
Если вам не нужен случайный случай, тогда как-то проще. Суть проблемы таблиц как очередей в том, что вы должны искать выходную строку, вы абсолютно не можете сканировать для нее. Сканирование по очереди не только не выполняется, но и является гарантированной тупиковой ситуацией из-за способа использования очередей (высококонкурентные операции удаления очереди, когда все потоки хотят строку с одинаковой ). Для этого ваше предложение WHERE должно быть доступным для sarg, что зависит от 1) ваших выражений в предложении WHERE и 2) ключа кластеризованного индекса. Ваше выражение не может содержать OR
условий, поэтому потеряйте все IS NULL OR ...
, измените поля так, чтобы они не обнулялись, и всегда заполняйте их. Во-вторых, вы должны сравнивать индекс по-дружески, не DATEDIFF(..., field, ...) < @variable)
, а вместо этого всегда использовать field < DATEDIDD (..., @variable, ...)
, потому что вторая форма поддерживает SARG. И вы должны согласиться на одно из двух полей, [Timestamp]
или [UpdateDate]
, вы не можете искать на обоих. Все это, конечно, требует гораздо более строгого и жесткого конечного автомата в вашем приложении, но это хорошо, что слабые условия и операторы OR являются лишь признаком плохого ввода данных.
select @now = getdate();
select @expired = dateadd(second, @now, @processTimeout);
with cte as (
select *
from [MyDb].[dbo].[LoginInfos] WITH (readpast, xlock)
WHERE
[Type] = @type) AND
[Timestamp] < @expired)
update cte
set [Timestamp] = @now
output INSERTED.*;
Чтобы это работало, кластеризованный индекс таблицы должен быть на ([Type], [Timestamp])
(что подразумевает превращение первичного ключа LoginInfoId
в некластеризованный индекс).