T-SQL: un modo corretto per chiudere il cursore DEALLOCATE nel trigger di aggiornamento
-
05-07-2019 - |
Domanda
Diciamo che ho un trigger come questo:
CREATE TRIGGER trigger1
ON [dbo].[table1]
AFTER UPDATE
AS
BEGIN
--declare some vars
DECLARE @Col1 SMALLINT
DECLARE @Col1 TINYINT
--declare cursor
DECLARE Cursor1 CURSOR FOR
SELECT Col1, Col2 FROM INSERTED
--do the job
OPEN Cursor1
FETCH NEXT FROM Cursor1 INTO @Col1, @Col2
WHILE @@FETCH_STATUS = 0
BEGIN
IF ...something...
BEGIN
EXEC myProc1 @param1 = @Col1, @Param2 = @Col2
END
ELSE
IF ...something else...
BEGIN
EXEC myProc2 @param1 = @Col1, @Param2 = @Col2
END
FETCH NEXT FROM Cursor1 INTO @Col1, @Col2
END
--clean it up
CLOSE Cursor1
DEALLOCATE Cursor1
END
Voglio essere sicuro che Cursor1 sia sempre chiuso e deallocato. Anche myProc1 o myProc2 falliscono.
Devo usare il blocco try / catch?
Soluzione
Sì, usa TRY / CATCH ma assicurati di deallocare ecc. dopo. Sfortunatamente, non c'è finalmente in SQL Server.
Tuttavia, suggerisco di racchiuderlo in un altro tentativo / cattura
CREATE TRIGGER trigger1 ON [dbo].[table1] AFTER UPDATE
AS
BEGIN
--declare some vars
DECLARE @Col1 SMALLINT, @Col1 TINYINT
BEGIN TRY
--declare cursor
DECLARE Cursor1 CURSOR FOR
SELECT Col1, Col2 FROM INSERTED
--do the job
OPEN Cursor1
FETCH NEXT FROM Cursor1 INTO @Col1, @Col2
WHILE @@FETCH_STATUS = 0
BEGIN
IF ...something...
EXEC myProc1 @param1 = @Col1, @Param2 = @Col2
ELSE
IF ...something else...
EXEC myProc2 @param1 = @Col1, @Param2 = @Col2
FETCH NEXT FROM Cursor1 INTO @Col1, @Col2
END
END TRY
BEGIN CATCH
--do what you have to
END CATCH
BEGIN TRY
--clean it up
CLOSE Cursor1
DEALLOCATE Cursor1
END TRY
BEGIN CATCH
--do nothing
END CATCH
END
Il fatto che un cursore in un trigger sia una buona idea è una questione diversa ...
Altri suggerimenti
È possibile utilizzare la funzione CURSOR_STATUS ().
if CURSOR_STATUS('global','cursor_name') >= 0
begin
close cursor_name
deallocate cursor_name
end
riferimento : http://msdn.microsoft .com / it-it / library / ms177609.aspx
Quello che dovresti fare non è mai usare un cursore in un trigger. Scrivi invece il codice corretto basato su set. Se qualcuno eseguisse un'importazione di dati nella tua tabella di 100.000 nuovi record, blocceresti la tabella per ore e porteresti il ??tuo database a una battuta d'arresto. È una pratica molto scadente utilizzare un cursore in un trigger.
Dieci anni dopo, immagino che dovrei aggiungere alcune informazioni a questa particolare domanda.
Esistono due soluzioni principali al tuo problema. Innanzitutto, utilizzare una dichiarazione del cursore LOCAL
:
DECLARE --Operation
Cursor1 -- Name
CURSOR -- Type
LOCAL READ_ONLY FORWARD_ONLY -- Modifiers
FOR -- Specify Iterations
SELECT Col1, Col2 FROM INSERTED;
Questo limita il tuo particolare cursore solo alla sessione attiva, piuttosto che al contesto globale del server, supponendo che nessun'altra azione stia chiamando in questo cursore. Simile in linea di principio è usare una variabile cursore, che apparirebbe così:
DECLARE @Cursor1 CURSOR;
SET @Cursor1 = CURSOR LOCAL READ_ONLY FORWARD_ONLY FOR SELECT Col1, Col2 FROM INSERTED;
Usando una variabile cursore, puoi sempre sovrascriverla in qualsiasi momento usando la sintassi SET
, oltre a gestire l'ambito in modo che rientri nella tua sessione particolare come nell'esempio precedente. Sovrascrivendo il contesto del cursore, si dealloca in modo efficace qualsiasi riferimento passato che aveva. Detto questo, entrambi questi approcci realizzano la tua intenzione originale collegando lo stato del cursore all'attività della connessione corrente.
Ciò potrebbe lasciare un blocco persistente se il contesto dell'app utilizza Pool di connessioni, nel qual caso dovresti utilizzare il modello Try-Catch
come segue:
CREATE TRIGGER trigger1
ON [dbo].[table1]
AFTER UPDATE
AS
BEGIN
--declare some vars
DECLARE @Col1 SMALLINT;
DECLARE @Col2 TINYINT;
--declare cursor
DECLARE
Cursor1
CURSOR
LOCAL READ_ONLY FORWARD_ONLY
FOR
SELECT
Col1,
Col2
FROM
INSERTED;
--do the job
OPEN Cursor1;
BEGIN TRY
FETCH
NEXT
FROM
Cursor1
INTO
@Col1,
@Col2;
WHILE @@FETCH_STATUS = 0
BEGIN
IF -- my condition
EXEC myProc1 @param1 = @Col1, @Param2 = @Col2;
ELSE IF -- additional condition
EXEC myProc2 @param1 = @Col1, @Param2 = @Col2;
FETCH
NEXT
FROM
Cursor1
INTO
@Col1,
@Col2;
END;
END TRY
BEGIN CATCH
-- Error Handling
END CATCH
--clean it up
CLOSE Cursor1;
DEALLOCATE Cursor1;
END;
L'uso del pattern in questo modo riduce la duplicazione del codice o è necessario controllare lo stato del cursore. Fondamentalmente, l'inizializzazione del cursore dovrebbe essere sicura, come lo è la dichiarazione aperta. Una volta che il cursore è aperto, ti consigliamo di chiuderlo sempre a deallocare dalla sessione, e questa dovrebbe essere sempre un'azione sicura supponendo che il cursore sia stato aperto (che abbiamo appena stabilito dovrebbe essere sempre un'operazione sicura). In quanto tale, lasciare quelli al di fuori dei confini del Try-Catch
significa che tutto può essere accuratamente chiuso alla fine, dopo il blocco Catch
.
Vale la pena ricordare che ho specificato l'attributo READ_ONLY
del cursore, nonché FORWARD_ONLY
, poiché il tuo codice di esempio non scorreva avanti e indietro tra i record nel set. Se stai modificando le righe sottostanti in quelle procedure, probabilmente stai meglio usando un cursore STATIC
per assicurarti di non causare accidentalmente un ciclo infinito. Questo non dovrebbe essere un problema poiché stai usando la tabella INSERTED
per gestire il contesto del tuo cursore, ma vale comunque la pena menzionarlo per altri potenziali casi d'uso.
Se vuoi saperne di più sui cursori in SQL Server, consiglio vivamente di leggere questo post sul blog sull'argomento, mentre entra in dettaglio spiegando quali sono i vari modificatori di un cursore e gli effetti che hanno all'interno del Motore di database.