![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
KEY VIOLATION при выполнении процедуры. [D6, IB6.x]
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)
Вот именно этого-то там как раз и нет.
Жаль. Понятно.