|
|
|
Как обработать исключение, возникшее в триггере
|
|||
|---|---|---|---|
|
#18+
Имеется хранимая процедура, в которой выполняется команда INSERT, запускающая триггер FOR INSERT, который в свою очередь выполняет команду UPDATE. Выглядит это вот так (MS SQL 2000): ---------------------------- CREATE PROCEDURE spAddPlace (@sName varchar(50), @nTopid int) AS BEGIN declare @ErrorStatus int SET NOCOUNT ON SET LOCK_TIMEOUT 5000 BEGIN TRANSACTION INSERT INTO tbPlace ( pname, topid ) VALUES ( @sName, @nTopid ) select @ErrorStatus = @@ERROR if @ErrorStatus = 0 COMMIT TRANSACTION else ROLLBACK TRANSACTION RETURN (@ErrorStatus) END GO ---------------------------- CREATE TRIGGER tgAddPlace ON [dbo].[tbPlace] FOR INSERT AS set nocount on UPDATE tbPlace SET subcount = P.subcount + (select count(*) from inserted where toppid = P.pid) FROM tbPlace P, inserted I WHERE I.toppid is not null AND P.pid = I.toppid ---------------------------- Вобщем, в табличке хранится древовидная структура, и триггер просто добавляет количество "детей" у "родителя". Теперь берем и выполняем в Query Analyzer'е следующее: DECLARE @RC int EXEC @RC = spAddPlace 'test', 123 select @RC При отсутствии ошибок все проходит, и на выходе получаем 0. Однако, если в момент добавления "родитель" кем-то блокирован, то по истечении 5 секунд получим сообщение об ошибке номер 1222 "Lock request time out period exceeded. The statement has been terminated" и выполнение просто прерывается. Хотя, по идее, код этой ошибки должен сохраняться в переменной @ErrorStatus, дабы быть потом возвращенным из хранимой процедуры пользователю. Однако, этого не происходит. Все строки кода в процедуре после INSERT'а не выполняются, строка "select @@RC" после вызова процедуры тоже не выполняется. Если в триггере после UPDATE написать "print @@ERROR" - тоже не выполняется. Т.е., как будто происходит вылет вверх по всей цепочке вызовов. В чем причина? Помогите, пожалуйста, разобраться. Заранее спасибо. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 20.05.2002, 13:18:37 |
|
||
|
Как обработать исключение, возникшее в триггере
|
|||
|---|---|---|---|
|
#18+
IMHO дело тут не в триггере, а в том, что внутренний обработчик ситуации timeout принудительно завершает активный батч. Даже трюк с 'заворачиванием' проблемного кода в sp_executesql в данном случае не работает. Батч все равно прерывается, а выставленная @@ERROR видна только в следующем батче текущего соединения. BOL предлагает возложить всю обработку timeout-ов на клиентское приложение. Вот что там написано на эту тему: When a statement has waited longer than the LOCK_TIMEOUT setting, the blocked statement is canceled automatically, and error message 1222 "Lock request time-out period exceeded" is returned to the application. However, any transaction containing the statement is not rolled back or canceled by SQL Server. Therefore, the application must have an error handler that can trap error message 1222. If an application does not trap the error, it can proceed unaware that an individual statement within a transaction has been canceled, and errors can occur because statements later in the transaction may depend on the statement that was never executed. Implementing an error handler that traps error message 1222 allows an application to handle the time-out situation and take remedial action for example, automatically resubmitting the statement that was blocked, or rolling back the entire transaction. Вообще говоря отсутствие нормальной структурированной обработки ошибок это известная проблема для Transact-SQL. Микрософт обещает исправить это только в следующей версии SQL сервера. Будем надеяться что не обманут ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 20.05.2002, 16:20:53 |
|
||
|
Как обработать исключение, возникшее в триггере
|
|||
|---|---|---|---|
|
#18+
Я тут поэкспериментировал, и вот что заметил. Получается, что дело не в LOCK_TIMEOUT, а в том, как ведет себя MS SQL 2000 (или триггер, или - не знаю кто ). Если в цепочке вызовов не присутствует триггер, то ошибка прекрасно ловится в текущем BATCH'е. Но если в эту цепочку вклинить триггер, в котором содержится команда, заведомо приводящая к ошибке на этапе выполнения (причем, любая ошибка, ну, например, INSERT в несуществующую таблицу заканчивается возвращением @@ERROR=208 ), то получаем все ту же картину: выполнение прерывается и ошибка не ловится, хотя Query Analyzer ругается. И, как сказал cvasil (которому, кстати, спасибо), "выставленная @@ERROR видна только в следующем батче текущего соединения". Т.е., если после неудачного завершения текущего BATCH'а сразу же посмотреть "select @@ERROR", она действительно-таки там присутствует, и можно произвести диагностику. Но ведь это уже в следующем батче. А можно ли поймать такую ошибку в текущем? И если можно, то как? ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 21.05.2002, 10:11:20 |
|
||
|
Как обработать исключение, возникшее в триггере
|
|||
|---|---|---|---|
|
#18+
Хм ... Я тут тоже маленько поэксперементировал, но у меня ошибка по таймауту не ловится даже при отсутствии триггера. Например делаю так: --В соединении 1 создаю табличку, вставляю туда строку и запускаю долгую транзакцию с update этой строки create table t1(id int, data int) insert t1 (id,data) values (1,1) go begin tran update t1 set data=0 where id=1 waitfor delay '000:05:00' rollback tran go drop table t1 go -- В то же самое время из соединения 2 делаю update той же строки: set LOCK_TIMEOUT 1000 update t1 set data=0 where id=1 if @@error<>0 print 'timeout' -- или вот так: declare @rc int set LOCK_TIMEOUT 1000 exec @rc=sp_executesql N'update t1 set data=0 where id=1' if @rc<>0 print 'timeout' И в том и в другом случае до print 'timeout' дело не доходит. Ошибка несуществующей таблицы ловится во втором варианте, но не в первом. В случае же когда ошибка (любая) происходит в триггере она похоже действительно не ловится. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 21.05.2002, 13:33:13 |
|
||
|
Как обработать исключение, возникшее в триггере
|
|||
|---|---|---|---|
|
#18+
Вот сижу и не пойму: это что - очередной сюрприз Микрософта? Смотрю в BOL, раздел "Rollbacks in Stored Procedures and Triggers", а там написано: -------------------------- You must use the SAVE TRANSACTION statement to do a partial rollback in a trigger, even if it is always called in autocommit mode. This is illustrated by the following trigger: CREATE TRIGGER TestTrig ON TestTab FOR UPDATE AS SAVE TRANSACTION MyName INSERT INTO TestAudit SELECT * FROM inserted IF (@@error <> 0) BEGIN ROLLBACK TRANSACTION MyName END -------------------------- Ну, случится этот самый ERROR в INSERT'е, и - большой привет, проверка никогда не выполнится. Хотя, с другой стороны, если люди пишут такую документацию, значит считают этот пример абсолютно рабочим? Значит, после генерации исключения в триггере его выполнение не должно прерываться? Почему ж тогда наблюдается противоположная картина? Может, есть все-таки методы? Может, установки какие-то или трюки? ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 21.05.2002, 15:06:24 |
|
||
|
Как обработать исключение, возникшее в триггере
|
|||
|---|---|---|---|
|
#18+
Не надо чуть-что ругать мелкософт...большинство ошибок из-за нашего не допонимания или не знания, хотя кое-где и мелкософт виновен. А теперь по теме: вставь перед Begin Tran строку - SET XACT_ABORT ON. И отлавливай ошибку выполнения своей SP. Удачи. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 22.05.2002, 04:32:07 |
|
||
|
Как обработать исключение, возникшее в триггере
|
|||
|---|---|---|---|
|
#18+
Я так думаю, что надо поставить все-таки SET XACT_ABORT OFF, чтобы сервер НЕ прерывал транзакцию автоматически, если возникает ошибка run-time, тогда операторы после INSERT будут выполняться. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 22.05.2002, 05:10:44 |
|
||
|
Как обработать исключение, возникшее в триггере
|
|||
|---|---|---|---|
|
#18+
Deem: Но в этом случае триггер не отработает и SP завершится без ошибки Kosta: Попробуйте- CREATE TRIGGER tgAddPlace ON [dbo].[tbPlace] FOR INSERT AS set nocount on UPDATE tbPlace SET subcount = P.subcount + (select count(*) from inserted where toppid = P.pid) FROM tbPlace P, inserted I WHERE I.toppid is not null AND P.pid = I.toppid if @@ERROR >0 begin raiserror 50010 'Неверная попытка update!' rollback transaction end ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 22.05.2002, 05:21:52 |
|
||
|
Как обработать исключение, возникшее в триггере
|
|||
|---|---|---|---|
|
#18+
Andreyka, Deem: Ставил SET XACT_ABORT и ON и OFF - не помогает. Andreyka: Если бы все было так просто... Все дело как раз в том, что проверка "if @@ERROR >0" не отработает, если в "UPDATE" сгенерируется исключение. Прерывается выполнение всего пакета вплоть до верхнего уровня. Я уже свел свои "изыскания" до минимума. Вот попробуйте сделать у себя такой примерчик, и посмотрите, что получается. Может, я действительно чего-то не вижу? (Напомню: это MS SQL 2000): ------------------------------------------------------------------------------ 1. Создаем простенькую табличку Test с двумя полями - CODE int, NAME char(10). 2. Создаем триггер на вставку для этой таблички: CREATE TRIGGER tgTest ON [dbo].[Test] FOR INSERT AS set nocount on insert into AbsentTable values (1,2,3) -- попытка вставки в несуществующую таблицу, стопроцентная ошибка print 'Trigger: '+ltrim(str(@@ERROR)) 3. В Query Analyzer'е пишем: insert into Test values (1, 'Try insert') select @@ERROR ------------------------------------------------------------------------------ Вот в такой ситуации я никогда не получу сообщения 'Trigger: 208' и не увижу результатов от "select @@ERROR". Хотя на закладке "Messages" появится ожидаемое сообщение об ошибке (и правильно, так и было задумано). И все, батч прервался... Т.е., если даже и вставить проверку на наличие ошибки после INSERT (как в триггере, так и в Analyzer'е), она не отработает. А вот теперь, после того, как наш батч прервался, повторим в Query Analyzer'е "select @@ERROR" и увидим результат, с которым завершился предыдущий батч (т.е., 208 в данном случае). Но, опять же: это уже СЛЕДУЮЩИЙ батч, а поймать ошибку нужно было в текущем. Вот в чем вся загвоздка ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 22.05.2002, 07:03:05 |
|
||
|
Как обработать исключение, возникшее в триггере
|
|||
|---|---|---|---|
|
#18+
По-моему, тут дело в том, что триггер всегда откатывает всю вложенную транзакцию, поэтому мне кажется, что в нашем случае мы никогда не доберемся до переменной @ErrorStatus. Вот если бы ошибку времени выполнения давала бы вторая вложенная процедура, то в ней можно было бы откатывать только ее транзакцию с помощью точек сохранения, и тогда первая ХП продолжала бы работу при SET XACT_ABORT OFF (ее транзакцию сервер бы не откатил). А тут, наверное, номер ошибки (если он нужен) придется отлавливать на клиенте. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 22.05.2002, 10:16:22 |
|
||
|
Как обработать исключение, возникшее в триггере
|
|||
|---|---|---|---|
|
#18+
Deem: На клиенте в таком случае возникает EOleException, перехватив которое получаем: 1. В ErrorCode - сумасшедший отрицательный код (около 10 знаков), который абсолютно не является значением @@ERROR с SQL-сервера. 2. В Message - текстовое сообщение в оригинальном виде (естественно, на английском языке); При всем при этом в @RETURN_VALUE хранимой процедуры возвращается 0, даже если перед вызовом проинициализировать его другим значением. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 22.05.2002, 11:01:41 |
|
||
|
Как обработать исключение, возникшее в триггере
|
|||
|---|---|---|---|
|
#18+
А на чем написано клиентское приложение? ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 22.05.2002, 13:08:06 |
|
||
|
Как обработать исключение, возникшее в триггере
|
|||
|---|---|---|---|
|
#18+
Deem: Еще пока не написано. Только начинаю. Borland C++Builder 6. Вобщем-то, есть и 5-й, но если вышел 6-й - грех не попробовать ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 22.05.2002, 13:38:31 |
|
||
|
Как обработать исключение, возникшее в триггере
|
|||
|---|---|---|---|
|
#18+
Kosta: Вообще я с такой ситуацией столкнулся у себя (MSSQL2000&MSACCESS2000.adp), т.е. когда триггер на таблице давал ошибку, но хранимая процедура ее не ловила и не делала откат транзакции. Вылечил это так: на клиенте: 1) открываю транзакцию; 2)вызываю SP; 3)закрываю транзакцию или откатываю; на сервере: 1) в теле SP перед открытием транзакции включаю ...ABORT ON(чтобы SP при ошибке в триггере откатила транзакцию). Получаю вот что: при ошибке в триггере SP действительно не ловит @@Error, но транзакция откатывается и прерывается выполнение SP, транзакция на клиенте прерывается и возникает ошибка (она действительно не суразная, но что стого, она всегда в этом случае одна - именно ее и лови). В принципе можно и без транзакции на клиенте. Ведь ты говоришь, что ошибка ловиться только не нравиться ее код, ну и хрен с ним (если он в данном случае всегда такой именно его и обработай по своему - дай свое сообщение и т.д.). ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 23.05.2002, 05:41:37 |
|
||
|
Как обработать исключение, возникшее в триггере
|
|||
|---|---|---|---|
|
#18+
andreyka,Kosta: В общем-то правильно, хотелось бы только, чтобы Kosta сообщил, если у него все получится. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 23.05.2002, 07:16:34 |
|
||
|
Как обработать исключение, возникшее в триггере
|
|||
|---|---|---|---|
|
#18+
andreyka, Deem: Похоже, докопался, наконец-то до того кода ошибки, который возвращается с сервера. Делаю вот что: 1. На сервере оставляю все, как и было. 2. На клиенте (BCB5,TADOConnection,TADOStoredProc) заключаю вызов хранимой процедуры в блок try...catch, 3. В catch'е смотрю в TADOConnection->Errors->Item[index]->NativeError. Вот там-то он и лежит - свеженький и нетронутый (Item[index] - потому что в коллекции может присутствовать несколько ошибок, но в данном случае - всего одна) А тот сумасшедший отрицательный код, который находится в ErrorCode объекта EOleException, лежит в TADOConnection->Errors->Item[index]->Number и, как пишет MSDN, "Indicates the number that uniquely identifies an Error object". В принципе, можно и им пользоваться, и, скорее всего, тоже не ошибешься, т.к., в том же MSDN говорится "Based on the error documentation from the Source, Number, and Description properties of Error objects, you can write code that will handle the error appropriately". Но, мне, например, гораздо больше по душе видеть первоисточник Большое спасибо cvasil, andreyka, Deem! Всем - удачи! ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 23.05.2002, 09:37:07 |
|
||
|
|

start [/forum/topic.php?fid=46&msg=32030869&tid=1822624]: |
0ms |
get settings: |
8ms |
get forum list: |
15ms |
check forum access: |
3ms |
check topic access: |
3ms |
track hit: |
187ms |
get topic data: |
10ms |
get forum data: |
3ms |
get page messages: |
57ms |
get tp. blocked users: |
1ms |
| others: | 204ms |
| total: | 491ms |

| 0 / 0 |
