Was könnte dazu führen, dass die falsche ID eingefügt wird?

7

Ich habe einen SQL Server 2008-Server (Build 10.0.5500). Anfang dieser Woche habe ich dies für eine Tabelle ausgeführt , in der bereits Daten enthalten waren :

delete from dbo.table
go    
dbcc checkident('dbo.table',reseed,0)

Wenn der Benutzer später einen neuen Datensatz erstellen wollte, wurde irgendwie eine ID von 0 in die ID-Spalte eingefügt, anstatt der 1, die SQL Server normalerweise eingibt, wenn Identität (1,1) für die ID konfiguriert ist.

Dies verursachte einige seltsame Probleme, aber das Löschen der Daten und das Ausführen des Reseeds führte erwartungsgemäß dazu, dass eine 1 eingefügt wurde. Ich kann das Problem nicht duplizieren.

Als Referenz ist hier das allgemeine Format für unsere Sicherungssps:

alter procedure dbo._TableSave
    @pk_id int,
    @field varchar(50)
as
    if (@pk_id is null)
    begin
        set nocount on;

        insert into dbo.Table
        (
            Field
        )
        values
        (
            @field
        );
        select scope_identity();
    end
    else
    begin
        update dbo.Table
        set Field=@field
        where PK_ID=@pk_id

        select @pk_id
    end

Weiß jemand, was dazu führen kann, dass SQL Server eine 0 in die ID einfügt, wenn es eine 1 sein sollte?

Wir fügen keine Daten in Identitätsspalten (mit aktivierter Identitätseinfügung ) irgendwo in der Anwendung ein.

DForck42
quelle

Antworten:

10

Wenn Sie dies tun dbcc checkident('dbo.table',reseed,0), hat der nächste Eintrag in einer neu erstellten / abgeschnittenen Tabelle 0 als Identität.

        CREATE TABLE TestIdent
        (
            ID INT NOT NULL CONSTRAINT PK_TestIdent PRIMARY KEY CLUSTERED IDENTITY(1,1)
            , SomeText nvarchar(255)
        );

        dbcc checkident('dbo.TestIdent',reseed,0)

        INSERT INTO TestIdent VALUES ('Test');

        SELECT * FROM dbo.TestIdent;

Geben Sie hier die Bildbeschreibung ein

Dies bewirkt, dass die nächste eingegebene Identität 10 ist:

        TRUNCATE TABLE dbo.TestIdent;

        DBCC CHECKIDENT('dbo.TestIdent',reseed,10)

        INSERT INTO TestIdent VALUES ('Test');

        SELECT * FROM dbo.TestIdent;

Geben Sie hier die Bildbeschreibung ein

Max Vernon
quelle
4
Nur wenn die Tabelle neu erstellt wurde oder abgeschnitten wurde. Wenn Sie eine Zeile einfügen und löschen, erhalten Sie ein anderes ErgebnisCREATE TABLE dbo.[table] (id int identity(1,1));INSERT INTO dbo.[table] DEFAULT VALUES;DELETE FROM dbo.[table];dbcc checkident('dbo.table',reseed,0);INSERT INTO dbo.[table] OUTPUT inserted.* DEFAULT VALUES;DROP TABLE dbo.[table]
Martin Smith
Ich stimme zu, Martin. Ich glaube, die einzige Möglichkeit, eine 0 in einem Identitätsfeld zu erhalten, besteht darin, mit einer leeren / abgeschnittenen Tabelle zu beginnen.
Max Vernon
@MartinSmith Das Löschen des Datensatzes und das erneute Säen ist das Muster, dem ich gefolgt bin. Abschneiden funktioniert bei den meisten unserer Tabellen nicht, da sie FK-Beziehungen haben.
DForck42
9

Ich schlage den folgenden Code vor, um dies zu verhindern:
(Anstelle des Codes, den Sie in Ihrer Frage gepostet haben)

DELETE FROM dbo.table
GO
IF EXISTS 
(
    SELECT * 
    FROM sys.identity_columns 
    WHERE object_id = OBJECT_ID('dbo.table') 
        AND last_value IS NOT NULL
)
DBCC CHECKIDENT ('dbo.table', RESEED, 0);

Es sollte das "Nachsaaten auf 0" verhindern.

Erläuterung:
(Ich habe keine offizielle Dokumentation gefunden, die dies unterstützt ...)
Das Problem liegt in der Tabelle sys.syscolparsin einer Spalte mit dem Namen idtval
, die die Spalte enthält, die ALLE Informationen zur Identitätsspalte enthält.
Da die Tabelle eine Zeile für jede Spalte in der Datenbank (nicht nur die Identität Spalt) hat,
a NULLwird Wert in diesem Bereich eine Nichtidentität Spalte dar
( im Gegensatz zu sys.identity_columnsdenen Verwendungen NULLfür eine neue \ trunkiert Tabelle).

sys.syscolparsverwendet ein anderes Verfahren zum Herstellen des Informationshaltes:
es das uses 0x01000000010000000100000001Formats (Beispiel der Identität (1,1) neu erstellt wird ),
mit den linken 8 Stellen (Position 1-8 , nachdem die 0x) für Last_Value,
den nächsten 8 - stellig (Position 9-16 nach dem 0x) für increment_value,
die nächsten 8 Ziffern (Position 17-24 nach dem 0x) für seed_value,
die 2. Ziffer von rechts ist mir ein Rätsel,
und die 1. Ziffer von rechts - sagt uns, ob es sich um eine neue Tabelle handelt (= 1 ) oder nicht!

zurück zu unserem Problem -
wenn DBCC CHECKIDENTes ausgeführt wird, prüft es, sys.identity_columns.last_value
ob dies der Fall ist, NULLund aktualisiert dann den neuen Wert von sys.syscolpars.idtvalmit 1an der richtigen Position.
sys.identity_columns.last_valuebleibt NULL.
Dadurch wird der nächste Insert-Identitätswert wie in einer neuen Tabelle behandelt.
(Ich gehe davon aus, dass dies sys.identity_columnsunabhängig ist, sys.syscolparsda das erste sofort aktualisiert wird und das letztere erst aktualisiert wird, nachdem ein Prüfpunkt durchgeführt wurde.)

Da es keine Möglichkeit gibt, sys.syscolparsin Echtzeit abzufragen (es erfordert DAC), ist
mein Vorschlag die obige Bedingung, möglicherweise mit der Hinzufügung einer ELSEKlausel.

Auch hier kann ich nicht garantieren, dass eine dieser Erklärungen wahr ist, aber es funktioniert :)

Wenn jemand genauere Informationen über die DBCC CHECKIDENTHintergrundoperation hat,
würde ich gerne erfahren, bitte teilen!

Viel Glück,
רועי גביש

Roi Gavish
quelle
0
dbcc checkident('dbo.table',reseed,0)

Dieser Befehl bewirkt, dass der neue Identitätswert auf 0 gesetzt wird. Ich habe das gleiche Problem festgestellt.

dann wende ich so eine reseed identität auf 1 an

  dbcc checkident('dbo.table',reseed,1)
Muhammad Jawad
quelle
Ja, der erste setzt die ID auf 0. Wenn also der nächste Datensatz eingefügt wird, nimmt der SQL Server die aktuelle ID, addiert 1 und erhält 1. Die nächste ID, die ich erhalten würde, ist 2.
DForck42