Introduzione
Non importa quanto ci sforziamo di progettare e sviluppare applicazioni, gli errori si verificheranno sempre. Esistono due categorie generali:sintassi o errori logici possono essere errori programmatici o conseguenze di una progettazione errata del database. In caso contrario, potresti ricevere un errore a causa dell'input errato dell'utente.
T-SQL (il linguaggio di programmazione di SQL Server) consente di gestire entrambi i tipi di errore. Puoi eseguire il debug dell'applicazione e decidere cosa devi fare per evitare bug in futuro.
La maggior parte delle applicazioni richiede la registrazione degli errori, l'implementazione di una segnalazione degli errori intuitiva e, quando possibile, la gestione degli errori e la continuazione dell'esecuzione dell'applicazione.
Gli utenti gestiscono gli errori a livello di istruzioni. Significa che quando si esegue un batch di comandi SQL e il problema si verifica nell'ultima istruzione, tutto ciò che precede quel problema verrà eseguito nel database come transazioni implicite. Questo potrebbe non essere ciò che desideri.
I database relazionali sono ottimizzati per l'esecuzione di istruzioni batch. Pertanto, è necessario eseguire un batch di istruzioni come un'unità e fallire tutte le istruzioni se un'istruzione fallisce. Puoi farlo usando le transazioni. Questo articolo si concentrerà sia sulla gestione degli errori che sulle transazioni, poiché questi argomenti sono fortemente collegati.
Gestione degli errori SQL
Per simulare le eccezioni, dobbiamo produrle in modo ripetibile. Iniziamo con l'esempio più semplice:divisione per zero:
SELECT 1/0
L'output descrive l'errore generato:Dividi per zero errore riscontrato . Ma questo errore non è stato gestito, registrato o personalizzato per produrre un messaggio intuitivo.
La gestione delle eccezioni inizia inserendo le istruzioni che vuoi eseguire nel blocco BEGIN TRY…END TRY.
SQL Server gestisce (cattura) gli errori nel blocco BEGIN CATCH…END CATCH, in cui puoi inserire una logica personalizzata per la registrazione o l'elaborazione degli errori.
L'istruzione BEGIN CATCH deve seguire immediatamente dopo l'istruzione END TRY. L'esecuzione viene quindi passata dal blocco TRY al blocco CATCH al primo verificarsi dell'errore.
Qui puoi decidere come gestire gli errori, se vuoi registrare i dati sulle eccezioni sollevate o creare un messaggio intuitivo.
SQL Server dispone di funzioni integrate che potrebbero aiutarti a estrarre i dettagli dell'errore:
- ERROR_NUMBER():restituisce il numero di errori SQL.
- ERROR_SEVERITY():restituisce il livello di gravità che indica il tipo di problema riscontrato e il relativo livello. I livelli da 11 a 16 possono essere gestiti dall'utente.
- ERROR_STATE():restituisce il numero dello stato di errore e fornisce maggiori dettagli sull'eccezione generata. Utilizzare il numero di errore per cercare nella Knowledge Base di Microsoft i dettagli di errore specifici.
- ERROR_PROCEDURE():restituisce il nome della procedura o del trigger in cui è stato generato l'errore, oppure NULL se l'errore non si è verificato nella procedura o nel trigger.
- ERROR_LINE():restituisce il numero di riga in cui si è verificato l'errore. Potrebbe essere il numero di riga delle procedure o dei trigger o il numero di riga nel batch.
- ERROR_MESSAGE():restituisce il testo del messaggio di errore.
L'esempio seguente mostra come gestire gli errori. Il primo esempio contiene la Divisione per zero errore, mentre la seconda affermazione è corretta.
BEGIN TRY
PRINT 1/0
SELECT 'Correct text'
END TRY
BEGIN CATCH
SELECT ERROR_NUMBER() AS ERR_NO
, ERROR_SEVERITY() AS ERR_SEV
, ERROR_STATE() AS ERR_STATE
, ERROR_LINE() AS ERR_LINE
, ERROR_MESSAGE() AS ERR_MESSAGE
END CATCH
Se la seconda istruzione viene eseguita senza la gestione degli errori (SELECT 'Correggi testo'), riuscirebbe.
Poiché implementiamo la gestione degli errori personalizzata nel blocco TRY-CATCH, l'esecuzione del programma viene passata al blocco CATCH dopo l'errore nella prima istruzione e la seconda non è mai stata eseguita.
In questo modo è possibile modificare il testo fornito all'utente e controllare meglio cosa succede se si verifica un errore. Ad esempio, registriamo gli errori in una tabella di registro per ulteriori analisi.
Utilizzo delle transazioni
La logica aziendale potrebbe determinare che l'inserimento della prima istruzione non riesce quando la seconda istruzione ha esito negativo o che potrebbe essere necessario ripetere le modifiche della prima istruzione in caso di errore della seconda istruzione. L'utilizzo delle transazioni ti consente di eseguire un batch di istruzioni come un'unità che non riesce o riesce.
L'esempio seguente mostra l'utilizzo delle transazioni.
Innanzitutto, creiamo una tabella per testare i dati archiviati. Quindi utilizziamo due transazioni all'interno del blocco TRY-CATCH per simulare le cose che accadono se una parte della transazione fallisce.
Useremo l'istruzione CATCH con l'istruzione XACT_STATE(). La funzione XACT_STATE() viene utilizzata per verificare se la transazione esiste ancora. Nel caso in cui la transazione venga ripristinata automaticamente, la TRANSAZIONE ROLLBACK produrrebbe una nuova eccezione.
Avere un bottino al codice qui sotto:
-- CREATE TABLE TEST_TRAN(VALS INT)
BEGIN TRY
BEGIN TRANSACTION
INSERT INTO TEST_TRAN(VALS) VALUES(1);
COMMIT TRANSACTION
BEGIN TRANSACTION
INSERT INTO TEST_TRAN(VALS) VALUES(2);
INSERT INTO TEST_TRAN(VALS) VALUES('A');
INSERT INTO TEST_TRAN(VALS) VALUES(3);
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF XACT_STATE() > 0 ROLLBACK TRANSACTION
SELECT ERROR_NUMBER() AS ERR_NO
, ERROR_SEVERITY() AS ERR_SEV
, ERROR_STATE() AS ERR_STATE
, ERROR_LINE() AS ERR_LINE
, ERROR_MESSAGE() AS ERR_MESSAGE
END CATCH
SELECT * FROM TEST_TRAN
-- DROP TABLE TEST_TRAN
L'immagine mostra i valori nella tabella TEST_TRAN e i messaggi di errore:
Come vedi, è stato commesso solo il primo valore. Nella seconda transazione, si è verificato un errore di conversione del tipo nella seconda riga. Pertanto, l'intero batch è stato ripristinato.
In questo modo puoi controllare quali dati entrano nel database e come vengono elaborati i batch.
Generazione di messaggi di errore personalizzati in SQL
A volte, vogliamo creare messaggi di errore personalizzati. Di solito, sono pensati per scenari in cui sappiamo che potrebbe verificarsi un problema. Possiamo produrre i nostri messaggi personalizzati dicendo che è successo qualcosa di sbagliato senza mostrare i dettagli tecnici. Per questo, utilizziamo la parola chiave THROW.
BEGIN TRY
IF ( SELECT COUNT(sys.all_objects) > 1 )
THROW ‘More than one object is ALL_OBJECTS system table’
END TRY
BEGIN CATCH
SELECT ERROR_NUMBER() AS ERR_NO
, ERROR_SEVERITY() AS ERR_SEV
, ERROR_STATE() AS ERR_STATE
, ERROR_LINE() AS ERR_LINE
, ERROR_MESSAGE() AS ERR_MESSAGE
END CATCH
Oppure, vorremmo avere un catalogo di messaggi di errore personalizzati per la categorizzazione e la coerenza del monitoraggio e della segnalazione degli errori. SQL Server ci consente di predefinire il codice del messaggio di errore, la gravità e lo stato.
Una procedura memorizzata denominata "sys.sp_addmessage" viene utilizzata per aggiungere messaggi di errore personalizzati. Possiamo usarlo per chiamare il messaggio di errore in più punti.
Possiamo chiamare RAISERROR e inviare il numero del messaggio come parametro invece di codificare gli stessi dettagli dell'errore in più punti del codice.
Eseguendo il codice selezionato dal basso, aggiungiamo l'errore personalizzato in SQL Server, lo eleviamo e quindi utilizziamo sys.sp_dropmessage per eliminare il messaggio di errore definito dall'utente specificato:
exec sys.sp_addmessage @msgnum=55000, @severity = 11,
@msgtext = 'My custom error message'
GO
RAISERROR(55000,11,1)
GO
exec sys.sp_dropmessage @msgnum=55000
GO
Inoltre, possiamo visualizzare tutti i messaggi in SQL Server eseguendo il modulo di query seguente. Il nostro messaggio di errore personalizzato è visibile come primo elemento nel set di risultati:
SELECT * FROM master.dbo.sysmessages
Crea un sistema per registrare gli errori
È sempre utile registrare gli errori per il debug e l'elaborazione successivi. Puoi anche inserire trigger su queste tabelle registrate e persino configurare un account e-mail e diventare un po' creativo nel modo di notificare le persone quando si verifica un errore.
Per registrare gli errori, creiamo una tabella chiamata DBError_Log , che può essere utilizzato per memorizzare i dati di dettaglio del registro:
CREATE TABLE DBError_Log
(
DBError_Log_ID INT IDENTITY(1, 1) PRIMARY KEY,
UserName VARCHAR(100),
ErrorNumber INT,
ErrorState INT,
ErrorSeverity INT,
ErrorLine INT,
ErrorProcedure VARCHAR(MAX),
ErrorMessage VARCHAR(MAX),
ErrorDateTime DATETIME
);
Per simulare il meccanismo di registrazione, stiamo creando il GenError stored procedure che genera la Divisione per zero error e registra l'errore nel DBError_Log tabella:
CREATE PROCEDURE dbo.GenError
AS
BEGIN TRY
SELECT 1/0
END TRY
BEGIN CATCH
INSERT INTO dbo.DBError_Log
VALUES
(SUSER_SNAME(),
ERROR_NUMBER(),
ERROR_STATE(),
ERROR_SEVERITY(),
ERROR_LINE(),
ERROR_PROCEDURE(),
ERROR_MESSAGE(),
GETDATE()
);
END CATCH
GO
EXEC dbo.GenError
SELECT * FROM dbo.DBError_Log
Il DBError_Log la tabella contiene tutte le informazioni necessarie per eseguire il debug dell'errore. Fornisce inoltre ulteriori informazioni sulla procedura che ha causato l'errore. Anche se questo può sembrare un esempio banale, puoi estendere questa tabella con campi aggiuntivi o utilizzarla per riempirla con eccezioni personalizzate.
Conclusione
Se vogliamo mantenere ed eseguire il debug delle applicazioni, vogliamo almeno segnalare che qualcosa è andato storto e registrarlo anche sotto il cofano. Quando disponiamo di un'applicazione a livello di produzione utilizzata da milioni di utenti, la gestione degli errori coerente e segnalabile è la chiave per eseguire il debug dei problemi in runtime.
Sebbene sia possibile registrare l'errore originale nel registro degli errori del database, gli utenti dovrebbero visualizzare un messaggio più amichevole. Pertanto, sarebbe una buona idea implementare messaggi di errore personalizzati che vengono lanciati alle applicazioni chiamanti.
Qualunque sia il progetto implementato, è necessario registrare e gestire le eccezioni di utenti e di sistema. Questa attività non è difficile con SQL Server, ma è necessario pianificarla dall'inizio.
L'aggiunta delle operazioni di gestione degli errori ai database già in esecuzione in produzione potrebbe comportare seri refactoring del codice e problemi di prestazioni difficili da trovare.