Модераторы: Akina
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Как обеспечить конкурентную безопасность? 
:(
    Опции темы
infarch
Дата 4.3.2015, 11:09 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 526
Регистрация: 13.3.2009

Репутация: нет
Всего: 1



Здравствуйте.

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

Напимер таблица Task:
Id
Status
Data

Клиент обращается к ней и берет первую запись со статусом 1, меняет его на 2 и обрабатывает данные. Но ведь есть опасность что другой клиент в то самое время сделает свой запрос и получит ту самую строку. Как избежать такой ситуации? В моем случае абсолютно недопустимо отдавать одну строку на обработку два раза.
PM MAIL   Вверх
Akina
Дата 4.3.2015, 11:35 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Советчик
****


Профиль
Группа: Модератор
Сообщений: 20570
Регистрация: 8.4.2004
Где: Зеленоград

Репутация: 25
Всего: 453



Цитата(infarch @  4.3.2015,  12:09 Найти цитируемый пост)
Как избежать такой ситуации?

Ну например так.

В изменённом статусе фиксируется не (только) текущий статус, а (в том числе) уникальный идентификатор клиента. Как вариант - в специальном поле. Например:
Код

UPDATE table
SET status = 2, clientID = 123456
FROM (SELECT TOP 1 ID
      FROM table 
      WHERE status = 1 AND cliendID IS NULL) t1
WHERE table.ID = t1.ID

Сразу же проверяем, что существует запись с clientID = 123456. Если да - то спокойно обрабатываем эту запись (причём тупо фильтруем её по clientID), а по завершении обработки опять обNULLяем поле clientID, если нет - значит, кто-то успел раньше, и пробуем зарезервить за собой другую запись, повторно выполняя запрос.

При таком подходе всегда можно получить сведения о текущей обработке - какие записи, кем обрабатываются, на какой стадии процесс. И даже при слёте обработки можно перезапустить клиента, который, обнаружив "свою" запись, продолжит обработку в соответствии с текущим статусом. Если же обнаружена совсем залипшая недообработанная запись - можно передать её на дообработку другому клиенту, просто изменив clientID. Только убедиться, что этот клиент в данный момент ничего не обрабатывает. Например, выполняя изменение clientID с опцией TABLELOCK.


--------------------
 О(б)суждение моих действий - в соответствующей теме, пожалуйста. Или в РМ. И высшая инстанция - Администрация форума.

PM MAIL WWW ICQ Jabber   Вверх
infarch
Дата 4.3.2015, 13:05 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 526
Регистрация: 13.3.2009

Репутация: нет
Всего: 1



Akina, спасибо за подсказку. Чтобы подвести итог, вот так мои клиенты будут получать данные для обработки: 

Код

UPDATE table
SET status = 2, clientID = 123456
FROM (SELECT TOP 1 ID
      FROM table 
      WHERE status = 1 AND cliendID IS NULL) t1
WHERE table.ID = t1.ID

SELECT * FROM table WHERE status=2 AND clientID=123456


Достаточно ли этого для уверенности в уникальности данных? Надо ли указать уровень изоляции транзакции?
PM MAIL   Вверх
Akina
Дата 4.3.2015, 15:38 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Советчик
****


Профиль
Группа: Модератор
Сообщений: 20570
Регистрация: 8.4.2004
Где: Зеленоград

Репутация: 25
Всего: 453



ПРЕДВАРИТЕЛЬНО Вы делаете запрос 
Код

SELECT COUNT(1) FROM table WHERE clientID=123456

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

Если запрос вернул ноль - то всё нормально, можно резервировать запись и работать с ней. 

В последнем запросе условие status=2 лишнее - потому как при нормальном течении процесса, и с учётом предварительной проверки, запись с clientID=123456 строго одна. Или, если при апдейте произошло какая-то накладка, то ни одной - и тогда надо повторно резервировать запись.


--------------------
 О(б)суждение моих действий - в соответствующей теме, пожалуйста. Или в РМ. И высшая инстанция - Администрация форума.

PM MAIL WWW ICQ Jabber   Вверх
infarch
Дата 4.3.2015, 16:00 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 526
Регистрация: 13.3.2009

Репутация: нет
Всего: 1



Я понял. Спасибо, буду применять на практике.
PM MAIL   Вверх
Zloxa
Дата 7.3.2015, 13:19 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Чо?
****


Профиль
Группа: Завсегдатай
Сообщений: 3470
Регистрация: 12.9.2008

Репутация: 10
Всего: 161



Цитата(infarch @  4.3.2015,  12:09 Найти цитируемый пост)
берет первую запись со статусом 1, меняет его на 2 и обрабатывает данные. Но ведь есть опасность что другой клиент в то самое время сделает свой запрос и получит ту самую строку.  

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

В вашем случае, следует использовать режим изоляции не  ниже read commited. Захват задачи считать успешным, если для нее успешно отработал update set status =2 where status = 1. Два разных клиента, работающих в режиме изоляции read commited выполнить такую операцию над одной строкой не смогут. 

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

Это сообщение отредактировал(а) Zloxa - 7.3.2015, 13:24


--------------------
Достоверно известно, что 89% людей доверяют статистике взятой с потолка smile
PM   Вверх
Akina
Дата 7.3.2015, 23:42 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Советчик
****


Профиль
Группа: Модератор
Сообщений: 20570
Регистрация: 8.4.2004
Где: Зеленоград

Репутация: 25
Всего: 453



Цитата(Zloxa @ 7.3.2015,  14:19)
Захват задачи считать успешным, если для нее успешно отработал update set status =2 where status = 1. Два разных клиента, работающих в режиме изоляции read commited выполнить такую операцию над одной строкой не смогут. 

Это да, но всё равно ему нужно поле номера обработчика. Иначе как определить, кто именно захватил запись - ты или конкурент? Или потребуется два запроса.


--------------------
 О(б)суждение моих действий - в соответствующей теме, пожалуйста. Или в РМ. И высшая инстанция - Администрация форума.

PM MAIL WWW ICQ Jabber   Вверх
Zloxa
Дата 8.3.2015, 01:34 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Чо?
****


Профиль
Группа: Завсегдатай
Сообщений: 3470
Регистрация: 12.9.2008

Репутация: 10
Всего: 161



МС разве не умеет возвращать rowcount у апдейта? smile
Если rowcount >0 значит захватил я. Нет?

Добавлено @ 01:38
Посмотрел. Умеет https://msdn.microsoft.com/ru-ru/library/ms187316.aspx
Добавлено @ 01:38
Вычитал ещё и по аутпут, здесь тоже будет полезен https://msdn.microsoft.com/ru-ru/library/ms177564.aspx

Это сообщение отредактировал(а) Zloxa - 8.3.2015, 02:08


--------------------
Достоверно известно, что 89% людей доверяют статистике взятой с потолка smile
PM   Вверх
Zloxa
Дата 8.3.2015, 01:52 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Чо?
****


Профиль
Группа: Завсегдатай
Сообщений: 3470
Регистрация: 12.9.2008

Репутация: 10
Всего: 161



А в чем есть проблема, так в том, что тут конкурирующие процессы будут выстраивается в очередь. И если обработка реализована в той же транзакции, что и захват, парллелить обработку не получится. Как реализовать на МС "Возьми из очереди первый никем пока не блокированый" я не знаю и, всамделе, очень хотел бы узнать.


--------------------
Достоверно известно, что 89% людей доверяют статистике взятой с потолка smile
PM   Вверх
infarch
Дата 30.3.2015, 16:11 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 526
Регистрация: 13.3.2009

Репутация: нет
Всего: 1



Хочу написать о результатах тестирования. Вот запрос:

Код

use test;
declare @StartTime datetime = '2015-03-13 13:20';
declare @MinutesToAdd int = 1;
while 1=1
begin
    waitfor time @StartTime;
    
    UPDATE ServerTaskRequest
    SET [Status] = 2, ServerTaskManager_ID = 1
    --output deleted.id INTO @MyTableVar
    FROM (SELECT TOP 1 ID
          FROM ServerTaskRequest 
          WHERE [Status] = 1 AND ServerTaskManager_ID IS NULL) t1
    WHERE ServerTaskRequest.ID = t1.ID;

    set @StartTime=DATEADD(minute, @MinutesToAdd, @StartTime);
end


Я запустил его в десяти окнах SSMS и стал следить. На первой же итерации несколько окон выдали ошибку:

Transaction (Process ID 59) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

Пробовал заворачивать update в тарнзакцию, указывал разные уровни изоляции. Ошибка по прежнему случалась. Единственный момент отклонения от этой грустной системы был в том, что при изоляции SNAPSHOT ошибка вышла другая:

Snapshot isolation transaction aborted due to update conflict. You cannot use snapshot isolation to access table 'dbo.ServerTaskRequest' directly or indirectly in database 'Test' to update, delete, or insert the row that has been modified or deleted by another transaction. Retry the transaction or change the isolation level for the update/delete statement.

Потом я запрос упростил:

Код

use test;
declare @taken table(ID int not null);
DECLARE @StartTime dateTIME = '2015-03-13 13:56';
DECLARE @MinutesToAdd INT = 1;
while 1=1
begin
    waitfor time @StartTime;

    UPDATE top(1) ServerTaskRequest
        SET [Status] = 2, ServerTaskManager_ID = 2
        output deleted.id INTO @taken
        WHERE [Status] = 1 AND ServerTaskManager_ID IS NULL;

    set @StartTime=DATEADD(minute, @MinutesToAdd, @StartTime);
end


В таком виде проблем не наблюдается, однако мне будет спокойнее если общественность выскажет своё "фи" )
PM MAIL   Вверх
Zloxa
Дата 31.3.2015, 01:15 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Чо?
****


Профиль
Группа: Завсегдатай
Сообщений: 3470
Регистрация: 12.9.2008

Репутация: 10
Всего: 161



Цитата(infarch @  30.3.2015,  17:11 Найти цитируемый пост)
You cannot use snapshot isolation to access table 'dbo.ServerTaskRequest' directly or indirectly in database 'Test' to update, delete, or insert the row that has been modified or deleted by another transaction.

Это капец.  Реализация write consistency через анальный запрет конкурентной записи. smile 
Цитата(infarch @  30.3.2015,  17:11 Найти цитируемый пост)
top(1)

Топ 1 без ордербая означает любой 


--------------------
Достоверно известно, что 89% людей доверяют статистике взятой с потолка smile
PM   Вверх
infarch
Дата 31.3.2015, 11:34 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 526
Регистрация: 13.3.2009

Репутация: нет
Всего: 1



Цитата(Zloxa @  31.3.2015,  01:15 Найти цитируемый пост)
Топ 1 без ордербая означает любой  

Мне любой вполне подходит, главное чтоб только один и согласно where. Да и не такой уж он и любой... Если ордера нет то выдаст первую запись согласно очередности добавления в базу. Или нет?..
PM MAIL   Вверх
Zloxa
Дата 31.3.2015, 14:28 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Чо?
****


Профиль
Группа: Завсегдатай
Сообщений: 3470
Регистрация: 12.9.2008

Репутация: 10
Всего: 161



Цитата(infarch @  31.3.2015,  12:34 Найти цитируемый пост)
Если ордера нет то выдаст первую запись согласно очередности добавления в базу. Или нет?.. 

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

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

Повторюсь, я не знаю как такие вещи делать на MS SQL, потому и слежу за этой темой - интересно. 

В лоб бы я ее решал - открыаем однопроходный курсор (можно грязными чтениями) и обходя его в цикле до первого успешного апдейта (в режиме read commited и обязательно с доп условием по статусу). При этом я врядли избежал бы толкотни на блокировках.

Дедлоки из первого примера меня очень огорчили. Ну и особенности реализации снапшота тоже. Таки писать под MS это садамаза, хоть он и куда няшее оракли.... с первого взгляду.


Это сообщение отредактировал(а) Zloxa - 31.3.2015, 14:36


--------------------
Достоверно известно, что 89% людей доверяют статистике взятой с потолка smile
PM   Вверх
Zloxa
Дата 31.3.2015, 14:53 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Чо?
****


Профиль
Группа: Завсегдатай
Сообщений: 3470
Регистрация: 12.9.2008

Репутация: 10
Всего: 161



Не выходят у меня из головы эти дедлоки.

Код

    UPDATE ServerTaskRequest
    SET [Status] = 2, ServerTaskManager_ID = 1
    --output deleted.id INTO @MyTableVar
    FROM (SELECT TOP 1 ID
          FROM ServerTaskRequest 
          WHERE [Status] = 1 AND ServerTaskManager_ID IS NULL) t1
    WHERE ServerTaskRequest.ID = t1.ID;

Как я понимаю как это происходит:
Сессия 1, выполняя select подзапрос накладывает shared лок по предикату [Status] = 1
Сессия 2, выполняя select подзапрос накладывает shared лок по предикату [Status] = 1
Сессия 1, приступая к апдейту пытается произвести захват update лока по Status, блокируется сессией 2, удерживающей шаред лок
Сессия 2,  приступая к апдейту пытается произвести захват update лока по Status, блокируется сессией 1, удерживающий шаред лок, и, в свою очередь блокированной сессией 2 - дедлок

Ребят, кто знает - правильно я полагаю? Правильно поинмаю как обеспечивает изоляцию MS? Он же блокирует по предикату?

Ессли я правильно поинмаю, выкрутиться тут можно - перевести подзапрос в грязные чтения (хинт nolock) и where апдейта дописать and status = 1. Врде б должно попустить.

Это сообщение отредактировал(а) Zloxa - 31.3.2015, 14:57


--------------------
Достоверно известно, что 89% людей доверяют статистике взятой с потолка smile
PM   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "MS SQL"
Akina

Akina

Запрещается!

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

  • Действия модераторов можно обсудить здесь
  • С просьбами о написании курсовой, реферата и т.п. обращаться сюда
  • Вопросы составления неспецифических запросов рассматриваются здесь
  • Используйте теги [code=sql][/code] для подсветки кода. Используйтe чекбокс "транслит" (возле кнопок кодов) если у Вас нет русских шрифтов.

Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Zloxa, Akina.

 
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | MS SQL Server | Следующая тема »


 




[ Время генерации скрипта: 0.2133 ]   [ Использовано запросов: 21 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.