Try-Catch Statement, SQL Server’daki yeni bir özelliktir. Birbiri ardına yerleştirilmesi gereken tek bir Try (deneme) bloktan ve tek bir Catch (yakalama) bloktan oluşur. Try blokta diğer Transact-SQL ifadelerden bir grup oluşturabilir/iliştirebilirsiniz. Eğer Try blokta bir hata olursa kontrol Catch bloktaki ifade gruplarına transfer edilir. Doğal olarak bir hata yakalandığı ve kontrol edilebildiği zaman (Catch blockta yürütülen ifadeler tarafından) idare eden kişiye raporlanmaz. Catch bloğun yürütülmesi tamamlandığı zaman makine kontrolü tekrar Catch bloktan sonraki ilk ifadeye verir. (Bazı istisnaları da vardır. Eğer Try bloktaki bir ifadenin yürütülmesi sorunsuz olarak tamamlandıysa Catch bloktaki ifadeler atlanacaktır ve makine Catch bloktan sonraki ifadeleri yürütmeye/işlemeye başlayacaktır.
Create Procedure dbo.ap_ChargeLog_Insert
@ItemId int,
@ActionId smallint,
@Cost money,
@Note varchar(max),
@Activity varchar(1000)
as
BEGIN TRY
INSERT [dbo] . [ChargeLog] ([ItemId] , [ActionId] , [ChargeDate] ,
[Cost],[Note]}
VALUES (@ItemId, @ActionId, GetDateO, @Cost, @Note)
INSERT INTO [dbo] . [ActivityLog] ([Activity] , [LogDate],
[UserName],[Note]}
VALUES (@Activity, GetDateO, system_user, @Note)
END TRY
BEGIN CATCH
INSERT INTO [dbo] . [ErrorLog] ([ErrorNum] , [ErrorType] ,
[ErrorMsg],[ErrorSource])
VALUES (50000,’E’, ’Unable to record transaction in ChargeLog.’,
’ap_ChargeLog_Insert’ )
END CATCH
Stored Procedure bir işlem (transaction) açar ve iki Insert ifade işlemeye çalışır. Eğer ifadelerden biri ya da ikisi de başarısız olursa bir işlem (transaction) geri sarar(rollback) ve kayıt özel bir tabloda tutulur.
Hata Yakalama
SQL Server 2005 oldukça fazla türde hatayı bulma konusunda kendisinden öncekilerden daha başarılıdır. Şimdi ne türün hataların yakalandığını ve hangilerinin yakalanamadığını anlatacağım. The Try-Catch ifadesi aşağıdaki uyarıları yakalamaz:
BEGIN TRY
Print ’Begin try’
INSERT [dbo] . [ChargeLog] ( [ItemId] , [ActionId] , [ChargeDate] , [Cost] , [Note] )
VALUES ( 30 , 15 , GetDate (), $150 , null)
raiserror ( ’Some Error!’ , 10 , 1 )
INSERT INTO [dbo] . [ActivityLog] ( [Activity] , [LogDate] , [UserName] , [Note] )
VALUES ( ’Repair’ , GetDate (), system_user , null)
Print ’End try’
END TRY
BEGIN CATCH
print ’Catch’
INSERT INTO [dbo] . [ErrorLog] ( [ErrorNum] , [ErrorType] , [ErrorMsg] , [ErrorSource] )
VALUES ( 50000 , ’E’ , ’Unable to record transaction in
ChargeLog.’,’ap_ChargeLog_Insert’)
END CATCH
Print ’Finished!’
|
Eğer yukarıdaki örneği uygularsanız göreceksiniz ki Try blok uyarıya aldırmayacak ve hiçbir şey olmamış gibi devam edecektir:
Begin try
(1 row(s) affected)
Some Error!
(1 row(s) affected)
End try
Finished!
|
Önceki versiyonlarda yaşanan büyük problem; çok ciddi hataların otomatik olarak işlemin kesintiye sebep olmasıydı. SQL Server’ın bu sürümü ciddi hataların bulunması ve çözülmesinde çok daha iyi! (17 veya üstü önem derecesiyle). Eğer bağlantı kopmamış ise SQL Server makinesi hatayı atlayarak işleme devam etmeye çalışacaktır. Aşağıdaki örnekte ciddi bir hata canlandıracağım:
BEGIN TRY
print ’Begin try’
raiserror (’Some Error!’, 23, 1}
print ’End try’
END TRY
BEGIN CATCH
print ’Catch’
END CATCH
Print ’Finished!’
|
SQL Server 2005 bu hatayı yakalayıp Catch bloktaki kodla devam edecektir:
Begin try
Catch
Finished!
|
Şimdi buna eşdeğer kodu SQL Server 2000’de deneyelim:
print ’start’
Raiserror ( ’Some error!’ , 23 , 1 } With LOG
if @@error <> 0
print ’Error detected!’
|
Bu durumda, SQL Server 2000 bağlantıyı otomatik olarak koparacaktır:
start
Server: Msg 50000, Level 23, State 1, Line 2
Some error!
Connection Broken
|
SQL Server’ın önceki sürümleri önem derecesi 17den düşük olan hataları bile işlemede başarısız oldu. Aşağıdaki örnekte tamsayı değişkene bir tarih atayacağım:
declare @i int
print ’start’
set @i = ’2/2/2005’
if @@error <> 0
print ’error occurred’
print ’finished’
|
Ne yazık ki SQL Server 2000 yerleşik prosedür veya dizi işlemini aniden durduruyor:
start
Server: Msg 245, Level 16, State 1, Line 4
Syntax error converting the varchar value ’2/2/2005’ to a column of data type int.
|
Şimdi buna eşdeğer kodu SQL Server 2005’te deneyelim:
BEGIN TRY
print ’Begin try’
declare @i int
set @i = ’2/2/2’
print ’End try’
END TRY
BEGIN CATCH
print ’Catch’
END CATCH
print ’Finished’
|
Beklendiği gibi makine hatayı yakaladı:
Catch Blok Fonksiyonları
Sadece Catch block içinde çalışan özel hata kontrol fonksiyonları bulunmaktadır:
Error_Message() Returns the error message that would normally be returned to the caller application
Error_Number() Returns the identifier of the error
Error_Severity() Returns the severity
Error_State() Returns the state Error_Procedure()Returns the name of the procedure (or other programmatic database object) in which the error has occurred
Error_Line() Returns the line number of the procedure in which the error has occurred
Bu fonksiyonların önemli bir yeni özelliği de (@@Error ile kıyaslandığında) değerlerini Catch blok içinde tutmasıdır. Bunlara birçok defa başvurabilirsiniz. Bu fonksiyonlar hatanın sorgulanması ve aynı zamanda bir problem varsa yöneticinin sorundan haberdar edilmesi için önemlidir. Örneğin, aşağıdaki işlem bu fonksiyonları error logda depolanacak özel bir hata mesajı oluşturmak için kullanır ve ardından idareciyi uyarmak için hatayı bildirir:
Alter Procedure dbo.ap_ChargeLog_Insert2
@ItemId int
@ActionId smallint,
@Cost money,
@Note varchar(max),
@Activity varchar(1000)
as
BEGIN TRY
INSERT [dbo].[ChargeLog]([ItemId],[ActionId],[ChargeDate],
[Cost],[Note])
VALUES (@ItemId, @ActionId, GetDate(),
@Cost, @Note)
INSERT INTO [dbo].[ActivityLog]([Activity],[LogDate],
[UserName] , [Note])
VALUES(@Activity, GetDate(),
system_user, @Note)
END TRY
BEGIN CATCH
declare @severity int
set @severity = Error_Severity()
declare @msg varchar(255)
set @msg = ’Unable to record transaction in ChargeLog.’
+ ’Error(’ + ERROR_NUMBER() + ’):’ + ERROR_MESSAGE()
+ ’ Severity = ’ + ERROR_SEVERITY()
+ ’ State = ’ + ERROR_STATE()
+ ’ Procedure = ’ + ERROR_PROCEDURE()
+ ’ Line num. = ’ + ERROR_LINE()
INSERT INTO [dbo] . [ErrorLog] ([ErrorNum] , [ErrorType] , [ErrorMsg] , [ErrorSource])
VALUES (ERROR_NUMBER(), ’E’, @msg, ERROR_PROCEDURE())
RAISERROR (@msg, @severity, 2)
END CATCH
Return
|
Catch blocktaki son ifade hatayı yöneticiye tekrar bildirmek içindir. Raiserror()da the Error_Severity() kullanılamaz. Sadece değerler ve değişkenler Raiserror()de izinlidir. Net işlemlerle Try-Catch Statement (Try-Catch Statement with Explicit Transactions) Try-Catch işlemler ve ifadelerle ilgili aklınızda bulundurmanız gereken ilk şey sihirli hiçbir şey olmadığıdır Catch blokta işlemi manüel olarak geri sarmanız gerekir.
Alter Procedure dbo.ap_ChargeLog_Insert_wTran
@ItemId int,
@ActionId smallint,
@Cost money,
@Note varchar(max),
@Activity varchar(1000)
as
BEGIN TRY
BEGIN TRAN
INSERT [dbo] . [ChargeLog] ([ItemId] , [ActionId] , [ChargeDate],
[Cost],[Note])
VALUES (@ItemId, @ActionId, GetDate(), @Cost, @Note)
INSERT INTO dbo.ActivityLog(Activity, LogDate, UserName, Note)
VALUES (@Activity, GetDate(), system_user, @Note)
COMMIT TRAN
END TRY
BEGIN CATCH
ROLLBACK TRAN
INSERT dbo.ErrorLog(ErrorNum,[ErrorType],[ErrorMsg],[ErrorSource])
VALUES (50000,’E’, ’Unable to record transaction in ChargeLog.’,
ERROR_PROCEDURE(}}
END CATCH
return
|
Teorik olarak bir hata meydana geldiğinde işlemi(transaction) ileri boyutlarda sorgulamak mümkündür. Hiçbir işlem(transaction) açık değilse fonksiyonlar 0’a döner. Bir işlem(transaction) açıksa 1’e döner ve işlenebilir veya geri sarılabilir. -1’e döndüğünde açık işlem(transaction) işlenemez. Bu durumda üzerinde yapılacak hiçbir değişiklik mümkün değildir. Bu durum tipik olarak serverda dramatik bir şey olduğunda meydana gelir (örneğin bir işlem (transaction) logunun dolması). Data okunabilir ve bütün kilitler yerinde muhafaza edilir/sürdürülür (böylece her şeyi sorgulayabilirsiniz). Fakat modifikasyonlara devam etmek için geri sarmalısınız.
Aşağıdaki yerleşik prosedürün ne kadar mantıklı ya da açıklayıcı olduğu tartışılır ancak Xact_State() fonksiyonunun kullanımını canlandırmaktadır. İşlem karmaşıktır, birçok Insert ve Select ifadelerinden ibarettir. Bu nedenle, Catch block daha fazla karmaşıktır. İlk önce işlemin yürütülemez ya da kapalı olduğu basit durumları idare eder. Bu durumlarda hata kontrolü daha kolay anlaşılır durumdadır işlem (transaction) geri sarılmalı veya görmezden gelinmelidir. En ilginç olan durum işlemin yürütülebilir olduğu durumlardır. İşlem veya geri alıma karar vermeden önce ilave kontroller yapmanız gerekmektedir:
Alter Procedure dbo.ap_ChargeLog_Insert_wTranState
@ItemId int,
@ActionId smallint,
@Cost money,
@Note varchar (max) ,
@Activity varchar(1000)
as
declare @Today smalldatetime
declare @User sysname
declare @ErrorCode int
declare @EqId int
declare @Price money
BEGIN TRY
select @Today = GetDate()
set @User = system_user
BEGIN TRAN
INSERT [dbo].[ChargeLog]([ItemId],[ActionId],[ChargeDate],
[Cost] , [Note])
VALUES (@ItemId, @ActionId, @Today, @Cost, @Note)
select @EqId = EqId from dbo.Orderltem
where ItemId = @ItemId
select @EqId = EqId from dbo.OrderItem
where ItemId = @ItemId
select @Price = Price
from dbo.PriceList
where EqId = @EqId
INSERT INTO dbo.Sales(EqId, [UnitPrice], [Qty], [ExtPrice] ,[SalesDate])
VALUES (@EqId, @Price, 1, @Price, @today)
INSERT INTO dbo.ActivityLog(Activity, LogDate, UserName, Note)
VALUES (@Activity, @Today , @User, @Note)
COMMIT TRAN
END TRY
BEGIN CATCH
set @ErrorCode = Error_Number()
if xact_state() = -1
begin
-- transaction is uncommittable
ROLLBACK TRAN
INSERT dbo.ErrorLog(ErrorNum, ErrorType, ErrorMsg, ErrorSource, ErrorState)
VALUES (@ErrorCode, ’E’, ’Unable to record transaction in ChargeLog.’,
ERROR_PROCEDURE(), -1)
end
else if xact_state() = 0
begin
--error occurred before tran started
INSERT dbo.ErrorLog(ErrorNum, ErrorType, ErrorMsg, ErrorSource, ErrorState)
VALUES (@ErrorCode,’E’, ’Unable to pre-process ChargeLog transaction.’,
ERROR_PROCEDURE(), 0)
end
else if xact_state() = 1
begin
--error could be committed or rolled back
commit tran
if exists(select * from dbo.ActivityLog
where Activity = @Activity
and LogDate = @Today
and UserName = @User)
begin
INSERT dbo.ErrorLog(ErrorNum, ErrorType, ErrorMsg, ErrorSource, ErrorState)
VALUES (@ErrorCode,’E’, ’Unable to record transaction in ActivityLog.’,
ERROR_PROCEDURE(), 1)
end
if exists(select * from dbo.Sales
where EqId = @Activity
and [SalesDate] = @Today)
begin
INSERT dbo.
VALUES ’E’,’Unable to record transaction in’,
ERROR_PROCEDURE(), 1)
end
end
END CATCH
return @ErrorCode
|
Not:Bu prosedür tasarımının savunulabilir olduğunu düşünmüyorum. İşlem yürütülebilirken error idaresi yapmak için ekstra kontroller yapmak yerine Try bloğu bir çok Try bloğa bölebilmeli/parçalayabilmeli ve her durumu/olayı ayrı bir Catch blockta halledebilmeliydim. Veya işlemin yürütülmesi için bütün adımların tamamlanmaması gerekiyorsa o zaman işlem iki veya daha fazla işleme bölünebilmeli.
Deadlock Retries
Try-Catch blockların SQL Server 2005’te yakalayabildiği geniş yelpazedeki hataların bir sonucu da Transact-SQL’de kilitlenmenin üstesinden gelebilecek bir script yaratabilmenizdir.
Deadlocks
Kilitlenme, bağlantılar aynı anda kaynaklar için rekabete girip birbirlerinin işlemlerini bloke ettiklerinde SQL Server’da ortaya çıkan bir durumdur. Kilitlenmenin pek çok türü vardır. Genelde oluşan durumlar:
* Bağlantı 1, A kaynağını (tablo) kilitler ve onu değiştirir.
* Bağlantı 2, B kaynağını (tablo) kilitler ve onu değiştirir.
* Bağlantı 1, B tablosu üzerinde kilit elde etmeye çalışır ancak bağlantı 2’nin işlemini tamamlamasını beklemesi gerekir.
* Bağlantı 2, A tablosu üzerinde kilit elde etmeye çalışır ancak bağlantı 1’in işlemini tamamlamasını beklemesi gerekir.
* SQL Server kilitlenme tespit eder ve bağlantılardan birini koparmaya karar verir. Error 1502 ortaya çıkar.
* Diğer bağlantı işlemini tamamlar.
Öncelikle bir kilitlenme simulasyonu yapmaya çalışalım. Her biri aralarında iki modification ifade ve bir WaitFor ifade içeren iki stored procedure yarattım. WaitFor ifadenin amacı uygulamalarının üst üste binmesi için "stored procedures çalıştırırken Management Studio’yu işlettiğimde bana 10 saniye kazandırmasıdır. Stored procesures aynı tabloya zıt sıralamayla giriş yaparlar:
Alter Procedure [dbo].[ap_SalesByDate_IncreasePrice]
@Factor real,
@Date smalldatetime
as
set xact_abort on
begin tran
update dbo.Sales
set UnitPrice = UnitPrice * @Factor,
ExtPrice = ExtPrice * @Factor
where SalesDate = @Date
waitfor delay ’0:00:10’
update dbo.PriceList
set Price = Price * @Factor
commit tran
return
GO
ALTER procedure [dbo].[ap_PriceByEqId_Set]
@EqId int,
@Price money
as
set xact_abort on
begin tran
update dbo.PriceList
set Price = @Price
where EqId = @EqId
waitfor delay ’0:00:10’
update dbo.Sales
set UnitPrice = @Price,
ExtPrice = @Price * Qty
where EqId = @EqId
commit tran
return
|
Management Studioda iki Query penceresinden prosedürleri aynı anda yönetirseniz (birinciden sonra ikinciyi başlatmak için 10 saniyeniz var) bir süre sonra SQL Server kilitlenme tespit edecek ve diğerinin devam edebilmesi için bağlantılardan birini kesecektir.
SQL Serverın eski versiyonlarında kilitlenme hatalarını kopan bağlantılarla sonuçlanıyordu. SQL Server 2005’te diğer hatalar gibi tespit edilebiliyorlar. Şimdi hatayı tespit etmek için bir prosedürü değiştirelim ve loop taki gecikmenin ardından yeniden işlemeyi deneyelim:
ALTER procedure [dbo].[ap_PriceByEqId_Set_wRetry]
@EqId int,
@Price money
as
-- exec ap_PriceByEq!d_Set_wRetry 1, $16.82
declare @i int
set @i = 1
while @i <= 10
begin
begin try
set xact_abort on
begin tran
update dbo.PriceList
set Price = @Price
where EqId = @EqId
waitfor delay ’0:00:10’
update dbo.Sales
set UnitPrice = @Price,
ExtPrice = @Price * Qty
where EqId = @EqId
commit tran
------------
print ’completed’
break
end try
begin catch
if ERROR_NUMBER() = 1205
begin
rollback tran
set @i = @i + 1
print ’retry’
INSERT INTO [dbo].[ErrorLog]([ErrorNum],[ErrorType],[ErrorMsg]
, [ErrorSource] , [CreatedBy] , [CreateDT] , [ErrorState])
VALUES(Error_Number(), ’E’, Error_Message(),
Error_Procedure () , suser_sname () , GetDateO, Error_State ())
waitfor delay ’0:00:03’
end
end catch
end
print ’Completed’
return
|
Bu stored procedure’ı çalıştırdığınızda hatadan kurtulur.
Not
Bu tür kilitlenme hatasına cycle deadlock adı verilir. Cycle deadlockları idare edebilmenin en iyi yolu modifiye edilmiş tabloların sırasını değiştirmektir. Sıralamayı değiştirmek kilitlenmenin oluşmasını engeller. Retry, kilitlenme için başvurulacak son yöntemdir ve sadece kullanımı kaçınılmazsa kullanılmalıdır (key deadlockı durumunda olduğu gibi)
Try-Catch Statement Nesting
Try-Catch ifadeleri iç içe koymak mümkündür. Örneğin, bir Try-Catch ifade Try blok içinde olabilir. Aynı zamanda Try blok içindeki bir Exec ifadeden Try-Catch blok olan bir stored procedure çağırmak da mümkündür. Hata meydana geldiğinde makine Catch bloktaki son ifadeleri çalıştırmaya başlayacaktır. Özellikle belirtmek istediğim bir özellik var: Catch blockta hata yakalandığı ve düzeltildiği zaman SQL Server işleme nereden devam edecek? Daha önce açıkladığım gibi eğer dizinin tek bir Try-Catch bloku varsa o zaman işlem Catch bloktan sonraki ilk ifadeden devam ettirilecektir. Try-Catch block olmayan iç içe yerleştirilmiş stored procedure durumunda errorü işleyen Catch bloktan sonraki ifadeden devam ettirilecektir. Ancak iç içe geçirilmiş procedurun Try-catch bloku varsa onun Catch bloku hatayı işleyecek fakat işlem iç içe geçirilen prosedürü kuran Exec ifadeden sonraki ilk ifadeden devam edecektir. (iç içe geçirilmiş Catch’ten sonraki ilk ifadeden değil)
Error Handling Architecture
Hata yönetimini farklı yönlerini inceledik ancak önemli olan soru; gelecek projenizde standartlar veya yönergeler için ne kullanmalısınız? Try-Catch statement oldukça güçlüdür ve önceki sürümlerde tespit etmenin mümkün olduğundan çok daha fazla problemi tespit edebilir. İki seçeneğiniz var. Birincisi, Try-Catch ifadelerdeki her stored procedure’ı paketlemeye/sarmalamaya karar verebilirsiniz v Diğer çözüm ; tüm hatalar için değil, sadece bazı anlamlı hata yönetimi uygulayacağınız yerlerde Try-Catch statement kullanmaktır. Bu tür bir mimaride beklenmedik hatalar client uygulama (veya özel yazılım bileşenleri) tarafından yönetilmelidir.
Microsoft SQL Server 2005 Stored Procedure Programming in T-SQL & .NET, Third Edition by Dejan Sunderi
Makale:
SQL Server 2005' te Try Catch Kullanımı ADO.NET ve SQL İsmail Yurtsever
|