Господа, доброго времени суток.
Есть некий ком-объект, написанный на VB.NET, с которым работает некоторое приложение (это приложение с другими объектами не работает, так что только ком )
И есть некий сервер который добавляет в базу задание на обработку этим комом
Каждый экземпляр кома запускается отдельно с каждым приложением (заранее узнать сколько объектов будет запущено не возможно).
Все объекты работают независимо и о существовании друг от друга ничего не знают.
В каждом коме включен таймер, который мониторит БД с целью получения нового задания на работу. Как только задание получено, выставляется флаг "в работе".
Вопрос в том как сделать семафор чтобы комы не ломились в БД и не получали задания одновременно.
Сначало была идея сделать семафорный файл, но решили отказаться — не очень стабильно работает
Сейчас это реализованное таким образом:
В базе есть таблица с одним полем — IsBusy. Ком обращается к БД, смотрит это поле и если оно равно 0, то выставляет его в 1, получает задание, выставляет флаг "в работе" и возвращает поле IsBusy в 0.
Если IsBusy равно 1, то объект засыпает на пол минуты и снова запрашивает БД — это сделано в цикле.
Существуют ли ещё алгоритмы как это реализовать?
Флаг в БД тоже не очень нравится, т.к. может возникнуть ситуация когда 2 (или более) объекта обратятся к БД и получат одно и тоже задание.
02.04.08 14:21: Перенесено модератором из 'Алгоритмы' — надеюсь, это более подходящее место — Кодт
Здравствуйте, lord0n, Вы писали:
L>Существуют ли ещё алгоритмы как это реализовать? L>Флаг в БД тоже не очень нравится, т.к. может возникнуть ситуация когда 2 (или более) объекта обратятся к БД и получат одно и тоже задание.
можно извратиться и сделать "транспорт", который один только может работать с бд.. все запросы будет кидать в очередь, а коллизии решать критическими секциями.. И еще можно создать 2 таблицы: не обработанные данные и обработанные..
я бы сделал как то так
Здравствуйте, neFormal, Вы писали:
F>И еще можно создать 2 таблицы: не обработанные данные и обработанные.. F>я бы сделал как то так
Логично, но все равно может возникнуть ситуация с получением одного задания двумя объектами.
Я имею в виду ситуацию когда SQL сервер загружен и не может быстро обработать запросы
В идеале, я думаю, это должно быть что-то вроде глобальной переменной. Но как это сделать не понятно.
но опять же придется писать некий транспорт и жутко извращаться
Этого делать не хочется.
Есть ещё один путь — блокировки в БД. Но тут надо подумать как при запросе, заблокировать только одну запись, а не все который выдаст запрос
Так что рассматриваются ещё варианты.
<>
Может быть, взять нормальную транзакционную СУБД? В которой транзакция "выбрать свободное задание и пометить его как занятое" выполняется прямо сервером?
Специалисты по ...SQL подскажут, как именно это пишется.
Здравствуйте, Кодт, Вы писали:
К>Может быть, взять нормальную транзакционную СУБД? В которой транзакция "выбрать свободное задание и пометить его как занятое" выполняется прямо сервером? К>Специалисты по ...SQL подскажут, как именно это пишется. К>Кстати, а какую СУБД вы используете?
+1000
lord0n, Вы не думали, что во всех многопользовательских системах, работающих с БД, как-то же решается эта проблема? Так что не надо изобретать велосипеды, а пользоваться блокировками СУБД. К примеру, в случае Oracle почитайте про SELECT ... FOR UPDATE — и решение концептуально прояснится.
СУБД какая?
Для Oracle есть например dbms_lock, который позволяет создавать пользовательские блокировки.
Для MSSQL есть sp_getapplock/sp_releaseapplock.
Где-то на rsdn видел кажется статью на эту тему.
lord0n wrote:
> Существуют ли ещё алгоритмы как это реализовать? > Флаг в БД тоже не очень нравится, т.к. может возникнуть ситуация когда 2 > (или более) объекта обратятся к БД и получат одно и тоже задание.
Можно делать примерно так:
UPDATE Job SET isBusy=1 WHERE isBusy=0 AND...;
//check if @@ROWCOUNT>0, значит Job наш
@@ROWCOUNT — database-specific, хотя во всех БД, которые мне известны — есть.
Posted via RSDN NNTP Server 2.1 beta
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, Lloyd, Вы писали:
L>Здравствуйте, ., Вы писали:
.>>Можно делать примерно так: .>>
.>>UPDATE Job SET isBusy=1 WHERE isBusy=0 AND...;
.>>//check if @@ROWCOUNT>0, значит Job наш
.>>
L>и получить блокировку.
Всем спасибо за ответы.
На сколько мне известно блокировки в БД охватывают весь запрос
То есть если я напишу
select * from QUEUE where InWork = 0 and Result is null
то у меня заблокируется вся очередь, а не её первый элемент (который мне и нужен)
мне нужно какимто образом получить первый элемент и заблокировать его на то время пока я с ним работаю
как это сделать я не знаю
база sql server 2000
Lloyd wrote:
> .>UPDATE Job SET isBusy=1 WHERE isBusy=0 AND...; > .>//check if @@ROWCOUNT>0, значит Job наш > .> > и получить блокировку.
Эээ.. Где? Не понял.
Posted via RSDN NNTP Server 2.1 beta
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
lord0n пишет:
> На сколько мне известно блокировки в БД охватывают весь запрос
Это — ошибочное мнение.
> То есть если я напишу > > select * from QUEUE where InWork = 0 and Result is null > то у меня заблокируется вся очередь, а не её первый элемент (который мне > и нужен)
Нет, неверно. Может заблокироваться либо строка, либо страница,
либо вся таблица. Конкретика зависит от многих-многих вещей.
> мне нужно какимто образом получить первый элемент и заблокировать его на > то время пока я с ним работаю > как это сделать я не знаю
begin transaction
select * from QUEUE where <нужная строка> for update
-- или, если БД не поддерживает SELECT..FOR UPDATE
update QUEUE set some_field = some_field where <Нужная строка>
Lloyd wrote:
>> > и получить блокировку. > .>Эээ.. Где? Не понял. > Вот этот update и будет блокировать.
Так понятно, без блокировок в принципе невозможно. Но он будет блокировать только на момент работы самого update, транзакция будет тут же завершаться, а результат @@ROWCOUNT можно неспешно проверять потом, после снятия блокировок. В решении предложенным тобой, блокировка жить будет дольше.
Posted via RSDN NNTP Server 2.1 beta
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
-- job has the folllwing statuses: 0 - ready for processing, 1 - in processing, 2 - completed, 3 - cancelled
-- get next available jobbegin transaction
set @JobID = null
select top 1 @JobID = JobID from Jobs with(TABLOCKX)where Status = 0 order by something
if @JobID is not null begin
update Jobs set Status = 1 where JobID = @JobID
end
commit
Главная вещь здесь with(TABLOCKX). К сожалению помогает только полный лок таблицы. В остальных случаях SQL дает спокойно выбирать одну и туже задачу из конкурентных коннектов.
-- mark job as completeupdate Jobs set Status = 1 where JobID = @JobID
gwg-605 пишет: > -- job has the folllwing statuses: 0 — ready for processing, 1 — in processing, 2 — completed, 3 — cancelled > -- get next available job > begin transaction > set @JobID = null > select top 1 @JobID = JobID from Jobs *with(TABLOCKX)* where Status = 0 order by something > if @JobID is not null begin > update Jobs set Status = 1 where JobID = @JobID > end > commit > > > > Главная вещь здесь *with(TABLOCKX)*. К сожалению помогает только полный > лок таблицы. В остальных случаях SQL дает спокойно выбирать одну и туже > задачу из конкурентных коннектов.
Это очень много.
здесь почти наверняка вовсе не нужно блокировать всю таблицу, достаточно одного
фиктивного (или реального) UPDATE или SELECT ... FOR UPDATE на одну конкретную
запись этой таблицы (но транзакцию не отпускать до нужного времени).
А то, что вы говорите здесь, что якобы необходим with(TABLOCKX), проистекает
от того, что у вас наверняка влючена поддержка новомодного snapshot isolation
на MSSQL. Поэтому читателям дадут ПРОЧИТАТЬ старую версию этой таблицы из
того заветного рукова, где чародей-MSSQL хранит старые версии записей активных
транзакций. А вот дать ИЗМЕНИТЬ, т.е. сделать UPDATE или SELECT ... FOR UPDATE
(или SELECT with UPDLOCK ) ДРУГОЙ ПАРАЛЛЕЛЬНОЙ ТРАНЗАКЦИИ MSSQL не даст,
пока вы не закончите вашу эту семафорную транзакци. И произойдет это даже
на уровне изоляции 0 (также известном, как dirty read), а вовсе не на
уровне 3 ( serializable ), частичным аналогом которого является
select ... with(TABLOCKX)
Но даже если бы это было так, и нужен был бы TABLOCKX (это я так, для общего
развития, для других похожих вариантов), то лучше было бы все-таки
указать уровень изоляции транзакции 3 (для всей транзакции или
только для этой таблицы внутри этой транзакции), чем тупо блокировать таблицу
под себя.
Дело в том, что даже на SERIALIZABLE СУБД может позволить каким-то транзакциям
выполняться параллельно, если они не мешаю друг другу. TABLOCKX же запрещает
всем другим вообще ЛЮБУЮ деятельность с этой таблицей.
Но правда несмотря на всю мою уверенность в своих словах я все же рекомендую
тщательно проверить все на практике, это всегда хорошо, а в таких случаях
необходимо вообще всегда.
Здравствуйте, MasterZiv, Вы писали:
MZ>Это очень много. MZ>здесь почти наверняка вовсе не нужно блокировать всю таблицу, достаточно одного MZ>фиктивного (или реального) UPDATE или SELECT ... FOR UPDATE на одну конкретную MZ>запись этой таблицы (но транзакцию не отпускать до нужного времени).
Это было моей первой мыслью при имплементации этой задачи, но к сожалению она не сработала ни в MS SQL 2000 ни в MS SQL 2005. Я вставлял wait в несколько секунд между select-ом и update-ом, и запускал процедуру из двух сессий, результат выборка двух одинаковых айдишек
MZ>А то, что вы говорите здесь, что якобы необходим with(TABLOCKX), проистекает MZ>от того, что у вас наверняка влючена поддержка новомодного snapshot isolation MZ>на MSSQL.
Нет не включена. Также на SQL 2000 такой поддержки нету.
MZ>Но даже если бы это было так, и нужен был бы TABLOCKX (это я так, для общего MZ>развития, для других похожих вариантов), то лучше было бы все-таки MZ>указать уровень изоляции транзакции 3 (для всей транзакции или MZ>только для этой таблицы внутри этой транзакции), чем тупо блокировать таблицу MZ>под себя.
Пробовал, менять уровень изоляции, не помогло. Первая версия как раз и была поднимал уровень изоляции до 3-х, делал select for update, после чего update, результат — выбирается одна и таже айдишка.
MZ>Дело в том, что даже на SERIALIZABLE СУБД может позволить каким-то транзакциям MZ>выполняться параллельно, если они не мешаю друг другу. TABLOCKX же запрещает MZ>всем другим вообще ЛЮБУЮ деятельность с этой таблицей.
Это как раз понятно. Кстати даже попытка лочить не всю талицу, а только отдельную запись или страницу тоже не помогло
MZ>Но правда несмотря на всю мою уверенность в своих словах я все же рекомендую MZ>тщательно проверить все на практике, это всегда хорошо, а в таких случаях MZ>необходимо вообще всегда.
я добрался до with(TABLOCKX), после многих проверок различных вариантов.
Здравствуйте, gwg-605, Вы писали:
G6>Пробовал, менять уровень изоляции, не помогло. Первая версия как раз и была поднимал уровень изоляции до 3-х, делал select for update, после чего update, результат — выбирается одна и таже айдишка.
Как-то ты не так значит пробовал:
Вот эти два скрипта позволят прарллельно из одной таблицы выбирать разные задания нескольким активным процессам.
для 2000
-- Не забудь построить уникальный индекс по JobID, а то не заработает, будет блокироваться все равно вся таблица.
--DECLARE @JobID int
set @JobID = null
begin transaction
select top 1 @JobID = JobID from Jobs with(UPDLOCK, READPAST) where Status = 0 order by JobId
if @JobID is not null begin
update Jobs set Status = 1 where JobID = @JobID
end
commit
для 2005
-- и здесь про индекс тоже не забудь
--UPDATE TOP(1) Jobs WITH (READPAST) set Status = 1
OUTPUT INSERTED.JobID
WHERE Status = 0
G6>Кстати даже попытка лочить не всю талицу, а только отдельную запись или страницу тоже не помогло
Он это автоматом сделает, не надо туда лезть. Надо только индексы правильно построить.
gwg-605 пишет: > Это было моей первой мыслью при имплементации этой задачи, но к > сожалению она не сработала ни в MS SQL 2000 ни в MS SQL 2005. Я вставлял > wait в несколько секунд между select-ом и update-ом, и запускал > процедуру из двух сессий, результат выборка двух одинаковых айдишек
так просто не бывает. Вы что-то не так значит делали.
Вы транзакцию-то открытой оставляли ?
gwg-605 wrote:
> Главная вещь здесь *with(TABLOCKX)*. К сожалению помогает только полный > лок таблицы. В остальных случаях SQL дает спокойно выбирать одну и туже > задачу из конкурентных коннектов.
А чем мой вариант
set @JobID = null
select top 1 @JobID = JobID from Jobs where Status = 0 order by something
if @JobID is not null begin
begin transaction
UPDATE Job SET Status = 1 WHERE Status = 0 AND JobID=@JobID;
commit
if @@ROWCOUNT=0 begin
set @JobID = null-- кто-то выхватил задание из под носа, что ж остались без работы.end
end
И всю эту обвязку вовсе не обязательно писать на tsql, можно звать запросы из клиентского кода, без всяких долгоживущих транзакций.
Posted via RSDN NNTP Server 2.1 beta
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, yogi, Вы писали:
Y>lord0n, Вы не думали, что во всех многопользовательских системах, работающих с БД, как-то же решается эта проблема? Так что не надо изобретать велосипеды, а пользоваться блокировками СУБД. К примеру, в случае Oracle почитайте про SELECT ... FOR UPDATE — и решение концептуально прояснится.
Вообще, если используется ORACLE, то логичнее задания складывать в очереди AQ