2017-10-12 5 views
0

Le bloc catch ne s'exécute pas après la première modification apportée au script, mais fonctionne correctement à partir de la deuxième fois.Le bloc CATCH ne s'exécute pas la première fois mais s'exécute une seconde fois - Pourquoi?

Voici un script pour illustrer le problème.

use master 
go 

print 'rollback demo' 

declare @errordemo bit = 1 
select 'Transaction Count Before =', @@TRANCOUNT 
begin try 
    begin transaction 
    if (@errordemo = 0) select 'abc' as column1 into MyTestTable 
    insert into MyTestTable values ('xyz') 
    commit transaction 
end try 
begin catch 
    rollback transaction 
end catch 
go 

select 'Transaction Count After =', @@TRANCOUNT 
go 

-- this is only to bring back the system to its previous state 
if (@@TRANCOUNT > 0) rollback transaction 
go 
if exists(select * from sys.tables where name ='MyTestTable') drop table MyTestTable 
go 

Copiez le script ci-dessus SQL Server Management Studio et l'exécuter. Vous obtiendrez le résultat suivant avec une erreur.

Transaction Count Before = 0 
Transaction Count After = 1 

Appuyez sur F5 encore et encore et il exécutera avec succès:

Transaction Count Before = 0 
Transaction Count After = 0 

qui signifie que le bloc catch exécuté.

Maintenant commentez la première ligne print 'rollback demo', ou changez son texte. Vous recevrez à nouveau l'erreur. Appuyez à nouveau sur F5 n'importe quel nombre de fois et il n'y a pas d'erreur. Répétez en décommentant cette ligne (ou en faisant tout autre changement dans le script) et vous pouvez voir un modèle prévisible/reproductible.

Que se passe-t-il ici?

Voici quelques captures d'écran pour montrer ce qui se passe.

En cas de succès:

successful result 1 successful result 2

Quand échoué:

unsuccessful result 1 unsuccessful result 2

+0

Vous ne pouvez pas attraper les erreurs de 'nAME' d'objet non valide. – DavidG

+0

@DavidG, pourquoi n'y a-t-il pas d'erreur une deuxième fois? Il n'y a pas de changement dans le script, sauf qu'il est juste recompilé. –

Répondre

1

Ceci est parce que votre erreur est Erreur de liaison qui ne peut pas être prise en TRY..CATCH bloc. Lorsque vous référencez un objet inexistant, SQL Server n'essaie même pas de vérifier ses colonnes, il ne compile pas ce code, il le quitte pour l'exécution. Ceci est appelé deferred name resolution. Ce n'est que lorsqu'il s'agit de l'exécution de cette instruction, il vérifie la table et déclenche une erreur.

Il s'agit d'une erreur de compilation qui ne peut pas être détectée dans la même étendue (uniquement dans la portée externe). Il existe un élément de connexion que vous pouvez vérifier: Try-catch should capture the parse errors Par conséquent, votre bloc catch ne peut pas être atteint lorsque cette erreur est déclenchée.

Lors de l'exécution suivante, si aucun caractère de votre requête n'a été modifié, le plan mis en cache est utilisé. Donc, il n'est pas compilé lors de la seconde exécution.

Mais si vous changez votre texte de la requête (essayez d'ajouter - dans une partie de celui-ci), ou si vous demandez au serveur de ne pas mettre en cache le plan (en utilisant l'option recompile) comme ceci:

declare @errordemo bit = 1 
select 'Transaction Count Before =', @@TRANCOUNT 
begin try 
    begin transaction 
    if (@errordemo = 0) select 'abc' as column1 into dbo.MyTestTable 
    print @errordemo 
    insert into dbo.MyTestTable values ('xyz') 
    option (recompile) -------------------------------!!!!!!!!!!!!!!!!!!! 
    commit transaction 
end try 
begin catch 
    print 'catch block' 
    rollback transaction 
end catch 
go 

select 'Transaction Count After =', @@TRANCOUNT 
go 

if (@@TRANCOUNT > 0) rollback transaction 

le plan ne sera pas mis en cache et la requête sera compilée à chaque fois, et vous verrez une erreur de compilation à chaque fois et votre bloc catch ne sera jamais atteint.

Voici le cache plan: plan

Et là, vous pouvez voir qu'il n'y a pas de véritable plan d'insertion:

enter image description here

Voici comment véritable plan d'insertion ressemble: enter image description here

MISE À JOUR

J'ai essayé de reproduire la même chose avec SELECT requête et de trouver une différence dans le plan, mais je ne pouvais pas extraire le plan pour SELECT du cache plan. L'entrée pour elle existe, a la même taille que le plan INSERT, mais il est impossible de voir ce plan, il semble que ce n'est pas mis en mémoire cache, mais l'entrée existe ...

Pour le reproduire, vous pouvez utiliser la code suivant:

/*select query F7CA8D53-E171-4B5F-8CEA-B19461819C0D*/ 
declare @errordemo bit = 1 
select 'Transaction Count Before =', @@TRANCOUNT 
begin try 
    begin transaction 
    if (@errordemo = 0) select 'abc' as column1 into dbo.MyTestTable 
    print @errordemo 
    select * from MyTestTable 
    commit transaction 
end try 
begin catch 
    print 'catch block' 
    rollback transaction 
end catch 
go 

select 'Transaction Count After =', @@TRANCOUNT 
go 

if (@@TRANCOUNT > 0) rollback transaction; 
go 
------------------------------------------- 
------------------------------------------- 
/*insert query C7D24848-E2BB-46E7-8B1B-334406789CF9*/ 
declare @errordemo bit = 1 
select 'Transaction Count Before =', @@TRANCOUNT 
begin try 
    begin transaction 
    if (@errordemo = 0) select 'abc' as column1 into dbo.MyTestTable 
    print @errordemo 
    insert into MyTestTable values(1) 
    commit transaction 
end try 
begin catch 
    print 'catch block' 
    rollback transaction 
end catch 
go 

select 'Transaction Count After =', @@TRANCOUNT 
go 

if (@@TRANCOUNT > 0) rollback transaction 
go 
----------------------- 
----------------------- 
select * 
from sys.dm_exec_cached_plans p 
cross APPLY sys.dm_exec_query_plan(p.plan_handle) pl 
cross apply sys.dm_exec_sql_text (p.plan_handle) t 
where (t.text like '%F7CA8D53-E171-4B5F-8CEA-B19461819C0D%' -- select 
    or t.text like '%C7D24848-E2BB-46E7-8B1B-334406789CF9%')-- insert 
    and t.text not like '%sys.dm_exec_cached_plans%' 

enter image description here

+0

Je comprends que nous ne pouvons pas attraper des erreurs de liaison ou de compilation. Mais lorsqu'il y a une erreur de compilation, le plan ne devrait pas être mis en cache, n'est-ce pas? –

+0

Oui, il a chached. Il est mis en cache sans cette partie pour l'insertion car il est deffered. Je vais télécharger le plan et vous le verrez – sepupic

+0

++ Réponse agréable et utile. –