Начало » Использование СУБД » Firebird, HQbird, InterBase » Глюки Firebird4 ? (Firebird 2 и 4 работают по разному)
Глюки Firebird4 ? [сообщение #611] |
Tue, 20 September 2022 16:47 |
anbsoft
Сообщений: 4 Зарегистрирован: September 2022
|
Junior Member |
|
|
Давно работал с Firebird 2.0
Решил попробовать перейти на новую версию (4).
Некоторые триггеры стали работать некорректно.
Примерно понимаю почему так, но пока не знаю что с этим делать.
Привожу код тестовой базы:
В таблице клиентов ведется долг клиента.
В таблице затрат проходят затраты по нему.
Некоторые затраты могут проходить пакетом (При проведении родительской затраты, автоматически проводятся дочернии).
Вставляем список затрат и подтверждаем их переводом State в 1
Поля Info и Test1 добавлены для понимания того, что происходит.
CREATE DATABASE 'Base2.fdb' USER 'SYSDBA' PASSWORD 'masterkey';
CREATE TABLE Klient
(
ID Integer NOT NULL,
Name VarChar(50),
Dolg NUMERIC(15,2) DEFAULT 0,
PRIMARY KEY (ID)
);
CREATE GENERATOR GEN_Klient;
SET TERM ^ ;
CREATE TRIGGER SET_Klient FOR Klient
ACTIVE BEFORE INSERT POSITION 0
AS
declare variable IDTemp Integer;
BEGIN
IF ((NEW.ID=0) OR (NEW.ID IS NULL)) THEN BEGIN
NEW.ID=GEN_ID(GEN_Klient,1);
END
END^
SET TERM ; ^
INSERT INTO Klient (ID, Name, Dolg) VALUES (0,'Test', 0);
CREATE TABLE Zatrat
(
ID Integer NOT NULL,
Parent Integer default 0,
IDKlient Integer,
State Integer default 0,
Suma NUMERIC(15,2) DEFAULT 0,
Info VarChar(100),
Test1 Integer,
PRIMARY KEY (ID)
);
CREATE GENERATOR GEN_Zatrat;
CREATE GENERATOR GEN_Test;
SET TERM ^ ;
CREATE TRIGGER SET_Zatrat FOR Zatrat
ACTIVE BEFORE INSERT POSITION 0
AS
BEGIN
IF ((NEW.ID=0) OR (NEW.ID IS NULL)) THEN BEGIN
NEW.ID=GEN_ID(GEN_Klient,1);
END
END^
CREATE TRIGGER Edit_Zatrat FOR Zatrat
ACTIVE BEFORE UPDATE POSITION 0
AS
declare variable S1 VarChar(20);
declare variable S2 VarChar(20);
BEGIN
IF ((OLD.State<>1) AND (NEW.State=1)) THEN BEGIN
select Dolg from klient where (ID=NEW.IDKlient) into :S1;
UPDATE Klient SET Dolg=Dolg-OLD.Suma WHERE (ID=NEW.IDKlient);
select Dolg from klient where (ID=NEW.IDKlient) into :S2;
NEW.Info=' - ' || coalesce(OLD.Info, '') || ' ' || S1 || ' ' || S2;
NEW.Test1=GEN_ID(GEN_Test,1);
END
IF ((OLD.State=1) AND (NEW.State<>1)) THEN BEGIN
UPDATE Klient SET Dolg=Dolg+OLD.Suma WHERE (ID=NEW.IDKlient);
END
END^
CREATE TRIGGER Edit_Doc FOR Zatrat
ACTIVE AFTER UPDATE POSITION 0
AS
BEGIN
IF ((NEW.Parent<>0) AND (NEW.State<>OLD.State)) THEN BEGIN
UPDATE Zatrat SET State=NEW.State WHERE (Parent=NEW.Parent) AND (State<>NEW.State) AND (ID<>NEW.ID);
END
END^
SET TERM ; ^
INSERT INTO Zatrat (ID, Parent, IDKlient, State, Suma) VALUES (1,1,1,0,10);
INSERT INTO Zatrat (ID, Parent, IDKlient, State, Suma) VALUES (2,1,1,0,30);
INSERT INTO Zatrat (ID, Parent, IDKlient, State, Suma) VALUES (3,1,1,0,50);
INSERT INTO Zatrat (ID, Parent, IDKlient, State, Suma) VALUES (4,1,1,0,200);
UPDATE Zatrat SET State=1 where (ID=1);
SELECT * FROM Klient;
ID NAME DOLG
== ======== ========
1 Test -290.00
Select * FROM Zatrat ORDER BY ID;
ID PARENT IDKLIENT STATE SUMA INFO TEST1
== ====== ======== ===== ====== =================== =====
1 1 1 1 10.00 - 0.00 -10.00 1
2 1 1 1 30.00 - -10.00 -40.00 2
3 1 1 1 50.00 - -40.00 -90.00 3
4 1 1 1 200.00 - -90.00 -290.00 4
COMMIT WORK;
В Firebird 4 результаты совсем друние:
SELECT * FROM Klient;
ID NAME DOLG
== ======== ========
1 Test -740.00
Select * FROM Zatrat ORDER BY ID;
ID PARENT IDKLIENT STATE SUMA INFO TEST1
== ====== ======== ===== ====== =================== =====
1 1 1 1 10.00 - 0.00 -10.00 1
2 1 1 1 30.00 - -10.00 -40.00 2
3 1 1 1 50.00 - -490.00 -540.00 6
4 1 1 1 200.00 - -540.00 -740.00 7
Видно, что между обновлением 2 и 3 строки еще трижды проходит обновление, но никаких следов не оставляет.
Как побороть эту напасть?
|
|
|
|
|
|
|
|
|
Re: Глюки Firebird4 ? [сообщение #618 является ответом на сообщение #611] |
Wed, 21 September 2022 07:08 |
fraks
Сообщений: 134 Зарегистрирован: June 2022 Географическое положение: Новосибирск
|
Senior Member |
|
|
Лог для отслеживания событий в таблице.
CREATE GENERATOR GEN_ZATRAT_LOG_ID;
CREATE TABLE ZATRAT_LOG (
ID INTEGER NOT NULL,
IDZ INTEGER,
PARENT INTEGER,
IDKLIENT INTEGER,
STATE INTEGER,
SUMA NUMERIC(15,2),
INFO VARCHAR(100),
TEST1 INTEGER,
A VARCHAR(5)
);
ALTER TABLE ZATRAT_LOG ADD PRIMARY KEY (ID);
SET TERM ^ ;
CREATE OR ALTER TRIGGER ZATRAT_LOG_BI FOR ZATRAT_LOG
ACTIVE BEFORE INSERT POSITION 0
as
begin
if (new.id is null) then
new.id = gen_id(gen_zatrat_log_id,1);
end
^
CREATE OR ALTER TRIGGER ZATRAT_AIUD_LOG FOR ZATRAT
ACTIVE AFTER INSERT OR UPDATE OR DELETE POSITION 0
as
declare variable sa varchar(3);
begin
sa = '';
if (inserting) then sa = sa || 'I';
if (updating ) then sa = sa || 'U';
if (deleting ) then sa = sa || 'D';
--
insert into ZATRAT_LOG (idz, parent, idklient, state, suma, info, test1, a)
values(NEW.id, NEW.parent, NEW.idklient, NEW.state, NEW.suma, NEW.info, NEW.test1, :sa);
end
^
SET TERM ; ^
|
|
|
|
|
Re: Глюки Firebird4 ? [сообщение #621 является ответом на сообщение #613] |
Wed, 21 September 2022 15:13 |
SD
Сообщений: 407 Зарегистрирован: August 2022
|
Senior Member |
|
|
fraks писал(а) Wed, 21 September 2022 03:56
Для тех кто не в теме - сначала вставляем рыбу затрат, проверяем как оно все выглядит, и потом "подтверждаем" - проводим по долгу клиента.
Вот только обычно это таки делается в разных транзакциях, а здесь разные триггера на одно событие, то есть всё исполняется в рамках одного запроса.
И да, как сказал бы Влад, "нет такой версии".
Как раз в видимости собственных изменений в пределах PSQL блока был баг, который правился.
[Обновления: Wed, 21 September 2022 15:14] Известить модератора
|
|
|
|
Re: Глюки Firebird4 ? [сообщение #626 является ответом на сообщение #613] |
Thu, 22 September 2022 11:21 |
sim_84
Сообщений: 329 Зарегистрирован: June 2022
|
Senior Member |
|
|
fraks писал(а) Wed, 21 September 2022 04:56На FB-2.5.8 работает так же как FB-2.0
Сталкивался с подобной задачей, поэтому бредом не назову, вполне жизненно.
Правда про корректность решения ничего пока не могу сказать.
Для тех кто не в теме - сначала вставляем рыбу затрат, проверяем как оно все выглядит, и потом "подтверждаем" - проводим по долгу клиента.
Получается что родительский расход - это типа шапка документа, а все что под ним - это строки документа.
Проблема не в обновление статуса родительских записей, а в том что для них идёт расчёт нарастающих сумм. Нельзя в триггерах использовать логику, которая зависит от порядка срабатывания, например расчёт нарастающих сумм. Мутации оракуля в принципе как раз от этого и защищают, правда заодно режут и кучу других полезных возможностей.
|
|
|
|
|
|
Re: Глюки Firebird4 ? [сообщение #649 является ответом на сообщение #648] |
Mon, 26 September 2022 09:39 |
sim_84
Сообщений: 329 Зарегистрирован: June 2022
|
Senior Member |
|
|
Цитата:Или по дефолту в четверке запускается транзакция с таким уровнем изоляции, которая не видит собственных изменений?
не бывает таких уровней изолированности.
В 4.0 появился Read Committed Read Consistency, которая может запускать рестарты на конфликтах, а потому один и тот же запрос может выполняться несколько раз, но тут не тот случай. Рестарты в основном вредят в транзакции, если есть не транзакционные операторы (дергание генератора, UDR/UDF отправки почты ...) или автономные транзакции. В остальных случаях они на результат не влияют.
А вот начиная с 3.0 появилась стабильность курсора, которая скорее всего здесь и играет свою роль.
Теперь по коду самого триггера. Ну чего сказать, кто-то его не аккуратно написал так, что одна и та же запись обновляется несколько раз. Ибо по parent в одну и ту же запись из разных вызовов триггеров можно прийти несколько раз.
Расчёт на то что State изменит первый update и он будет виден остальным не верны. Ибо проверка видимости происходит в разных вызовах триггера, но при этом на одном большом update. Тут как раз срабатывает новшество тройки, а не 4.0. Сам по себе курсор update Zatrat стабилен, то есть пока обновляется одна запись, состояние остальных записей из курсора будет неизменным, даже если они обновляются в вызываемых триггерах.
[Обновления: Mon, 26 September 2022 12:30] Известить модератора
|
|
|
|
Re: Глюки Firebird4 ? [сообщение #656 является ответом на сообщение #650] |
Tue, 27 September 2022 12:37 |
anbsoft
Сообщений: 4 Зарегистрирован: September 2022
|
Junior Member |
|
|
Кто-нибудь может описать алгоритм работы для 4 версии?
Для 2.0 вроде все логично и понятно:
Рассмотрю пошагово:
Изначально у всех четырех строк State=0 (0,0,0,0) Dolg=0
1) Выполняю UPDATE Zatrat SET State=1 WHERE (ID=1); (1,0,0,0) Dolg=10
2) Срабатывает триггер для 1 строки, выполняется изменение State для 2 строки (1,1,0,0) Dolg=40
3) Срабатывает триггер для 2 строки:
а) 1 строку пропускает, так как у нее State=1
б) выполняется изменение State для 3 строки (1,1,1,0) Dolg=90
4) Срабатывает триггер для 3 строки:
а) 1 и 2 строку пропускает, так как у них State=1
б) выполняется изменение State для 4 строки (1,1,1,1) Dolg=290
5) Срабатывает триггер для 4 строки:
а) 1,2 и 3 строку пропускает, так как у них State=1
6) заканчивает работу триггер для 4 строки
7) заканчивает работу триггер для 3 строки
продолжает работу триггер для 2 строки и пропускает 4 строку, так как там State=1, заканчивает работу
9) продолжает работу триггер для 1 строки и пропускает 3 и 4 строку, так как там State=1, заканчивает работу
10) обновление завершено State (1,1,1,1) Dolg=290
В 4.0 создается впечатление, что рекурсивно запущенные триггеры видят не все уже проведенные изменения.
|
|
|
Re: Глюки Firebird4 ? [сообщение #657 является ответом на сообщение #656] |
Tue, 27 September 2022 13:10 |
hvlad
Сообщений: 355 Зарегистрирован: August 2022
|
Senior Member |
|
|
anbsoft писал(а) Tue, 27 September 2022 12:37Кто-нибудь может описать алгоритм работы для 4 версии?
Для 2.0 вроде все логично и понятно:
Рассмотрю пошагово:
Изначально у всех четырех строк State=0 (0,0,0,0) Dolg=0
1) Выполняю UPDATE Zatrat SET State=1 WHERE (ID=1); (1,0,0,0) Dolg=10
2) Срабатывает триггер для 1 строки, выполняется изменение State для 2 строки (1,1,0,0) Dolg=40
3) Срабатывает триггер для 2 строки:
а) 1 строку пропускает, так как у нее State=1
б) выполняется изменение State для 3 строки (1,1,1,0) Dolg=90
4) Срабатывает триггер для 3 строки:
а) 1 и 2 строку пропускает, так как у них State=1
б) выполняется изменение State для 4 строки (1,1,1,1) Dolg=290
5) Срабатывает триггер для 4 строки:
а) 1,2 и 3 строку пропускает, так как у них State=1
6) заканчивает работу триггер для 4 строки
7) заканчивает работу триггер для 3 строки
продолжает работу триггер для 2 строки и пропускает 4 строку, так как там State=1, заканчивает работу Не вникал в DDL, но вот тут, похоже, работает стабильный курсор, не позволяющий апдейту видеть изменения, сделанные "внутренними" триггерами.
anbsoft9) продолжает работу триггер для 1 строки и пропускает 3 и 4 строку, так как там State=1, заканчивает работу
10) обновление завершено State (1,1,1,1) Dolg=290
В 4.0 создается впечатление, что рекурсивно запущенные триггеры видят не все уже проведенные изменения.
Всё это есть мина замедленного действия, независимо от версии FB.
Оно зависит от физического порядка записей в таблице, это раз.
Для нескольких сотен записей тут будет переполнение стека, это два.
Бизнес логика в триггерах была, есть и будет - ЗЛО, так делать НЕЛЬЗЯ.
|
|
|
|
Re: Глюки Firebird4 ? [сообщение #659 является ответом на сообщение #656] |
Tue, 27 September 2022 15:06 |
sim_84
Сообщений: 329 Зарегистрирован: June 2022
|
Senior Member |
|
|
ты не учитываешь тот факт, что update это тоже курсор. И обновляет он у тебя отнюдь не одну запись.
Подсказываю
UPDATE Zatrat SET State=NEW.State WHERE (Parent=NEW.Parent) AND (State<>NEW.State) AND (ID<>NEW.ID);
Эквивалентно
FOR
SELECT *
FROM Zatrat
WHERE (Parent=NEW.Parent) AND (State<>NEW.State) AND (ID<>NEW.ID)
AS CURSOR C
DO
UPDATE SET State=NEW.State WHERE CURRENT OF C;
Не зависимо чего там наменяли триггеры при апдейте первой и последующих записей, сам SELECT выдаст точно такое количество строк и их результаты, как будто UPDATE записей в триггерах вообще не было.
[Обновления: Tue, 27 September 2022 15:12] Известить модератора
|
|
|
Re: Глюки Firebird4 ? [сообщение #660 является ответом на сообщение #659] |
Tue, 27 September 2022 15:49 |
anbsoft
Сообщений: 4 Зарегистрирован: September 2022
|
Junior Member |
|
|
sim_84 писал(а) Tue, 27 September 2022 15:06Не зависимо чего там наменяли триггеры при апдейте первой и последующих записей, сам SELECT выдаст точно такое количество строк и их результаты, как будто UPDATE записей в триггерах вообще не было.
Спасибо, теперь понял.
|
|
|
Переход к форуму:
Текущее время: Fri Nov 15 05:51:54 GMT+3 2024
Общее время, затраченное на создание страницы: 0.01629 секунд
|