Исходные данные. (Все упрощено в целях понимания) Есть 3 таблицы
DOCTABLE(.....DD datetime,ACCDE int ,ACCKR int, SUMM decimal(18,4) ...) - документы
OVERTABLE(.....DD datetime,ACCDE int ,ACCKR int, SUMM decimal(18,4) ...) - проводки
SALDOTABLE(.....ACC int, DD datetime,DEB decimal(18,4), KRE decimal(18,4),SALDO decimal(18,4)) - остатки
ACCDE - счет дебета
ACCKR - счет кредита
SUMM - сумма проводки
ACC счет
DEB оборот по дебету за DD
KRE оборот по кредиту за DD
SALDO остаток за DD
На DOCTABLE есть триггер:
CREATE TRIGGER dbo.DOCTABLE_CALC ON dbo.DOCTABLE
FOR INSERT, UPDATE, DELETE
AS
declare ....
Set NOCOUNT ON
Set XACT_ABORT ON
Set @LastIdent = @@Identity
while курсоры по inserted
begin
if @ACCDE is null
begin
raiserror ('Не указан счет дебета в документе',16,1)
goto ex
end
if @ACCKR is null
begin
raiserror ('Не указан счет кредита в документе',16,1)
goto ex
end
...
Insert Into OVERTABLE (...) values (...)
end курсоры по inserted
while курсоры по deleted
begin
delete from OVERTABLE where <Документ> = @ID
end курсоры по deleted
Set @s = 'SELECT Identity(int, ' + CAST(@LastIdent as varchar(10)) + ', 1 ) as I INTO #_T'
Exec(@s)
Set NOCOUNT OFF
return
ex:
Set NOCOUNT OFF
if @@trancount > 0 rollback
На OVERTABLE тоже есть триггер:
CREATE TRIGGER dbo.OVERTABLE_CALC ON dbo.OVERTABLE
FOR INSERT, UPDATE, DELETE
AS
Declare ...
Set NOCOUNT ON
Set XACT_ABORT ON
Set @LastIdent = @@Identity
while курсоры по deleted
begin
exec CALCSALDO (@ACCDE ,2/* удалить движение*/,@DD,@SUMM,...)
exec CALCSALDO (@ACCKR ,2/* удалить движение*/,@DD,@SUMM,...)
end курсоры по deleted
while курсоры по inserted
begin
exec CALCSALDO (@ACCDE ,1/* положить движение*/,@DD,@SUMM,...) /* место 1 */
exec CALCSALDO (@ACCKR ,1/* положить движение*/,@DD,@SUMM,...) /* место 2 */
end курсоры по inserted
Set @s = 'SELECT Identity(int, ' + CAST(@LastIdent as varchar(10)) + ', 1 ) as I INTO #_T'
Exec(@s)
Set NOCOUNT OFF
return
ex:
Set NOCOUNT OFF
if @@trancount > 0 rollback
Ну и последнее, процедура:
CREATE PROCEDURE dbo.CALCSALDO ...
Declare ...
SET nocount on
SET XACT_ABORT ON
Select @nCount=count(*) from SALDOTABLE
with (PAGLOCK,XLOCK,HOLDLOCK) /*эта мешанина подобрана в ходе разборок
с проблемой, но влияет только на дедлоки, проблему не устраняет*/
where ACC=@acc AND DD >= @dd
Select @AccDOpen = DOPEN,@ACCid = ID from ACCONTS where ID = @ACC
/* проверки параметров счета */
if @ACCid is null
begin
RaisError('Счет не найден.',16,1)
goto ex
end
if @AccDOpen > @dd
begin
RaisError('Счет не открыт на дату операции.',16,1)
goto ex
end
if /* еще ряд проверок */
begin
RaisError(' Сообщение о ошибке.',16,1)
goto ex
end
/* НАЧАЛО ПЕРЕСЧЕТА ОСТАТКА */
if /* записи в SALDOTABLE за эту дату еще нет - вставка */
begin
insert into SALDOTABLE( ....
end
if /* запись в SALDOTABLE за эту дату уже есть*/
begin
update SALDOTABLE( ....
end
if /* отменены все движения по счету за этут дату*/
begin
delete from SALDOTABLE where ....
end
/* КОНЕЦ ПЕРЕСЧЕТА */
if /* проверка на красное сальдо по счету */
begin
RaisError('Счет выходит на красное сальдо в дату ....',16,1) /*место 3 */
goto ex
end
SET nocount off
return
ex:
SET nocount off
if @@trancount > 0 rollback
Все "служебные" операторы (nocount,xact_abort и т.д) и структура текстов сохранены.
Этот код работает на нескольких базах. Кроме одной. На одной базе периодически происходят рассогласования SALDOTABLE и OVERTABLE. Всегда это выглядит так: по счету кредита сумма кредитового оборота (поле KR) в SALDOTABLE завышена и не соответствует OVERTABLE. В OVERTABLE нет записи, на ту сумму, которая есть в KR в SALDOTABLE .
Мне сейчас представляется такой ход событий:
Вставляется запись в OVERTABLE, это вызывает срабатывание триггера OVERTABLE_CALC он в свою очередь вызывает расчет по счету дебета процедурой CALCSALDO (место 1), затем по счету кредита (место 2).
Но при вызове расчета по счету дебета иногда возникает логическая ошибка в месте 3 (практически подтвержденно по профайлеру - сложно мониторить рабочую базу с большим потоком операций). И на основании этой ошибки вся транзакция откатывается. НО после отката остается запись по счету кредита в SALDOTABLE.
Такое ощущение, что несмотря на SET XACT_ABORT ON в триггере все-таки вызывается CALCSALDO (место2) но уже БЕЗ транзакции. И после отката всего остального запись в SALDOTABLE по такому движению остается.
В профайлере такжде иногда пробегают ошибки "Transaction count after EXECUTE indicates that a COMMIT or ROLLBACK TRANSACTION statement is missing" но точно определить к чему они относятся мне не удалось. Возможно и к пересчету.
Это пока только мои догадки. Насколько они могут соответствовать реальности? Нужно ли и как правильно обработать выход из первого вызова CALCSALDO. Проверять на @@trancount = 0 ? Или @@error ?
Повторить ситуацию на тестовом сервере не могу. Есть только рабочая - там тестовые проводки делать не имею права - приходится пока разбираться теоритически.
Сервер 2005 SP2 версия 9,0,3073