Опасное поведение MS SQL server

User avatar
Dmitry67
Уже с Приветом
Posts: 28294
Joined: 29 Aug 2000 09:01
Location: SPB --> Gloucester, MA, US --> SPB --> Paris

Опасное поведение MS SQL server

Post by Dmitry67 »

Даже не знал что такие грабли бывают

В приведенном коде в вызываемой процедуре bad возникает фатальная ошибка. В результате процедура 'снимается'

Code: Select all

create table TAB (a int)
go

create procedure bad
as
  print 'xxx begin'
  insert into TAB (a) select 666

  -- fatal error here
  create table #es (a int) insert into #es (b) select 1

  insert into TAB (a) select 777
  print 'xxx end'
go

create procedure up
as
  begin transaction
  declare @ret int
  print 'up begin'
  exec @ret=bad
  if @ret>0 begin rollback transaction return end -- if some status returned
  select @ret
  insert into TAB (a) select 888
  print 'up end'
  commit transaction
go

exec up

select * from TAB


Но процедура up продолжается как ни в чем ни бывало !
Пoнятно что надо не далеть таких ошибок но я считаю что поведение достаточно опасно...

В реальной жизни часто вообще статусы возврата процедур на проверяют что конечно плохо
Но когда проверяют то проверяют часто вот так:

Code: Select all

  declare @ret int
  exec @ret=SomeProc
  if @ret>0 begin ... something like rollback transaction...


В данном примере в случае фатальной ошибки в процедуре значение @ret не присваивается вообще. То есть поведение программы непредсказуемо и зависит от предыдущего значения ret. Правильно было бы написать

Code: Select all

  declare @ret int
  set @ret=NULL
  exec @ret=SomeProc
  if @ret>0 or @ret is NULL begin ... something like rollback transaction...


BOL молчаливо обходят этот вопрос.
Зарегистрированный нацпредатель, удостоверение N 19719876044787 от 22.09.2014
User avatar
Dmitry67
Уже с Приветом
Posts: 28294
Joined: 29 Aug 2000 09:01
Location: SPB --> Gloucester, MA, US --> SPB --> Paris

Post by Dmitry67 »

А вот это вообще непонятно.
arithabort не работает ?

Code: Select all

create procedure bad
as
  set arithabort on
  print 'xxx begin'

  -- fatal error
  declare @x int set @x=10/0

  print 'xxx end'
go

create procedure up
as
  set arithabort on
  declare @ret int
  print 'up begin'
  exec @ret=bad
  select @ret
  print 'up end'
go

set nocount on
set arithabort on
exec up

--------------- result
Serveur : Msg 8134, Niveau  16, État 1, Procédure bad, Ligne 7
Divide by zero error encountered.
up begin
xxx begin
xxx end
           
-----------
-6

up end


Вопросы:
почему -6 ? (понятно что какой то флаг но в BOL не нашел)
почему xxx end ???
Зарегистрированный нацпредатель, удостоверение N 19719876044787 от 22.09.2014
User avatar
tengiz
Уже с Приветом
Posts: 4468
Joined: 21 Sep 2000 09:01
Location: Sammamish, WA

Post by tengiz »

Обработка ошибок - одна из самых запутанных тем, связанных с SQL Server. Причина - обратная совместимость со старыми версиями (включая Sybase), которая уже тянется слишком долго и с которой нужно было уже давно расстаться или резко ограничить (оставив для специальных случаев опцию обратной совместимости). Особенно в наиболее вопиюще нелогичных местах.

Ошибка во времени по своим последствиям выполнения грубо делятся на следующие группы:

1. Аварийно прерывающие выполнение запроса.
2. Аварийно прерывающие выполнение процедуры или батча.
3. Аварийно прерывающие выполнение транзакции.
4. Аварийно прерывающие соединение.

Причём, поведение при некоторых ошибках ещё зависит от текущих параметров соединения и базы данных. В Ваших примерах:

1. Ошибка компиляции относится к группе, прерывающей выполнение процедуры/батча.
2. Арифметическая ошибка относится к группе, прерывающей выполнение запроса, но не процедуры.

Поэтому и разница в поведении. Что касается подробного документирования такой разницы - попробую накопать ссылок. Что найду - выложу.
Cheers
Niky
Уже с Приветом
Posts: 550
Joined: 31 Mar 2000 10:01
Location: Moscow --> Baltimore, MD

Post by Niky »

1. Ну а пока обработку ошибок не переделали, можно посоветовать проверять @@ERROR после потенциально опасных операций. Примеров, кстати, в BOL полно.
2. ARITHABORT таки прерывает батч - попробуйте поставить хотя бы какой-нибудь SELECT после деления на 0 в приведенном примере - он не будет выполнен. PRINT - довольно специфический оператор, используемый больше для диагностики.
User avatar
tengiz
Уже с Приветом
Posts: 4468
Joined: 21 Sep 2000 09:01
Location: Sammamish, WA

Post by tengiz »

Niky wrote:2. ARITHABORT таки прерывает батч - попробуйте поставить хотя бы какой-нибудь SELECT после деления на 0 в приведенном примере - он не будет выполнен. PRINT - довольно специфический оператор, используемый больше для диагностики.

Как я уже писал, поведение при некоторых ошибках ещё зависит от текущих параметров соединения и текущей базы данных. Для того, чтобы ARITHABORT прервал батч или транзакцию, нужно отключить ANSI_WARNINGS. При установке сервера по умолчанию эта опция включена во всех базах данных. Если Вы наблюдали прекращение батча при арифметической ошибке, то это только значит, что либо в опциях база данных, либо в опциях соединения ANSI_WARNINGS было явно выключено. У PRINT (или RAISERROR (1,...)) нет никакой особенной специфики на стороне сервера. Он не может отработаться если батч был прекращён. Поэтому, если если получен результат из PRINT, то процедура благополучно выполнила всё, что было до этого оператора. Все кажущиеся странности в порядке вывода сообщений - результат дизайна протокола TDS и конкретной клиентской библиотеки.
Cheers
zVlad
Уже с Приветом
Posts: 15311
Joined: 30 Apr 2003 16:43

Post by zVlad »

Ребята! А в DB2 с этим полный порядок (по крайней мере в DB2 for OS/390) в аналогичных ситуациях я лишь прошу (и помогаю) прикладных программеров внимательно читать сообщение, которое DB2 выдает и смотреть описание так называемых reason codes and tokens как правило присутствующих в них. Нет проблем с проблемами, иными словами.

Извините за офтопик.
Niky
Уже с Приветом
Posts: 550
Joined: 31 Mar 2000 10:01
Location: Moscow --> Baltimore, MD

Post by Niky »

tengiz wrote:
Niky wrote:2. ARITHABORT таки прерывает батч - попробуйте поставить хотя бы какой-нибудь SELECT после деления на 0 в приведенном примере - он не будет выполнен. PRINT - довольно специфический оператор, используемый больше для диагностики.

Как я уже писал, поведение при некоторых ошибках ещё зависит от текущих параметров соединения и текущей базы данных. Для того, чтобы ARITHABORT прервал батч или транзакцию, нужно отключить ANSI_WARNINGS. При установке сервера по умолчанию эта опция включена во всех базах данных. Если Вы наблюдали прекращение батча при арифметической ошибке, то это только значит, что либо в опциях база данных, либо в опциях соединения ANSI_WARNINGS было явно выключено. У PRINT (или RAISERROR (1,...)) нет никакой особенной специфики на стороне сервера. Он не может отработаться если батч был прекращён. Поэтому, если если получен результат из PRINT, то процедура благополучно выполнила всё, что было до этого оператора. Все кажущиеся странности в порядке вывода сообщений - результат дизайна протокола TDS и конкретной клиентской библиотеки.

Да, действительно, попробовал поставить INSERT после divide by zero - при ANSI_WARNINGS ON этот INSERT срабатывает, при ANSI_WARNINGS OFF - нет. Тогда, похоже, в BOL путаница?
SET ARITHABORT:
...
Remarks
If SET ARITHABORT is ON, these error conditions cause the query or batch to terminate. If the errors occur in a transaction, the transaction is rolled back. If SET ARITHABORT is OFF and one of these errors occurs, a warning message is displayed, and NULL is assigned to the result of the arithmetic operation.
...

SET ANSI_WARNINGS
...
Remarks
SET ANSI_WARNINGS affects these conditions:

When ON, if null values appear in aggregate functions (such as SUM, AVG, MAX, MIN, STDEV, STDEVP, VAR, VARP, or COUNT) a warning message is generated. When OFF, no warning is issued.


When ON, divide-by-zero and arithmetic overflow errors cause the statement to be rolled back and an error message is generated. When OFF, divide-by-zero and arithmetic overflow errors cause null values to be returned. The behavior in which a divide-by-zero or arithmetic overflow error causes null values to be returned occurs if an INSERT or UPDATE is attempted on a character, Unicode, or binary column in which the length of a new value exceeds the maximum size of the column. If SET ANSI_WARNINGS is ON, the INSERT or UPDATE is canceled as specified by the SQL-92 standard. Trailing blanks are ignored for character columns and trailing nulls are ignored for binary columns. When OFF, data is truncated to the size of the column and the statement succeeds.

...
User avatar
tengiz
Уже с Приветом
Posts: 4468
Joined: 21 Sep 2000 09:01
Location: Sammamish, WA

Post by tengiz »

Niky wrote:...похоже, в BOL путаница?

Там просто информация на эту тему неудачно структурирована и в результате разбросана по нескольким местам, поэтому это всё это и выглядит неочевидно. После параграфа в статье о SET ARITHABORT где говорится, что "If either SET ARITHABORT or SET ARITHIGNORE is OFF and SET ANSI_WARNINGS is ON, SQL Server still returns an error message when encountering divide-by-zero or overflow errors." нужно было сразу поместить суммарную таблицу из другой статьи на эту же тему или хотя бы ссылку на неё - Effect of ANSI_WARNINGS Setting:

Code: Select all

ARITHABORT ANSI_WARNINGS Behavior 
---------- ------------- ------------------------
ON         ON            Abort statement only.
ON         OFF           Abort batch.
OFF        ON            Abort statement only.
OFF        OFF           Continue; value is NULL.
Cheers
Niky
Уже с Приветом
Posts: 550
Joined: 31 Mar 2000 10:01
Location: Moscow --> Baltimore, MD

Post by Niky »

Спасибо

Return to “Вопросы и новости IT”