2017年2月9日 星期四

[SQL Server]關於T-SQL的錯誤處理(Error Handling)

真沒想到這麼快就又見面了!
趁著記憶猶新,趕緊來記錄一下小弟我所發現關於SQL Server動態T-SQL的Error Handling現象。

一般來說,T-SQL錯誤處理的方法有兩種。

1. 使用@@ERROR系統全域變數:
此全域變數會記錄上一個T-SQL語句查詢的結果,也就是說隨著執行過程,@@ERROR會一直改變。
也就是說,如果是一串的SQL指令,每一句都需要做Error Handle的話,可能使用下一種方法會更為合適。
2. 使用TRY...CATCH:
就跟一般高階語言(C#、JAVA等)的用法類似,就不再贅述了...

進入重頭戲啦!

還記得先前做動態T-SQL建立資料庫嗎?回憶傳送門

先來看一段T-SQL的語法。
declare @DBName nvarchar(50), @Sqlcmd nvarchar(max)
set @DBName = N'HowardDB'

set @Sqlcmd = 
    'USE [master]

    CREATE DATABASE ['+@DBName+'] ON  PRIMARY 
    ( NAME = N'''+@DBName+''', 
    FILENAME = N''C:\Program Files\Microsoft SQL Server
    \MSSQL10.SQLEXPRESS\MSSQL\DATA\'+@DBName+'.mdf'' , 
    SIZE = 3072KB , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB )
    LOG ON 
    ( NAME = N'''+@DBName+'_log'', 
    FILENAME = N''C:\Program Files\Microsoft SQL Server
    \MSSQL10.SQLEXPRESS\MSSQL\DATA\'+@DBName+'_log.ldf'' , 
    SIZE = 1024KB , MAXSIZE = 2048GB , FILEGROWTH = 10%)'
 
-------------錯誤處理1-----------
BEGIN TRY
    execute sp_executesql @Sqlcmd
END TRY
BEGIN CATCH
    print ERROR_MESSAGE()
END CATCH
-----------錯誤處理2-----------
execute sp_executesql @Sqlcmd
if(@@ERROR<>0)
begin
    print 'Dymanic T-SQL Error'
end
這是一段動態組成建立資料庫的語法,底下有兩種執行動態T-SQL後錯誤處理的方法


可以看到兩種方法都可以成功取的錯誤並進行錯誤的處理。

那我們把這段動態語法做點手腳來執行看看吧~
declare @DBName nvarchar(50), @Sqlcmd nvarchar(max)
set @DBName = N'HowardDB'

set @Sqlcmd = 
    'USE [master]

    CREATE DATABASE ['+@DBName+'] ON  PRIMARY 
    ( NAME = N'''+@DBName+''', 
    FILENAME = N''C:\Program Files\Microsoft SQL Server
    \MSSQL10.SQLEXPRESS\MSSQL\DATA\'+@DBName+'.mdf'' , 
    SIZE = 3072KB , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB )
    LOG ON 
    ( NAME = N'''+@DBName+'_log'', 
    FILENAME = N''C:\Program Files\Microsoft SQL Server
    \MSSQL10.SQLEXPRESS\MSSQL\DATA\'+@DBName+'_log.ldf'' , 
    SIZE = 1024KB , MAXSIZE = 2048GB , FILEGROWTH = 10%)

    select @@version'
 
-------------錯誤處理1-----------
BEGIN TRY
    execute sp_executesql @Sqlcmd
END TRY
BEGIN CATCH
    print ERROR_MESSAGE()
END CATCH
-----------錯誤處理2-----------
execute sp_executesql @Sqlcmd
if(@@ERROR<>0)
begin
    print 'Dymanic T-SQL Error'
end

奇蹟的事情發生了!@@ERROR沒有擷取到錯誤!
原來是因為他機制的關係,他只會記錄上一個T-SQL語句查詢的結果(select @@version)。

再來觀察一個結果...


但仔細看可以發現,查詢結果只出現了一個查詢。

因為Try Catch則在T-SQL發生任何錯誤就不再執行接下來的查詢,直接進入到Catch的階段。

這次我們在建立資料庫的語法上亂搞試看看...
declare @DBName nvarchar(50), @Sqlcmd nvarchar(max)
set @DBName = N'HowardDB'

set @Sqlcmd = 
    'USE [master]

    CREAT DATABASE ['+@DBName+'] ON  PRIMARY 
    ( NAME = N'''+@DBName+''', 
    FILENAME = N''C:\Program Files\Microsoft SQL Server
    \MSSQL10.SQLEXPRESS\MSSQL\DATA\'+@DBName+'.mdf'' , 
    SIZE = 3072KB , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB )
    LOG ON 
    ( NAME = N'''+@DBName+'_log'', 
    FILENAME = N''C:\Program Files\Microsoft SQL Server
    \MSSQL10.SQLEXPRESS\MSSQL\DATA\'+@DBName+'_log.ldf'' , 
    SIZE = 1024KB , MAXSIZE = 2048GB , FILEGROWTH = 10%)

    select @@version'
 
-------------錯誤處理1-----------
BEGIN TRY
    execute sp_executesql @Sqlcmd
END TRY
BEGIN CATCH
    print ERROR_MESSAGE()
END CATCH
-----------錯誤處理2-----------
execute sp_executesql @Sqlcmd
if(@@ERROR<>0)
begin
    print 'Dymanic T-SQL Error'
end

很好,TRY-CATCH一直表現的非常良好,而@@ERROR這次也有擷取到錯誤了!
查了一些資料,原來這樣的語法錯誤屬於fatal error,錯誤等級很高,動態T-SQL直接終止,select @@version根本沒有執行。


總結一下:

1. @@ERROR只存放上一句T-SQL查詢的結果。若是動態T-SQL查詢也是同樣情形,除非錯誤等級很高。
2. Try Catch則在T-SQL發生任何錯誤(都擷取的到)就直接終止查詢(機制就跟C#、JAVA一樣)
3. 感覺各有優缺點,就看應用選擇吧~


p.s.今天跟同事討論,討論到關於寫程式遇到問題時的思維,發現難免都會依照過去經驗而有所謂「腦補」的現象。

例如這篇文章的內容,@@ERROR特性從認為在預存程序中不穩定、以為只有CREATE/ALTER相關語法才會無法擷取到最後筆記下來的最終內容。
有了這次的經驗,時間充裕的話,還是多去嘗試、了解,以免誤人子弟...

參考資料:
TechNet - 在 Transact-SQL 中使用 TRY...CATCH
https://technet.microsoft.com/zh-tw/library/ms179296(v=sql.105).aspx
@@ERROR not return anything for some error
http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=88760
Can not get @@ERROR after EXEC() with Error
http://stackoverflow.com/questions/10148061/can-not-get-error-after-exec-with-error


沒有留言:

張貼留言