Запретить запросу возвращать одну и ту же строку в разные потоки - PullRequest
0 голосов
/ 01 февраля 2020

У меня есть служба веб-API, которая сопоставляет пользователей в соответствии с некоторыми условиями.

Пользователь не должен получать более одного совпадения за указанный интервал.

Для создания сопоставления я использую SQL запрос (база данных SQL Сервер):

SELECT TOP 1 
FROM table_name
WHERE last_match >= DATEADD(hour, -3, GETDATE()) and **some_other_conditions** 
ORDER BY last_match desc;

Поскольку существует несколько вызовов этой службы some_other_conditions может быть одинаковым для разных пользователей и единственное, что будет отличать результат в столбце last_match.

Проблема в том, что пока пользователь A выполняет этот вызов, столбец last_match еще не обновлен, и поэтому другие пользователи могут получить такое же совпадение.

Как можно Я избегаю или решаю эту проблему?

1 Ответ

1 голос
/ 01 февраля 2020

Вы, вероятно, можете сделать это на SQL Сервере, хотя база данных определенно не лучшее место для реализации таких вещей. Требуется немного изменить запрос, добавив несколько подсказок, таких как:

SELECT TOP 1 *
FROM table_name with (xlock, holdlock, rowlock, readpast)
WHERE last_match >= DATEADD(hour, -3, GETDATE()) and **some_other_conditions** 
ORDER BY last_match desc;

Первые 3 подсказки гарантируют, что строка, выбранная одним соединением, останется недоступной для других, пока его транзакция не будет зафиксирована. Подсказка readpast позволяет запросу пропускать строки, заблокированные другими транзакциями, и продолжать идти в поисках подходящего соответствия, не дожидаясь снятия блокировок.

Пара предостережений, которые вам нужно имейте в виду:

  1. Вся единица работы должна быть выполнена в рамках одной транзакции на стороне клиента с использованием одного и того же соединения с базой данных. Убедитесь, что у вас не более одного соединения на один поток приложения, и что соединения не распределяются между потоками;
  2. Подсказка readpast позволяет пропускать блокировки на уровне строки, но не пропускает страницу замки. Вам нужно будет как-то убедиться, что на эту таблицу не будут наложены никакие блокировки страниц, не говоря уже о блокировках разделов или таблиц.
  3. Если у вас есть какое-то количество одновременных соединений, о которых стоит упомянуть, вы можете легко исчерпать буфер блокировки , который не настраивается в SQL Server. К счастью, компонент Database Engine может динамически наращивать его при необходимости, но вы все равно можете столкнуться с неожиданными ситуациями, когда чрезмерная конкуренция за блокировку приведет к заметному снижению производительности.
  4. Если в вашей базе данных включен RCSI, вам необходимо добавить readcommittedlock подсказка к вашему запросу. Без него блокировки ничего бы не значили, так как другие соединения смогут читать «предыдущие версии» выбранных совпадений (которые в этом случае будут иметь те же данные, что и строка, выбранная другим потоком).

Если вы собираетесь использовать этот подход, обязательно выполните полномасштабное стресс-тестирование, прежде чем запускать его в производство.

...