2016-07-19 9 views
0

I haben Werte in eine Tabelle mit diesen Spalten einzufügen: wird IdMasterIdZipCodeLocalityValue, CreatedTime, UpdatedTimeKann ich nur einfügen, wenn ein Datensatz in einer einzelnen Anweisung nicht existiert?

Ein eindeutiger Wert, der durch diese 3 Spalten definiert: MasterIdZipCodeLocality

Es gibt keine eindeutige Einschränkung auf die Datenbank über diese Spalten, aber ich muss sicherstellen, dass 2 Benutzer nicht die gleichen Daten eingeben können. Ich kann keine Änderungen am T-SQL-Datenbankschema vornehmen.

Aktuelle SQL:

INSERT INTO dbo.Carrier VALUES (?, ?, ?, ?, ?, GETDATE(), GETDATE()) 

My SQL Änderungen:

IF NOT EXISTS 
(
    SELECT TOP 1 * 
    FROM dbo.Carrier 
    WHERE MasterId = ? 
    AND ZipCode = ? 
    AND Locality = ? 
) 
BEGIN 
    INSERT INTO dbo.Carrier VALUES (?, ?, ?, ?, ?, GETDATE(), GETDATE()) 
END 

Diese Änderungen arbeiten für Standardbenutzereingabe, aber es gibt auch einen Service, der viele Einträge gleichzeitig einfügt. Manchmal versucht dieser Dienst, Tausende der gleichen eindeutigen Zeilen multithreadweise einzufügen, und das SQL hat eine Racebedingung, da es sich nicht um eine Anweisung handelt. Dadurch können Duplikate eingegeben werden, wenn 2 Anweisungen gleichzeitig ausgeführt werden.

Gibt es eine Möglichkeit, nur einzufügen, wenn ein Datensatz in einer einzelnen Anweisung nicht existiert?

Ich habe dieses Problem nicht auf UPDATE, weil ich kann eine wo UpdatedTime hat sich nicht geändert.

+0

Merge-Anweisung https://msdn.microsoft.com/en-us/library/bb510625(v=sql.110).aspx – Phritzy

+0

Die 'top 1 "sollte mit einer' exists'-Klausel völlig überflüssig sein und vielleicht ein paar zusätzliche Nano-Sekunden Aufwand für das Parsen der Abfrage verursachen. –

+0

Eigentlich ist es nicht garantiert, dass es auch mit Standard-Benutzereingaben funktioniert, es ist nur viel schwieriger, die Race Condition zu treffen. –

Antwort

6

Ich würde als eine einzige Anweisung tun dies not exists mit:

insert into dbo.Carrier (masterid, zipcode, locality, . . .) 
    select v.* 
    from (values (?, ?, ?, ...), (?, ?, ?, ...)) v(masterid, zipcode, locality, . . .) 
    where not exists (select 1 
         from dbo.Carrier c 
         where c.masterid = v.masterid and c.zipcode = v.zipcode and c.locality = v.locality 
        ); 

EDIT:

Wenn Sie die Datenbank für Rennbedingungen schützen möchten, sollten Sie den Schutz der Datenbank zu tun haben. Fügen Sie einen eindeutigen Index/Constraint für (masterid, zipcode, locality) hinzu. Dann sollten Sie einfach jeden Fehler ignorieren, der die Einschränkung verletzt.

+1

... was ich auch versuchen würde, obwohl Sie wahrscheinlich auch das erforderliche Transaktionslevel auflisten sollten. –

+0

Ihre Antwort bezieht sich nicht auf das Hauptproblem der Frage - die Race Condition. Ja, Sie haben eine einzige 'INSERT'-Anweisung geschrieben, aber sie ist genauso anfällig für die Race-Bedingung wie zwei separate Anweisungen. Eine Möglichkeit, es zu beheben, wäre es, es in die Transaktion einzubinden und 'WITH (UPDLOCK, HOLDLOCK)' Hinweise für das innere 'SELECT' zu verwenden. Weitere Informationen finden Sie unter http://weblogs.sqlteam.com/dang/archive/2007/10/28/Conditional-INSERTUPDATE-Race-Condition.aspx. –

+1

Ja, ich habe diesen Ansatz versucht und kann bestätigen, dass dies alleine nicht hilft, den Race Condition zu besiegen. – flip66

1

Eine MERGE statement Lösung so etwas wie dieses würde:

MERGE INTO Carrier dst 
USING (VALUES(?,?,?,...)) as src(MasterId, Zipcode, Locality,...) 
ON src.MasterId=dst.MasterId AND src.Zipcode=dst.Zipcode AND src.Locality=dst.Locality 
WHEN NOT MATCHED THEN INSERT (
    MasterId, Zipcode, Locality 
    , ... 
) VALUES (
    src.MasterId, src.Zipcode, src.Locality 
    , ... 
) 

Wenn Sie auch die Merge-Anweisung haben gewünscht könnte eine Modernisierung auf Zeilen gleichzeitig passende, wie es Zeilen fehlt Einsätze.

+0

'MERGE' kümmert sich auch nicht magischerweise um die Atomizität (Im Wesentlichen führt es immer noch separate Anweisungen aus, so dass Sie immer noch Nebenläufigkeitsprobleme haben. Außerdem ging es darum sicherzustellen, dass es keine neue Zeile mit einem passenden Schlüssel gibt. also bezweifle ich, blind eine bestehende Zeile zu aktualisieren wird helfen –

0

einen CTE verwenden und eine Join:

;with new_record as (
    select 1 as MasterId , 90065 as ZipCode, 1 as Locality 
) 
insert into Carrier(MasterId, ZipCode, Locality) 
select a.MasterId, a.ZipCode, a.Locality 
from new_record a 
left join Carrier b 
on a.MasterId = b.MasterId 
    and a.ZipCode = b.ZipCode 
    and a.Locality = b.Locality 
where b.MasterId is null 
+0

Technisch immer noch vermeidet die Race-Bedingung, die das eigentliche Problem ist. –