| 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 YakalamaSQL 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
 |