Использование базы данных для отслеживания блокировки / разблокировки объектов

8

У меня есть требование отслеживать действия по блокировке / разблокировке объектов. Перед выполнением какого-либо действия над объектом (контрактом, партнером и т. Д.) Генерируется lockсобытие. После того, как действие закончено, оно выпускает unlockсобытие.

Я хочу получить те объекты, которые заблокированы, но еще не разблокированы. Цель состоит в том, чтобы сделать запрос быстрым и избежать тупиков.

Ниже таблица

create table locks (
    id int identity,
    name varchar(255),
    lock int
) 

insert into locks values('a', 1)
insert into locks values('b', 1)

insert into locks values('c', 1)
insert into locks values('d', 1)

insert into locks values('a', 0)
insert into locks values('c', 0)


insert into locks values('a', 1)
insert into locks values('b', 1)

Я использую запрос ниже, чтобы возразить еще не разблокированные объекты:

select distinct m.name from locks m
    where (select COUNT(id) from locks locked
           where locked.lock = 1 and locked.name = m.name)
        >  (select COUNT(id) from locks unlocked
            where unlocked.lock = 0 and unlocked.name = m.name)

Работает правильно и результат a, bи d.

Мои вопросы: - Достаточно ли моего решения, чтобы избежать тупиков? Может ли возникнуть какая-либо проблема, если INSERTво время выполнения запроса их много? - Есть ли у вас другой (лучший) способ решить эту проблему?

ОБНОВИТЬ

Я прошу прощения за то, что не поставил контекст в вопрос. Приведенный выше дизайн базы данных не предназначен для замены блокировки базы данных.

У нас есть внешняя система, которую мы называем из нашей системы. Это требует вызова lockи unlockметода в их системах перед каждым действием, выполняемым над объектом (может быть контракт или партнер).

В последнее время у нас возникают ситуации, когда происходит сбой сервера, и мы должны перезапустить его. К сожалению, запущенные процессы, которые уже вызывали lock, не имели возможности вызвать unlockдля освобождения объектов, что привело к ряду других проблем, когда наша система снова подключается к внешней.

Поэтому мы хотим предоставить возможность отслеживать каждый lockзвонок. После перезапуска сервера мы вызовем unlockобъекты, которые ранее были заблокированы.

Спасибо Ремусу Русану за то, что он указал, что мой вопрос использует прототип DDL. Это первый раз, когда я опубликовал вопрос о администраторе БД, и я прошу прощения за то, что не прочитал FAQ.

Спасибо

Genzer
источник

Ответы:

11

Цель состоит в том, чтобы сделать запрос быстрым и избежать тупиков.

Это нереальная цель. Взаимные блокировки определяются приложением, получающим блокировки, и не контролируют то, как вы реализуете блокировку. Лучшее, на что вы можете надеяться, - это обнаружить тупики.

Реализация блокировок как записи проблематична. Строки сохраняются, и вы будете пропускать блокировки при сбое приложения, даже если реализация будет идеальной . Делать замки с помощью апплоков . Они имеют четкую транзакционную семантику, и механизм обнаруживает взаимные блокировки.

Однако, глядя на то, что вы пытаетесь достичь, вряд ли вам нужны блокировки вообще . Вы описываете очередь (выберите следующий элемент, доступный для обработки и избегайте конфликтов => очередь, а не блокировка). Читайте Использование таблиц в качестве очередей .

Что касается вашей конкретной реализации (используя таблицу, которая хранит историю блокировок), я должен быть честным: это катастрофа. Начать с разработки таблицы совершенно недостаточно для предполагаемого использования: вы делаете запрос по имени, но таблица представляет собой кучу без индексов. У него есть столбец идентификации без видимой причины. Вы можете ответить, что это просто таблица «псевдокода», но это DBA.SE, вы не публикуете здесь неполный DDL!

Но что более важно, реализация не реализует блокировку! Ничто не мешает двум пользователям «заблокировать» один и тот же объект дважды. Ваша «блокировка» полностью зависит от того, как вызывающие абоненты магически ведут себя правильно. Даже лучшее написанное приложение не может использовать эту блокировку, потому что нет способа проверить и получить блокировку атомарно . Два пользователя могут проверить, сделать вывод, что «а» разблокирована, и одновременно вставить ('a', 1)запись. По крайней мере, вам понадобится уникальное ограничение. Что, конечно, нарушило бы семантику «посчитайте блокировки против разблокировок, чтобы определить статус».

Извините, но это реализация класса F.

ОБНОВИТЬ

Поэтому мы хотим предоставить возможность отслеживать каждый вызов блокировки. После перезапуска сервера мы будем вызывать unlock для ранее заблокированных объектов.

Если вы не участвуете в двухфазной фиксации распределенной транзакции с удаленной системой, все, что вы можете сделать, - это «лучшее усилие», потому что между написанием «unlock» и фактическим вызовом «unlock» в сторонней системе существует множество условий. , Как лучшее усилие, вот моя рекомендация:

  • создайте простую таблицу для отслеживания блокировок:

    CREATE TABLE locks (name VARCHAR(255) NOT NULL PRIMARY KEY);

  • перед вызовом lockвставьте замок в таблицу и зафиксируйте .

  • после вызова unlock удалите блокировку из таблицы и подтвердите
  • при запуске системы посмотрите на таблицу. любая строка, в которой есть блокировка, оставшаяся после предыдущего запуска, должна быть разблокирована. Вызовите unlockкаждую строку, затем удалите строку. Только после того, как все ожидающие блокировки были «разблокированы», вы можете возобновить нормальную работу приложения.

Я предлагаю это, потому что стол останется маленьким. В любое время он содержит только текущие активные блокировки. Это не будет расти до тошноты и вызвать проблемы позже из-за огромного размера. Тривиально, чтобы увидеть, что заблокировано.

Конечно, эта реализация не предлагает историю аудита того, кто и когда был заблокирован. Вы можете добавить это, если необходимо, в виде другой таблицы, в которую вы вставляете только события (блокировка или разблокировка) и никогда не запрашиваете это, чтобы выяснить «осиротевшие» блокировки.

Вы все еще должны быть готовы к вызовам «unlock» для сбоя во время запуска, потому что вы не можете гарантировать, что ваша система не зависала после вызова, unlockно перед удалением строки (другими словами, ваша таблица и сторонняя система разошлись и имеют разные версии правды). Опять же, вы не можете предотвратить это без распределенных транзакций, и я бы никогда не советовал для DTC.

Ремус Русану
источник
4

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

Взаимные блокировки могут возникать по нескольким причинам, поэтому я предлагаю вам изучить эти причины, прежде чем создавать собственную систему блокировки. Вы можете получить графики взаимоблокировок из сеанса XE работоспособности системы и начать их анализ.

Обычно взаимоблокировки являются результатом взятия блокировок на объектах в разных порядках, поэтому вы должны стараться делать блокировки всегда в одном и том же порядке. Существуют и другие причины возникновения взаимоблокировок, но общее руководство заключается в том, что короткие транзакции удерживают блокировки в течение короткого промежутка времени, поэтому настройка запросов с более качественным кодом, индексами и стратегиями «разделяй и властвуй», вероятно, будет лучшим способом избавиться от них. тупиков.

Взаимные блокировки типа «читатели, блокирующие записи» могут быть сильно смягчены путем переключения базы данных на Read Committed Snapshot Isolation . Если ваше приложение было построено с учетом пессимистической блокировки, вам следует тщательно проверить и протестировать все, прежде чем активировать эту опцию в своей базе данных.

В случае, если вы настаиваете на переходе по маршруту «пользовательской системы блокировки», по крайней мере, используйте что-то, что гарантирует освобождение блокировок в случае, если что-то с приложением пойдет не так. Возможно, вы захотите изучить встроенную хранимую процедуру sp_getapplock , которая делает что-то похожее на то, что вам кажется после.

ОБНОВЛЕНИЕ: после прочтения вашего отредактированного вопроса, это альтернативный способ выразить тот же запрос:

SELECT *
FROM (
    SELECT *, RN = ROW_NUMBER() OVER(PARTITION BY name ORDER BY id DESC) 
    FROM locks
) AS data
WHERE RN = 1 
    AND lock = 1;

Это сработает, если объектам будет разрешено блокироваться только один раз, что, в любом случае, является главной целью системы блокировки.

spaghettidba
источник