Top.Mail.Ru
Мастера DELPHI, Delphi programming community Титульная страница Поиск, карта сайта Написать письмо 
| Новости | Поиск | FAQ | Статьи | Книги | Новости VCL |
| Форумы ЧАТ | Орешник | KOL и MCK |
Форумы -» Основная («Начинающим»)/ Базы / WinAPI / Компоненты / Сети / Media / Игры / Corba и COM / KOL / FreePascal / .Net / Прочее / RSDN.ORG

« предыдущая ветвь | форум | следующая ветвь »

KEY VIOLATION при выполнении процедуры. [D6, IB6.x]


Страницы:   1 2 3

Vuk ©   (2004-01-21 14:38:00) 

Давно не ковырялся с Interbase, но тут пришлось...
Результаты странные. Получил KEY VIOLATION при выполнении поцедуры со следующей логикой:

CREATE PROCEDURE SomeProc(
KeyField1 INTEGER,
KeyField2 INTEGER,
value INTEGER)
AS
begin

if (exists(select 1 from SomeTable where (KeyField1 =:KeyField1) and (KeyField2 = :KeyField2))) then
update
SomeTable
set
Value = :Value
where
(KeyField1 = :KeyField1) and
(KeyField2 = :KeyField2);
else
insert into SomeTable (
KeyField1,
KeyField2,
Value
)
values(
:KeyField1,
:KeyField2,
:Value );
end


У кого-нибудь есть мысли, как такое может получиться?

Добавлю, что эта процедура вызывается из другой в рамках достаточно длительной по времени (работа с сервером через интернет) транзакции. PK в SomeTable составной (KeyField1, KeyField2).


Petr V. Abramov   (2004-01-22 16:28:00) [1]

> Vuk © (21.01.04 23:12) [43]
Ну тут я и впрямь погорячился ((:


jack128   (2004-01-21 23:14:00) [2]


> чем update -> if notfound -> insert (которую мы не можем
> реализовать из-за ограничений языка).
Почему не можем, все можем, если с IB на FB переползем(см выше про row_count) ;-)


Vuk   (2004-01-21 23:12:00) [3]

to Petr V. Abramov:
>Но я уверен, что быстрее, чем ручная проверка уникальности ключа
Тут ссылочки приводили. Там замеряли производительность. Схема с WHEN не просто медленная, а самая медленная во всех случаях. Там же приведен весьма неплохой вариант с курсором.


Petr V. Abramov   (2004-01-21 22:59:00) [4]

> Vuk © (21.01.04 22:30) [41]
> no_rec_version - не пойдет, т.к. в этом случае не пройдет даже
> чтение.
Еще как пройдет, если транзакция не nowait. Но будет ждать.
> Проверка нужна, иначе получим Key Violation при вставке, если
> запись существовала уже тогда, когда еще не стартовали все
> существующие транзакции.
Эта проверка уменьшит вероятностьпоявления Key Violation. За счет проверки частного случая, когда существует закоммиченная запись с заданным ключом (извиняюсь за перефразирование :) Если конкуренция за ресурс небольшая - даже решит проблему. Но на 99.9%, не на 100%
Да и вообще зачем писать самому то, что реализовано на уровне СУБД, в данном случае проверку уникальности??? Да, схема insert -> when KEY_VIOLATION -> update работает (чаще всего) медленне, чем update -> if notfound -> insert (которую мы не можем реализовать из-за ограничений языка). Но я уверен, что быстрее, чем ручная проверка уникальности ключа (которая и срабатывает только на "вчерашние" записи)


Vuk   (2004-01-21 22:30:00) [5]

to Petr V. Abramov:
>Проводим эксперимент
Еще раз повторяю. Проверка нужна, иначе получим Key Violation при вставке, если запись существовала уже тогда, когда еще не стартовали все существующие транзакции.

no_rec_version - не пойдет, т.к. в этом случае не пройдет даже чтение. Уж если на то пошло, то лучше SNAPSHOT TABLE STABILITY.


Petr V. Abramov   (2004-01-21 20:28:00) [6]

> Vuk © (21.01.04 18:40) [37]
> Бр-р-р-р.... Проверку if exists имеет смысл делать в любом
> случае.
Проводим эксперимент:
create table t1 ( f1 integer, primary key(f1))
create procedure brrr as
begin
if (exists (select 1 from t1 where f1 = 1)) then
exception some_exception;
end;

Запускаем 2 транзакции, А и B, обе read_commited rec_version
20:06 Transaction A
insert into t1 values(1)
20:07 Transaction B
execute procedure brrr -- все тихо
20:08 Transaction A
commit
20:09 Transaction B
execute procedure brrr -- поднимается исключение

если, как советует jack128 ©, использовать no_rec_version, транзакция B подождет завершения A, чтобы определиться, "exists" или "не exists". Но no_rec_version - это не развязанные чтение с записью, со всеми прелестями


Vuk   (2004-01-21 20:01:00) [7]

to jack128:
>Никакой уровень изоляции не гарантирует, что приду я с IBE
>между if (exists(...)) и insert"ом не добавлю запись с вашим PK.
Зато в любом случае получим Key Violation, если запись вставлена еще пару дней назад. :o)


jack128   (2004-01-21 19:39:00) [8]


> Бр-р-р-р.... Проверку if exists имеет смысл делать в любом
> случае.
Это как посмотреть ;-)
Никакой уровень изоляции не гарантирует, что приду я с IBE
между if (exists(...)) и insert"ом не добавлю запись с вашим PK. C этой точки зрения if (exists ) вообще не имеет смысла делать никогда ;-)
А если говорить только о вашем разрабатываемом приложении, то тут ИМХО нужен no_rec_version, который как раз гарантирует, что послt if (exists) не начнет апдейт "вашей" записи, но уж точно не кто не гарантирут, что я не вставлю, раньше Вас запись, которую хотите вставить.


Vuk   (2004-01-21 18:40:00) [9]

>Раз read_commited, if (exists(select ... смысла не имеет
Бр-р-р-р.... Проверку if exists имеет смысл делать в любом случае.


Petr V. Abramov   (2004-01-21 18:03:00) [10]

> Vuk
> if (exists(select 1 from SomeTable where (KeyField1 =:KeyField1) and (KeyField2 = :KeyField2))) then
Ну, выдумать KV сервер сам не мог (надеюсь :), поэтому конкурирующая транзакция, скорее всего, была ( возможно, она откатилась)
Все далее изложенное имеет смысл, если isolation level - read_commited rec_version, что скорее всего.
> if (exists(select 1 from SomeTable where (KeyField1 =:KeyField1) and (KeyField2 = :KeyField2))) then...
Раз read_commited, if (exists(select ... смысла не имеет, т.к. select не увидит незакоммиченную запись с заданным ключом, процедура уйдет на insert. К тому времени запись может успеть закоммититься, и получаем subj. Если не успеет, subj получит конкурирующая транзакция, причем, с её точки зрения, "на ровном месте".

kaif © (21.01.04 17:28) [31]
Работать будет медленне, факт, проверенный. Но ничего не поделаешь, это выход, если нет row_count, RowsAffcted, SQL%rowcount и т.д.


Johnmen   (2004-01-21 17:49:00) [11]

>jack128 © (21.01.04 17:44)

Благодарю !


jack128   (2004-01-21 17:47:00) [12]


> kaif © (21.01.04 17:28) [31]
посмотри ссылку, которую я дал выше, там проведен тест на эту тему и показан АБОЛЮТНО лудший вариант для любого соотнашения апдейтов/вставок


jack128   (2004-01-21 17:44:00) [13]

> Johnmen © (21.01.04 17:20) [27]

CREATE PROCEDURE TEST
RETURNS (
ROW_COUNT1 INTEGER)
AS
begin
/* Procedure Text */
delete from addresses
where name = "test";
row_count1 = row_count;
suspend;
end процедура возвращает кол - во удаленных записей

А вот что пишут Changes.txt
* New ROWS_AFFECTED system variable (SF #451927).
Return number of rows affected by the last INSERT/UPDATE/DELETE statement.
Notes:
1. Available in PSQL only.
2. For any other statement than INSERT/UPDATE/DELETE, result is always zero.
Contributor(s):
Dmitry Yemanov
примещание: начиная с rc1 ROWS_AFFECTED переименованно в ROW_COUNT


Sandman25   (2004-01-21 17:36:00) [14]

[31] kaif © (21.01.04 17:28)

Да, это тоже часто используется. Только не знаю, как на IB написать.


kaif   (2004-01-21 17:28:00) [15]

Я здесь недавно видел вопрос насчет применения WHEN в хранимой процедуре. Нельзя сделать вставку, а в случае ошибки, обработав ее в WHEN в той же процедуре, сделать UPDATE? Я не работал с WHEN. Но по-моему это правильное решение, особенно если вставок больше, чем апдейтов. Кстати, не факт, что это будет медленнее работать...


Sandman25   (2004-01-21 17:26:00) [16]

[28] Vuk © (21.01.04 17:22)

Согласен, невнимательно посмотрел на первый пост.
Впрочем, обычно это почти одно и тоже - все равно проверяется по уникальному индексу :)


Sandman25   (2004-01-21 17:23:00) [17]

Vuk

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

jack128

Я извиняюсь, я забыл контекст. Действительно, первоначально я пытался уменьшить вероятность такой ошибки.


Vuk   (2004-01-21 17:22:00) [18]

to Sandman25:
>К тому, чтобы отказаться от проверки (select count(*))
А вот select count(*) и exists(select 1...) - это две большие разницы, т.к. первое приводит к выборке всех записей по условию, а второе - только одной.


Johnmen   (2004-01-21 17:20:00) [19]

>jack128 © (21.01.04 17:08)
>А в FB1.5 есть ROW_COUNT

Можно поподробней ? С киданием куска кода, как многие любят :))))


Sandman25   (2004-01-21 17:19:00) [20]

[22] jack128 © (21.01.04 17:05)

И в чем проблема? Или нужно засунуть данные невзирая на UNIQUE INDEX? :)
Я к чему это написал вообще? К тому, чтобы отказаться от проверки (select count(*)) и за счет этого ускорить ХП.

[23] Vuk © (21.01.04 17:07)
Вот именно этого-то там как раз и нет.
Жаль. Понятно.


Страницы:   1 2 3

Наверх

Титульная страница Поиск, карта сайта Написать письмо