[rdbms] isolation / locking
> строчки в таблице field для doc_id. Это плохо.
alter table field add constraint doc_id_unique unique(doc_id)
---
LART: Luser Attitude Readjustment Tool.
Something large, heavy, and painful.
В реальной задаче есть такое понятие как "списковое поле", то есть полей у документа много, но появление дублей все равно не приветствуется.
alter table field add constraint doc_id_unique unique(doc_id)fail
читать надо все-таки вдумчиво, а не поверхностно (в тексте явно написано, что с одним doc_id может быть несколько значений)
вот так еще можно попробовать:
alter table field add constraint doc_id_unique unique(doc_id, value_data)
но это опять же это получится, только если совсем никогда не нужны дубли.
придумывай пример.
---
...Я работаю...
селект в 1 строчке меняем на delete всех полей у документа
> явно написано, что с одним doc_id может быть несколько значений)
В тексте явно написано, что две записи с одним doc_id --- плохо.
---
"Vyroba umelych lidi, slecno, je tovarni tajemstvi."
У нас здесь даже про таблицу ничего не известно, только что там
есть два поля, причём "id" у тебя почему-то не является ключом.
---
"Vyroba umelych lidi, slecno, je tovarni tajemstvi."
Я прошу не покритиковать архитектуру моей системы, в ней есть куча мест, от которых я в ужасе. Достаточно упомянуть, что это франкенштейн из веб-сервисов, веб-приложений, и pl/sql. Причем преобладает именно pl/sql, и он не обламается дернуть какой-нибудь веб-сервис, или скачать какой-нибудь пдфник по хттп с дайджест авторизацией.
В данном случае у меня есть вполне конкретная проблема, и ее надо так или иначе решить. Менять процесс/архитектуру тут уже поздно.
В двух словах про предметную область: у нас есть документы, у документов есть поля. Мы их храним.
Документы приходят к нам извне, мы их парсим и сохраняем к себе в бд. Извне также приходят изменения документов. В этом случае документ снова распарсивается, и инфа по полям в бд обновляется.
Соответственно, ситуация моделируемая в примере - нам одновременно пришло два обновления одного и того же документа.
В описываемом плохом случае в таблице полей по данному документу все поля будут присутствовать дважды. Для какого-то документа такой набор полей может быть вполне валиден, поэтому жесткими констрейнтами это никак не описать. Но для данного документа связанные поля не будут отражать ни одно состояние, пришедшее к нам из внешней системы.
> инфа будет излишней, особенно полная структура таблички fields.
Ну так никто не просит у тебя это. Тебе сложно написать коротко?
create table f(id integer unique, val integer);
alter table f add constraint f_id_unique unique(id);
insert into f values (1, 2);
insert into f values (1, 3);
Сложно? А про предметную область это, конечно, хорошо, но,
по большому счёту, нафиг не надо.
> В двух словах про предметную область: у нас есть документы, у
> документов есть поля. Мы их храним.
> Документы приходят к нам извне, мы их парсим и сохраняем к себе в бд.
Как вы их храните? Набросай схему базы в этой части.
---
"Vyroba umelych lidi, slecno, je tovarni tajemstvi."
create table DOCS
(
ID CHAR(32) default sys_guid not null,
SRC SYS.XMLTYPE --то что пришло извне
);
alter table DOCS
add constraint PK_DOCS primary key (ID)
using index;
create table FIELDS
(
ID CHAR(32) default sys_guid not null,
ID_DOCS CHAR(32) not null,
VALUE_DATA VARCHAR2(4000
);
alter table FIELDS
add constraint PK_FIELDS primary key (ID)
using index;
alter table FIELDS
add constraint FK_FIELDS foreign key (ID_DOCS)
references DOCS (ID) on delete cascade;
Понятно, над всем этим еще есть уровень метаданных, описывающих какие типы документов бывают, какие у документов бывают поля и где их брать/как их вычислять. Приводить это здесь, думаю, излишне.
alter table fields add constraint fields_attr_unique unique(id, id_docs)
-- у каждого документа может быть только один атрибут с заданным id.
Так тебя устраивает?
---
...Я работаю...
Плюс, акцентирую внимание еще раз на следующем куске текста:
Для какого-то документа такой набор полей может быть вполне валиден, поэтому жесткими констрейнтами это никак не описать.Попытаюсь пояснить. Имеется в виду, что те "плохие" данные, которые получаются после описанной параллельной отработки транзакций могут быть вполне валидны для какого-то другого документа, но не для текущего. Соответственно, так как констрейнты в бд документы различать не могут, то разрулить это констрейнтами в бд не удастся никак.
то ты говоришь, что две строки плохо, то говоришь,
что оно допустимо.
А чем тебя "select ... for update" не устроил?
---
...Я работаю...
Если в документе извне было два поля, то надо чтобы их было два, а не четыре. Если в документе было четыре, то надо чтобы было четыре, а не восемь. Итд.
Селект фор апдейт устраивает всем. Небольшой минус - надо, собственно, править код. Пока таких мест не очень много, соотв это достаточно дешево.
Но вдруг можно обойтись и без этого? То есть, скажем, меня бы больше устроил первый вариант, если бы там не возникало лайвлока, или он бы разруливался системой после одной-двух итераций.
"for update", а ты создавать новые записи собираешься.
Боюсь, тебе надо запирать всю таблицу.
---
...Я работаю...
Нет, "select ... for update" тебя не устраивает, потому что онВот этого я тоже боюсь.
"for update", а ты создавать новые записи собираешься.
На инсерт точно не залочит? Когда я второй раз попытаюсь такой же селект фор апдейт сделать тоже на залочит? Даже в случае Serializable? Все таки range lock и все такое...
Запирать всю таблицу вообще не вариант. В таком случае я будут писать лисопедный лок на документ.
Как он будет работать, если у тебя id всегда новый guid?
> В таком случае я будут писать лисопедный лок на документ.
Делай "select for update" на той таблице, где unique(id_docs).
---
...Я работаю...
> Как он будет работать, если у тебя id всегда новый guid?
Селект фор апдейт всех полей конкретного документа.
>> В таком случае я будут писать лисопедный лок на документ.
> Делай "select for update" на той таблице, где unique(id_docs).
Тоже вариант.
>> Как он будет работать, если у тебя id всегда новый guid?
> Селект фор апдейт всех полей конкретного документа.
Он выберет эти записи и запрёт их, а дальше?
Те записи, которые вставлять будешь, он совсем другие будут.
Или range lock работает на внешних ключах?
---
...Я работаю...
Но у меня ораклов нет (да, мне лень, но оракла всё равно нет,
так что пробуй сам).
---
"...И затем проверять то, чтобы наука у нас
не оставалась мёртвой буквой или модной фразой."
Да, пробовать будем по любому, просто хотелось заранее проконсультироваться =)
сорри, что вмешиваюсь, на разве после того как все разлочится висящий инсерт, который тебе не нужен, не отработает? или я задачу не уловил?
Мое утверждение в исходном посте - инсерт отрабатывает два раза, и это мне не нравится.
Повтори, пожалуйста, свое утверждение в номерах строчек и транзакций, описанных в первом посте =)
... сначала первая транзакция выполнит строку 1, потом вторая строку 1, потом первая строку 2, вторая строку 2 итд.не знаю как в оракле, а в MS SQL Server-е при таком сценарии, имхо, будет дедлок. Выполнив 1-ю строку, обе транзакции поставят rage lock на чтение, и будут мешать друг другу сделать инсерт.
...
2. Serializable isolation, pessimistic locking.
Тут все хорошо. Транзакции в 1 строчке делают range lock, соотв вторая транзакция тут припаузится до коммита первой..
Минусы:это не является конструктивным аргументом
- собсно дорогой сам по себе pessimistic locking
Учитывая то, что современные dbms используют довольно навороченные алгоритмы для разрешения гораздо более сложных дедлоков, то я очень сомневаюсь, что транзакции в моем примере безысходно повиснут где бы то ни было.
Кому как. Мне, если я пойду этим путем, надо будет перемеривать производительность системы в целом, и с большой вероятностью полностью отказываться от этого способа. Для меня это существенно.
Ммм. Честно говоря, я тебя не понял.
Мое утверждение в исходном посте - инсерт отрабатывает два раза, и это мне не нравится.
Повтори, пожалуйста, свое утверждение в номерах строчек и транзакций, описанных в первом посте =)
Сорри, с перваго раза не догнал, сейчас, кажется, понял, что тебе нужно.
Фор апдэйт тебе тут не поможет, во-первых, у тебя инсерты а от них таким образом не залочиться, во-вторых, у тебя count, для которого фор апдэйт ты сделать не сможешь, насколько я знаю.
Наверно выход - лочить всю таблицу.
Насколько я понимаю, задача в следующем, пока бежит 1-я транзакция, необходимо чтоб никакая другая параллельная транзакция не стартовала, ни по этому документу ни по какому-то другому, так?
----------- UPD
сорри, за повторы. облажался я...


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

то я очень сомневаюсь, что транзакции в моем примере безысходно повиснут где бы то ни было.хули тут сомневаться, сделай тестовую табличку да посмотри
да и я на самом деле не сомневаюсь, а уверен
dbms виснущая в описанном тобой случае - говно, а не dbms
селект фор апдейт - гораздо более локальное изменение, проеврить которое проще на порядки
Я определился. Мне надо, чтобы содержимое fields соответствовало тому, что пришло извне.а ты не можешь словить какой-н уникальный идентификатор "источника извне", чтобы дважды от него сообщения не принимать?
попробуй, как по ссылке, что ты привел... только мне чет пока не понятно как это работает..ну да, на след недельке будем пробовать
Я обязан принимать все сообщения, ибо документы со временем меняются.
потому что иначе твоя задача принципиально не решается
ты никогда не сможешь отличить легитимный апдейт от дубля
Таймстемпы меня мало волнуют, как и мало волнует был ли такой документ уже.
Единственное, что мне надо, чтобы если случайно начали обрабатываться две версии документа одновременно, то они обрабатывались "последовательно". То есть все, что мне надо - это блокировка по отдельным документам. Как ее достичь - тут и обсуждается.
ну смотри, если у тебя один "приход" вдруг начинает обрабатываться двумя потоками, нельзя ли этот "приход" так пометить, чтобы на уровне потоков сразу просечь, что он уже обрабатывается?
я уж не говорю о том, чтобы не запускать два потока на один приход ...
кстати да, может проще организовать единую очередь, которую будет разбирать строго один разборщик (или в целях оптимизации несколько, но работая согласованно - не беря на обработку изменение, если уже кто-то уже обрабатывает изменение с тем же doc_id)
Я же уже писал, что меня не надо лечить, какие у меня кривые условия, непральная архитектура, надо переделать постановку, итд.
Я знаю точно, что мне надо, и хочу это минимальными изменениями достичь.
По поводу помечания приходов - ты прав, этого мне в общем то и хочется. Только у меня осложняется тем, что документ может перерасчитываться не только по приходу обновления извне, но и по иным событиям внутри уже моей системы. Соответственно, мне хочется помечать не приходы, а сами документы. Типа - он обрабатывается, ждите.
И в идеале хочется, чтобы этим "помечанием" занимались стандартные средства dbms (они вроде для этого и придуманы а не я изобретал очередной лисопедный лок, добавляя в табличку колонку с флажком "занято".
Вариант идеальный, но потребует слишком больших кодерских усилий, ибо события у нас разнородные и довольно сложные, продумывать их структуру ломает.
но, если ориентироваться, на задачу из первого поста, то ultimate кувалдой, которая явно тебя спасет будет вот это: http://hashfactor.wordpress.com/2009/03/31/using-the-oracle-...
да, бтв вот здесь: книшка на 107 странице пишут, что инсерт не будет блокироваться ни на что разумное, кроме pk.
ну и еще попробуй поботать, что в случае локов будет делать merge: http://www.oracle-developer.net/display.php?id=203
http://en.wikipedia.org/wiki/Deadlock#AvoidanceHowever, for many systems it is impossible to know in advance what every process will request. This means that deadlock avoidance is often impossible.
mssql ставить лень, в оракле не виснетна MSSQL-е вот такие два запроса попадают в дедлок
да и я на самом деле не сомневаюсь, а уверен
dbms виснущая в описанном тобой случае - говно, а не dbms
set transaction isolation level serializable
begin tran
declare @doc_id int
set @doc_id = 11
declare @a int
select @a = count(1) from field where doc_id = @doc_id
WAITFOR DELAY '0:0:5';
if @a = 0
insert into field (doc_id, value_data) values (@doc_id, 'deadbeef');
commit tran
Для полноты сама таблица
CREATE TABLE [dbo].[field](
[Id] [int] IDENTITY(1,1) NOT NULL,
[doc_id] [int] NOT NULL,
[value_data] [nvarchar](max) NOT NULL,
CONSTRAINT [PK_field] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
Скорее всего IBM DB2 тоже выдаст дедлок в такой ситуации.
Расскажи, как ты на Oracle-е это проверял? и что он при этом делает? начинает ждать при выполнении селекта второй транзакцией?
set transaction isolation level snapshot
то, как и ожидалось, вставляются две записи.
За ссылки спасибо, кувалда явно спасет.
В книжке на 107 странице я так понимаю рассматривается дефолтная снепшот изоляция, отсюда и тезис.
Я где то утверждал, что эти механизмы идеальны или абсолютны?
На оракле гоняемся со снепшот изоляцией, соотв делает он ровно то, что описнао в примере =)
На оракле гоняемся со снепшот изоляцией, соотв делает он ровно то, что описнао в примере =)т.е. вставляются две записи
Id doc_id value_data
----------- ----------- ----------------------------------
13 16 deadbeef
15 17 deadbeef
16 17 deadbeef
не смотря на то, что стоит if (a = 0) ?
В первом посте у тебя написано
2. Serializable isolation, pessimistic locking.
Тут все хорошо. Транзакции в 1 строчке делают range lock, соотв вторая транзакция тут припаузится до коммита первой.
т.е. вставляются две записи, не смотря на то, что стоит if (a = 0) ?Да, вставляются.
В первом посте у тебя написаноДа, написано. Пока не вижу противоречий.
Я где то утверждал, что эти механизмы идеальны или абсолютны?а причем тут идеальность или неидеальность алгоритмов? Ты говорил
современные dbms используют довольно навороченные алгоритмы для разрешения гораздо более сложных дедлоков, то я очень сомневаюсь, что транзакции в моем примере безысходно повиснут где бы то ни было.
...
да и я на самом деле не сомневаюсь, а уверен
под "зависнут" ты понимал именно дедлок, поскольку ты отвечал на мой пост, где я говорил о дедлоке. Однако, я тебе явно продемонстрировал, что современное СУБД-блокировщик попадает в дедлок. И вместо того, чтобы признать, что не прав в том, что в рассматриваемой ситуации случится дедлок, ты уводишь разговор в сторону.
Да, вставляются.и ты называешь это словами
Это плохо.
Однако, то что происходит на Serializable isolation, ты называешь
Тут все хорошо.
Т.е. ты так и не попробовал ту ситуацию на Оракле?
Соответственно, если dbms, после того, как два треда повиснут, увидит это, и перезапустит какой то из тредов, то уже это я называю успешным разруливанием дедлока.
A deadlock is a situation wherein two or more competing actions are waiting for the other to finish, and thus neither ever does.
Ты под этим понимаешь что-то свое, и потому докапываешься в ебанутом стиле, к сожалению, присущем этому разделу.
Пожалуйста, либо вернись к теме топика, либо заведи отдельную тему, где я с тобой с удовольствием это пообсуждаю в свободное время.
Единственное, что мне надо, чтобы если случайноначали обрабатываться две версии документа одновременно, то ониобрабатывались "последовательно". То есть все, что мне надо - этоблокировка по отдельным документам. Как ее достичь - тут и обсуждается.
Слушай, а триггер такой написать не получится?
Триггер должен как то разбираться, кто щас инсертит поле в документ, хозяин лока или нет, а это еще отдельный наворот, соотв опять править код. Проще уж без триггера поправить код там где надо.
Триггер. Не знаю, пока мне не приходит ничего толкового в голову.
Триггердолжен как то разбираться, кто щас инсертит поле в документ, хозяинлока или нет, а это еще отдельный наворот, соотв опять править код.Проще уж без триггера поправить код там где надо.
Думаю, если грамотно триггер написать, то и лочить ничего не надо будет, триггер будет формировать очередь для номеров сессий, которые обрабатывают разные версии одного и того же документа. Как-то так...
перезапуститя никак не пойму, что ты под этим подразумеваешь? Ты имеешь ввиду, что транзакция оформлена одним скриптом, который серверу отсылается за один раз? В этом случае теоретически сервер может начать транзакцию заново, но даже в этом случае MS SQL Server просто выдает указанное сообщение об ошибке. А повторять попытку или нет решает клиент. Возможно, есть режим, когда сам сервер будет повторять попытки... надо будет разузнать. Но у нас часто транзакция объединяет несколько скриптов.
этом случае теоретически сервер может начать транзакцию зановоДа, и для этого отнюдь не надо иметь всю транзакцию целиком в наличии.
Клиент может еще у себя какие-то действия сделать и все в этой же транзакции.
Ммм. Не знаю, такое мне в голову не приходило, надо обдумать.
Тихо откатывать до самого начала, например, и тихо переделывать.
select count(1) into a from field where doc_id = :doc_id;
if (a = 0) then
begin
select 1 into a from documents where doc_id = :doc_id for update nowait;
insert into field (doc_id, value_data) values (:doc_id, 'deadbeef');
exception when others -- лень смотреть правильный
then
null; -- Здесь можно повесить свой обработчик. У тебя сказано что ты хочешь сохранять все
-- приходящие изменения. Но если изменения не различимы, то одно можно просто выкинуть.
end;
end if;
commit;
Если нет, то можно конечно делать dbms_lock.ALLOCATE_UNIQUE(:doc_id но как бы dbms_lock не шибко быстрый, а от большого количества защищённых ресурсов вообще дохнет.
Оставить комментарий
katrin2201
Есть след простенькая транзакцияТо есть проверяем, нет ли у определенного документа (:doc_id) полей, если нет, то добавляем одно поле.
Все хорошо.
Теперь пускаем две такие транзакции параллельно. Уровень изоляции у нас меньше serializable. Предположим, они обе выполняются поочередно построчно, то есть сначала первая транзакция выполнит строку 1, потом вторая строку 1, потом первая строку 2, вторая строку 2 итд.
В итоге в конце обе успешно скоммитятся и я буду иметь две строчки в таблице field для doc_id. Это плохо.
Варианты как этого избежать:
1. Serializable isolation, optimistic locking.
В этом случае в 1 строчке транзакции пометят range, но не залочат, дойдут до строчки 3, первая транзакция увидит, что она нарушит лок, и будет перезапущена (откачена?). Вторая транзакция в это время должна успеть дойти до коммита раньше, чем первая выполнит строчку 1, иначе ей тоже придется перезапуститься. Имеем livelock.
Минусы:
- если транзакции длинные(в реале они у меня оч длинные: есть внешний цикл по doc_id то перезапуск какой-нибудь транзакции будет очень дорогим
- если транзакции коротенькие, как приведено в примере выше, то оч вероятный livelock.
2. Serializable isolation, pessimistic locking.
Тут все хорошо. Транзакции в 1 строчке делают range lock, соотв вторая транзакция тут припаузится до коммита первой.
Минусы:
- собсно дорогой сам по себе pessimistic locking
3. Исправить код, добавив в 1 строчку select for update (у меня оракл). Тут то же самое, что в варианте два, только без глобального пессимистик лока. Соотв минус второго варианта уходит.
Вопрос: не лажаю ли я где-нибудь? особенно в первом варианте с лайвлоком, может сейчас делают оптимизации под этот случай (oracle 10g)? может есть еще варианты?