2009-09-27 3 views
6

Mit Hilfe von anderen auf SO habe ich heute morgen ein paar Tabellen und Stored Procedures, wie ich bin weit von einem DB-Programmierer.Ist diese gespeicherte Prozedur Thread-sicher? (oder was auch immer die Äquivalenz auf SQL Server ist)

Würde es jemandem etwas ausmachen, einen Blick darauf zu werfen und mir zu sagen, ob es threadsicher ist? Ich denke, das ist wahrscheinlich nicht der Begriff, den DBAs/DB-Entwickler verwenden, aber ich hoffe, Sie haben die Idee: Was passiert eigentlich, wenn dieser Computer ausgeführt wird und ein anderer zur selben Zeit kommt? Könnte man sich in den anderen einmischen? Ist das sogar ein Problem in SQL/SPs?

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

Ich stimme der Antwort von gbn vollständig zu, möchte aber hinzufügen, dass Sie dies selbst leicht herausfinden können - Sie können einfach Ihre gespeicherte Prozedur gleichzeitig von zwei oder mehr Verbindungen in einer Schleife laufen lassen (> 1mln) und nachsehen dich selber. –

Antwort

17

Sie wollen wahrscheinlich nicht IDENT_CURRENT mit sein - dies gibt die letzte Identität auf dem Tisch in Frage erzeugt wird, in jeder Sitzung und jedem Umfang. Wenn jemand anders zur falschen Zeit einen Einsatz macht, erhalten Sie stattdessen seine ID!

Wenn Sie die Identität erhalten möchten, die von der gerade durchgeführten Einfügung generiert wurde, verwenden Sie am besten die OUTPUT-Klausel, um sie abzurufen. Früher war es üblich SCOPE_IDENTITY() zu benutzen, aber es gibt Probleme mit parallelen Ausführungsplänen.

Das wichtigste SQL-Äquivalent zur Threadsicherheit ist, wenn mehrere Anweisungen ausgeführt werden, die unerwartetes oder unerwünschtes Verhalten verursachen. Die zwei Haupttypen von solchen Verhaltensweisen, die ich mir vorstellen kann, sind Sperren (insbesondere Deadlocks) und Nebenläufigkeitsprobleme.

Locking-Probleme treten auf, wenn eine Anweisung verhindert, dass andere Anweisungen auf die Zeilen zugreifen, mit denen sie arbeitet. Dies kann sich auf die Leistung auswirken, und im schlimmsten Fall führen zwei Anweisungen zu Änderungen, die nicht in Einklang gebracht werden können, und es kommt zu einem Deadlock, wodurch eine Anweisung beendet wird.

Eine einfache Einfügung wie die, die Sie haben, sollte jedoch keine Sperren verursachen, außer wenn etwas anderes betroffen ist (wie Datenbanktransaktionen).

Probleme mit der Parallelität (die sie sehr schlecht beschreiben) werden durch eine Reihe von Änderungen an Datenbanksätzen verursacht, die andere Änderungen an denselben Datensätzen überschreiben. Auch dies sollte kein Problem beim Einfügen eines Datensatzes sein.

+2

+1 für die Empfehlung von SCOPE_IDENTITY –

+1

-1 für die Empfehlung von SCOPE_IDENTITY; http://support.microsoft.com/kb/2019779. Sie sollten in allen Fällen die OUTPUT-Klausel verwenden. –

+0

@ MichaelJ.Gray danke - das habe ich behoben. Hatte noch nie von dem Problem gehört. Weißt du zufällig wann es entdeckt wurde? –

1

Zunächst einmal - warum geben Sie nicht immer die neue Ticketnummer statt 0? Ein besonderer Grund dafür?

Zweitens, um absolut sicher zu sein, sollten Sie Ihre INSERT und SELECT-Anweisung in eine TRANSACTION umbrechen, so dass nichts von außen eingreifen kann.

Drittens, mit SQL Server 2005 und höher, würde ich meine Anweisungen in einen TRY .... CATCH blockieren und Rollback der Transaktion, wenn es fehlschlägt.

Als nächstes würde ich versuchen, den Datenbankserver (TestDB42) in meinen Prozeduren wann immer möglich zu spezifizieren - was, wenn Sie diesen proc auf einen neuen Server (TestDB43) bereitstellen möchten ??

Und schließlich würde ich nie eine SET NOCOUNT in einer gespeicherten Prozedur verwenden - es kann dazu führen, dass der Anrufer fälschlicherweise denken, dass der gespeicherte Proc fehlgeschlagen ist (siehe mein Kommentar zu gbn unten - das ist ein potenzielles Problem, wenn Sie verwenden Nur ADO.NET-SqlDataAdapter-Objekte (weitere Informationen hierzu finden Sie unter MSDN docs zum Ändern von ADO.NET-Daten mit SqlDataAdapter).

So wäre mein Vorschlag für Ihre gespeicherte Prozedur sein:

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

Sie würden niemals SET NOCOUNT ON verwenden? "Ja wirklich?" – gbn

+0

Nun, wenn Sie DataAdapter von ADO.NET verwenden, kann die Verwendung von SET NOCOUNT ON dazu führen, dass der DataAdapter fälschlicherweise denkt, dass ein gespeicherter Proc fehlgeschlagen ist, da er den Rückgabewert als Anzahl der betroffenen Zeilen interpretiert - und wenn er 0 ist von SET NOCOUNT ON), dann interpretiert der DataAdapter das als "Stored Proc Failed". –

+0

Dieser Artikel ist 8 Jahre alt, also gilt vielleicht für v1.0 Punktnetz. Ich habe diesen Fehler seit Jahren nicht mehr gesehen: Ich würde annehmen, dass es jetzt behoben wurde ... – 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 

Ich würde nicht RETURN für eine sinnvolle Nutzung Daten verwenden: entweder Cord-oder Ausgabeparameter. RETURN würde normalerweise bei Fehlerzuständen verwendet werden (wie System gespeicherte Prozeduren in den meisten Fällen zu tun):

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

Sie können auch die OUTPUT-Klausel verwenden, anstatt einen Re-Cord zurückzukehren.

Dies ist "thread safe", dh es kann gleichzeitig ausgeführt werden.

1

Ich stimme mit der Antwort von David Hall überein, ich möchte nur ein wenig darauf eingehen, warum ident_current in dieser Situation absolut falsch ist.

Wir hatten hier einen Entwickler, der es benutzt hat. Die Einfügung aus der Clientanwendung erfolgte zur gleichen Zeit, zu der die Datenbank Millionen von Datensätzen durch einen automatisierten Import importierte. Die ID, die an ihn zurückgegeben wurde, stammte aus einer der Aufzeichnungen, die mein Prozess importierte. Er verwendete diese ID, um Datensätze für einige untergeordnete Tabellen zu erstellen, die nun an den falschen Datensatz angehängt waren. Schlimmer noch, wir haben jetzt keine Ahnung, wie oft das passiert ist, bevor jemand die Information nicht finden konnte, die in den Kindertabellen hätte sein sollen (sein Wechsel war seit mehreren Monaten auf prod). Nicht nur, dass mein automatisierter Import in seinen Code eingegriffen hätte, sondern ein anderer Benutzer, der zur gleichen Zeit einen Datensatz einlegte, hätte dasselbe tun können. Ident_current sollte nie verwendet werden, um die Identität eines gerade eingefügten Datensatzes zurückzugeben, da es nicht auf den Prozess beschränkt ist, der es aufruft.

+0

Das ist genau die Art von Sache, die ich vermeiden wollte, also vielen Dank für das Hinzufügen. – serialhobbyist

7

Der sicherste Weg hier zu gehen wäre wahrscheinlich, die Output-Klausel zu verwenden, da es unter Umständen einen Fehler in scope_endendity gibt (Multi/Parallel Processing).

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 

diese Weise können Sie Thread-sicher sein sollte, da die Ausgangs Klausel, um die Daten verwendet, die der Einsatz tatsächlich einfügt, und Sie werden keine Probleme auf Bereiche oder Sitzungen haben.

+0

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