Frage

Ich habe eine Tabelle [Datei], die das folgende Schema hat

CREATE TABLE [dbo].[File]
(
    [FileID] [int] IDENTITY(1,1) NOT NULL,
    [Name] [varchar](256) NOT NULL,
 CONSTRAINT [PK_File] PRIMARY KEY CLUSTERED 
(
    [FileID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

Die Idee ist, dass der FileID als Schlüssel für die Tabelle verwendet wird und der Name ist der vollständig qualifizierte Pfad, der eine Datei darstellt.

Was habe ich versucht, eine gespeicherte Prozedur zu tun ist, erstellen, um zu sehen, wird überprüft, ob der Name bereits vergeben ist, wenn ja, dann diesen Datensatz verwenden sonst einen neuen Datensatz erstellen.

Aber wenn ich testen, betonen Sie den Code mit vielen Threads Ausführen der gespeicherten Prozedur auf einmal ich verschiedene Fehler auftreten.

Diese Version des Codes wird ein Deadlock erstellen und eine Deadlock-Ausnahme auf dem Client werfen.

CREATE PROCEDURE [dbo].[File_Create]
    @Name varchar(256)
AS
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    BEGIN TRANSACTION xact_File_Create
    SET XACT_ABORT ON

    SET NOCOUNT ON 
    DECLARE @FileID int
    SELECT @FileID = [FileID] FROM [dbo].[File] WHERE [Name] = @Name
    IF @@ROWCOUNT=0
    BEGIN
        INSERT INTO [dbo].[File]([Name])
        VALUES (@Name)
        SELECT @FileID = [FileID] FROM [dbo].[File] WHERE [Name] = @Name
    END

    SELECT * FROM [dbo].[File]
    WHERE [FileID] = @FileID

    COMMIT TRANSACTION xact_File_Create
GO

Diese Version des Codes ich mit den gleichen Daten in der Spalte Name immer Zeilen enden.

CREATE PROCEDURE [dbo].[File_Create]
    @Name varchar(256)
AS
    BEGIN TRANSACTION xact_File_Create

    SET NOCOUNT ON 
    DECLARE @FileID int
    SELECT @FileID = [FileID] FROM [dbo].[File] WHERE [Name] = @Name
    IF @@ROWCOUNT=0
    BEGIN
        INSERT INTO [dbo].[File]([Name])
        VALUES (@Name)
        SELECT @FileID = [FileID] FROM [dbo].[File] WHERE [Name] = @Name
    END

    SELECT * FROM [dbo].[File]
    WHERE [FileID] = @FileID

    COMMIT TRANSACTION xact_File_Create
GO

Ich frage mich, was ist der richtige Weg, um diese Art von Maßnahmen ist zu tun? In der Regel ist dies ein Muster, das ich verwenden mag, wo die Spaltendaten entweder in einer einzigen Spalte oder mehr Spalten einzigartig sind und ein andere Spalte wird als Schlüssel verwendet.

Danke

War es hilfreich?

Lösung

Wenn Sie sich stark auf das Namensfeld suchen, werden Sie wahrscheinlich wollen, dass es indiziert (so einzigartig, und vielleicht sogar gruppierten wenn dies der primäre Suchfeld). Da Sie nicht die @FileID aus der ersten Auswahl verwenden, würde ich nur SELECT COUNT (*) aus der Datei, wo Name = @Name und sehen, ob es größer als Null ist (dies wird von SQL von Halten alle Sperren auf dem Tisch verhindern die Suchphase, da keine Spalten ausgewählt werden).

Sie sind auf dem richtigen Weg mit der SERIALIZABLE Ebene, wie die Aktion nachfolgender Abfragen Erfolg oder Misserfolg mit dem Namen anwesend auswirken. Der Grund für die Version ohne diesen Satz Duplikate verursacht, ist, dass zwei wählt gleichzeitig lief und fand es gab keinen Rekord, so dass beide gingen voran mit den Einsätzen (die das Duplikat erstellt).

Der Deadlock mit der früheren Version ist höchstwahrscheinlich aufgrund des Fehlens eines Index macht der Suchvorgang eine lange Zeit in Anspruch nehmen. Wenn Sie den Server geladen werden unten in einem SERIALIZABLE Transaktion, alles andere muss warten, bis der Vorgang abgeschlossen. Der Index sollte macht die Bedienung schnell, aber nur zu Test zeigt an, ob es schnell genug ist. Beachten Sie, dass Sie auf die fehlgeschlagene Transaktion durch erneut einreichen reagieren können: die Last vorübergehend sein wird hoffentlich in realen Situationen.

EDIT: Ihre Tabelle indiziert Indem, aber nicht mit SERIALIZABLE, am Ende mit drei Fällen bis:

  • Name gefunden wird, ID erfasst und verwendet wird. Allgemein
  • Name nicht gefunden wird, fügt wie erwartet. Allgemein
  • Name nicht gefunden wird, einfügen schlägt fehl, da eine weitere genaue Übereinstimmung innerhalb von Millisekunden nach dem ersten gebucht wurde. Sehr selten

ich würde der letzte Fall erwartet wirklich außergewöhnlich sein, also eine Ausnahme mit diesem sehr seltenen Fall zu erfassen wäre vorzuziehen Eingriff SERIALIZABLE, was schwerwiegende Performance Folgen hat.

Wenn Sie wirklich eine Erwartung haben, dass es üblich sein wird, Beiträge innerhalb von Millisekunden voneinander aus den gleichen neuen Namen haben, dann eine SERIALIZABLE Transaktion in Verbindung mit dem Index verwenden. Es wird im allgemeinen Fall langsamer sein, aber schneller, wenn diese Einträge gefunden werden.

Andere Tipps

Sie zunächst einen eindeutigen Index für den Spalt Namen erstellen. Dann von Ihrem Client-Code überprüfen Sie zuerst, ob der Name vorhanden ist, indem die FileID auswählen und den Namen in der where-Klausel setzen - wenn es, die FileID verwenden. Falls nicht, legen Sie einen neuen.

Mit der Exists-Funktion könnte Dinge aufzuräumen ein wenig.

if (Exists(select * from table_name where column_name = @param)
begin
  //use existing file name
end
else
  //use new file name
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top