2009-09-27 6 views
6

Con l'aiuto di altri su SO ho stoppato un paio di tabelle e stored procedure, stamattina, dato che sono lontano da un programmatore DB.Questa stored procedure è thread-safe? (o qualunque cosa equiv sia su SQL Server)

Qualcuno dispiacerebbe lanciare un occhio su questo e dirmi se è thread-safe? Immagino che probabilmente non è il termine usato dagli sviluppatori di DBA/DB, ma spero che tu abbia l'idea: in pratica, cosa succede se questa sp è in esecuzione e un'altra arriva allo stesso tempo? Si potrebbe interferire con l'altro? Si tratta di un problema anche in SQL/SP?

CREATE PROCEDURE [dbo].[usp_NewTicketNumber] 
    @ticketNumber int OUTPUT 
AS 
BEGIN 
    SET NOCOUNT ON; 
    INSERT INTO [TEST_Db42].[dbo].[TicketNumber] 
       ([CreatedDateTime], [CreatedBy]) 
     VALUES 
       (GETDATE(), SUSER_SNAME()) 
    SELECT @ticketNumber = IDENT_CURRENT('[dbo].[TicketNumber]'); 
    RETURN 0; 
END 
+0

Sono completamente d'accordo con la risposta di gbn, ma vorrei aggiungere che si può facilmente capire da soli - è possibile eseguire la procedura memorizzata contemporaneamente da due o più connessioni in un ciclo mnay volte (> 1mln), e vedere per te stesso. –

risposta

17

Probabilmente non si vuole essere utilizzare IDENT_CURRENT - restituisce l'ultima identità generato sul tavolo in questione, in ogni sessione e qualsiasi ambito. Se qualcun altro fa un inserto nel momento sbagliato, otterrete invece il loro id!

Se si desidera ottenere l'identità generata dall'inserto appena eseguito, è consigliabile utilizzare la clausola OUTPUT per recuperarla. Solitamente era usuale SCOPE_IDENTITY() per questo, ma ci sono problemi con i piani di esecuzione paralleli.

L'equivalente SQL principale della sicurezza del thread si ha quando vengono eseguite più istruzioni che causano un comportamento imprevisto o indesiderato. I due principali tipi di comportamento che posso pensare sono il blocco (in particolare deadlock) e problemi di concorrenza.

I problemi di blocco si verificano quando un'istruzione arresta altre istruzioni dall'accesso alle righe con cui sta lavorando. Ciò può influire sulle prestazioni e nello scenario peggiore due istruzioni apportano modifiche che non possono essere riconciliate e si verifica un deadlock, causando la chiusura di un'istruzione.

Tuttavia, un inserto semplice come quello che hai non dovrebbe causare blocchi a meno che non sia coinvolto qualcos'altro (come le transazioni del database).

I problemi di concorrenza (che li descrivono molto male) sono causati da un insieme di modifiche ai record del database che sovrascrivono altre modifiche agli stessi record. Ancora una volta, questo non dovrebbe essere un problema quando si inserisce un record.

+2

+1 per la raccomandazione di SCOPE_IDENTITY –

+1

-1 per la raccomandazione di SCOPE_IDENTITY; http://support.microsoft.com/kb/2019779. Y'all dovrebbe usare la clausola OUTPUT in tutti i casi. –

+0

@ MichaelJ.Gray grazie - L'ho risolto. Non avevo mai sentito parlare del problema prima. Ti capita di sapere quando è stato scoperto? –

1

Prima di tutto - perché non si restituisce sempre il nuovo numero di biglietto anziché 0 per tutto il tempo? Qualche ragione particolare per quello?

In secondo luogo, per essere assolutamente sicuri, è necessario avvolgere l'istruzione INSERT e SELECT in un'operazione, in modo che nulla dall'esterno possa intervenire.

In terzo luogo, con SQL Server 2005 e versioni successive, vorrei avvolgere le mie istruzioni in un PROVA .... blocco CATCH e ripristinare la transazione se non riesce.

Successivamente, proverei ad evitare di specificare il server database (TestDB42) nelle mie procedure ogni volta che è possibile - cosa succede se si desidera distribuire tale proc su un nuovo server (TestDB43) ??

E infine, non utilizzerei mai un SET NOCOUNT in una procedura memorizzata - può far sì che il chiamante pensi erroneamente che il proc memorizzato non sia riuscito (vedere il mio commento a gbn qui sotto - questo è un potenziale problema se si sta usando Solo oggetti ADO.NET SqlDataAdapter: vedere lo MSDN docs su come modificare i dati ADO.NET con SqlDataAdapter per ulteriori spiegazioni).

Quindi il mio suggerimento per la vostra stored procedure potrebbe essere:

CREATE PROCEDURE [dbo].[usp_NewTicketNumber] 
AS 
BEGIN 
    DECLARE @NewID INT 

    BEGIN TRANSACTION 
    BEGIN TRY 
    INSERT INTO 
     [dbo].[TicketNumber]([CreatedDateTime], [CreatedBy]) 
    VALUES 
     (GETDATE(), SUSER_SNAME()) 

    SET @NewID = SCOPE_IDENTITY() 

    COMMIT TRANSACTION 
    END TRY 
    BEGIN CATCH 
    ROLLBACK TRANSACTION 
    SET @NewID = -1 
    END CATCH 

    RETURN @NewID 
END 

Marc

+0

Non useresti mai SET NOCOUNT ON? Veramente? – gbn

+0

Bene, se si utilizza DataAdapter di ADO.NET, l'utilizzo di SET NOCOUNT ON può indurre il DataAdapter a pensare erroneamente che un processo memorizzato non sia riuscito poiché interpreta il valore restituito come conteggio "righe interessate" e se è 0 (nel caso di SET NOCOUNT ON), quindi il DataAdapter lo interpreta come "stored proc failed". –

+0

Questo articolo ha 8 anni, quindi forse si applica a v1.0 dot net. Non vedo questo errore da anni: suppongo che sia stato risolto per ora ... – gbn

3
CREATE PROCEDURE [dbo].[usp_NewTicketNumber] 
    @NewID int OUTPUT 
AS 
BEGIN 
    SET NOCOUNT ON; 
    BEGIN TRY 
     BEGIN TRANSACTION 
     INSERT INTO 
      [dbo].[TicketNumber] ([CreatedDateTime], [CreatedBy]) 
     VALUES 
      (GETDATE(), SUSER_SNAME()) 

     SET @NewID = SCOPE_IDENTITY() 

     COMMIT TRANSACTION; 
    END TRY 
    BEGIN CATCH 
     IF XACT_STATE() <> 0 
      ROLLBACK TRANSACTION; 
     SET @NewID = NULL; 
    END CATCH 
END 

Non vorrei usare RETURN per i dati di utilizzo significativo: o recordset o parametro di uscita. RITORNO sarebbe normalmente utilizzato per gli stati di errore (come sistema memorizzato procs fanno nella maggior parte dei casi):

EXEC @rtn = EXEC dbo.uspFoo 
IF @rtn <> 0 
    --do error stuff 

È inoltre possibile utilizzare la clausola OUTPUT per restituire un set di record, invece.

Questo è "thread safe", ovvero può essere eseguito contemporaneamente.

1

Sono d'accordo con la risposta di David Hall, voglio solo espandere un po 'sul perché ident_current è assolutamente la cosa sbagliata da usare in questa situazione.

Abbiamo uno sviluppatore qui che lo ha usato. L'inserimento dall'applicazione client è avvenuto nello stesso momento in cui il database stava importando milioni di record tramite un'importazione automatizzata. L'ID restituito a lui proveniva da uno dei record importati dal mio processo. Ha usato questo id per creare record per alcune tabelle figlio che ora erano associate al record sbagliato. Peggio ancora, ora non abbiamo idea di quante volte ciò sia accaduto prima che qualcuno non potesse trovare le informazioni che avrebbero dovuto essere nei tavoli dei bambini (il suo cambiamento era stato sul prod per diversi mesi). Non solo la mia importazione automatizzata avrebbe potuto interferire con il suo codice, ma un altro utente che inseriva un record al momento giusto avrebbe potuto fare la stessa cosa. Ident_current non dovrebbe mai essere utilizzato per restituire l'identità di un record appena inserito in quanto non è limitato al processo che lo chiama.

+0

Questo è esattamente il tipo di cosa che stavo cercando di evitare, grazie mille per averlo aggiunto. – serialhobbyist

7

Il modo più sicuro per andare qui sarebbe probabilmente quello di utilizzare la clausola Output, dal momento che c'è un bug noto in scope_idendity in determinate circostanze (elaborazione multi/parallela).

CREATE PROCEDURE [dbo].[usp_NewTicketNumber] 
AS 
BEGIN 
    DECLARE @NewID INT 

    BEGIN TRANSACTION 
    BEGIN TRY 
    declare @ttIdTable TABLE (ID INT) 
    INSERT INTO 
     [dbo].[TicketNumber]([CreatedDateTime], [CreatedBy]) 
    output inserted.id into @ttIdTable(ID) 
    VALUES 
     (GETDATE(), SUSER_SNAME()) 

    SET @NewID = (SELECT id FROM @ttIdTable) 

    COMMIT TRANSACTION 
    END TRY 
    BEGIN CATCH 
    ROLLBACK TRANSACTION 
    SET @NewID = -1 
    END CATCH 

    RETURN @NewID 
END 

In questo modo si dovrebbe essere thread-safe, dal momento che la clausola di uscita utilizza i dati che l'inserto inserti in realtà, e non avrà problemi in tutta scopi o sessioni.

+0

Collegamento a MSKB scope_identity: http://support.microsoft.com/default.aspx?scid=kb;en-US;2019779 –