Fügen Sie nur Deadlocks ein

7

Wir haben eine Anwendung, die Daten in eine Tabelle einfügt. Leider bekommen wir Deadlocks und die Deadlocks kommen nur von Inserts. Wir sehen, dass die Einfügungen Schlüsselsperren in einer anderen Reihenfolge für einen nicht gruppierten Index annehmen, was das Problem verursacht.

Warum verhalten sich die Einsätze so und was sollten wir tun, um die Deadlocks zu lindern? Jede Hilfe oder Einsicht wird geschätzt.

Im folgenden Beispiel sind nur zwei Einfügungen beteiligt, es waren jedoch bis zu 4 verschiedene Einfügungen an einem Deadlock beteiligt.

Hier ist das Deadlock-Diagramm:

    <deadlock>
    <victim-list>
        <victimProcess id="process3ab355868" />
    </victim-list>
    <process-list>
        <process id="process3ab355868" taskpriority="0" logused="1184" waitresource="KEY: 5:72057594043629568 (6234ed5bf036)" waittime="7493" ownerId="92332106" transactionname="implicit_transaction" lasttranstarted="2014-10-13T12:37:43.060" XDES="0x123699668" lockMode="X" schedulerid="3" kpid="3540" status="suspended" spid="89" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2014-10-13T12:37:44.333" lastbatchcompleted="2014-10-13T12:37:44.333" lastattention="1900-01-01T00:00:00.333" clientapp="Microsoft JDBC Driver for SQL Server" hostname="" hostpid="0" loginname="" isolationlevel="read committed (2)" xactid="92332106" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128058">
            <executionStack>
                <frame procname="adhoc" line="1" stmtstart="278" stmtend="818" sqlhandle="0x0200000053a65d302154b91e9fee55234669030a42479c050000000000000000000000000000000000000000">
                    INSERT INTO table (col1, col2, col3, col4, col5, col6, col7, col8, col9) VALUES (@P0, @P1, @P2, @P3, @P4, @P5, @P6, @P7, @P8)
                </frame>
                <frame procname="unknown" line="1" sqlhandle="0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000">
                    unknown
                </frame>
            </executionStack>
            <inputbuf>
                (@P0 datetime2,@P1 nvarchar(4000),@P2 nvarchar(4000),@P3 datetime2,@P4 nvarchar(4000),@P5 nvarchar(4000),@P6 decimal(38,1),@P7 int,@P8 int)INSERT INTO table (col1, col2, col3, col4, col5, col6, col7, col8, col9) VALUES (@P0, @P1, @P2, @P3, @P4, @P5, @P6, @P7, @P8)                                                                         select SCOPE_IDENTITY() AS GENERATED_KEYS
            </inputbuf>
        </process>
        <process id="process14b38c928" taskpriority="0" logused="2564" waitresource="KEY: 5:72057594043629568 (275232b7b238)" waittime="7491" ownerId="92325909" transactionname="implicit_transaction" lasttranstarted="2014-10-13T12:37:39.567" XDES="0x16b38b988" lockMode="X" schedulerid="3" kpid="3668" status="suspended" spid="65" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2014-10-13T12:37:44.337" lastbatchcompleted="2014-10-13T12:37:44.337" lastattention="1900-01-01T00:00:00.337" clientapp="Microsoft JDBC Driver for SQL Server" hostname="" hostpid="0" loginname="" isolationlevel="read committed (2)" xactid="92325909" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128058">
            <executionStack>
                <frame procname="adhoc" line="1" stmtstart="278" stmtend="818" sqlhandle="0x0200000053a65d302154b91e9fee55234669030a42479c050000000000000000000000000000000000000000">
                    INSERT INTO table (col1, col2, col3, col4, col5, col6, col7, col8, col9) VALUES (@P0, @P1, @P2, @P3, @P4, @P5, @P6, @P7, @P8)
                </frame>
                <frame procname="unknown" line="1" sqlhandle="0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000">
                    unknown
                </frame>
            </executionStack>
            <inputbuf>
                (@P0 datetime2,@P1 nvarchar(4000),@P2 nvarchar(4000),@P3 datetime2,@P4 nvarchar(4000),@P5 nvarchar(4000),@P6 decimal(38,1),@P7 int,@P8 int)INSERT INTO table (col1, col2, col3, col4, col5, col6, col7, col8, col9) VALUES (@P0, @P1, @P2, @P3, @P4, @P5, @P6, @P7, @P8)                                                                         select SCOPE_IDENTITY() AS GENERATED_KEYS
            </inputbuf>
        </process>
    </process-list>
    <resource-list>
        <keylock hobtid="72057594043629568" dbid="5" objectname="table1" indexname="unique_index" id="lock17bc3a480" mode="X" associatedObjectId="72057594043629568">
            <owner-list>
                <owner id="process14b38c928" mode="X" />
            </owner-list>
            <waiter-list>
                <waiter id="process3ab355868" mode="X" requestType="wait" />
            </waiter-list>
        </keylock>
        <keylock hobtid="72057594043629568" dbid="5" objectname="table1" indexname="unique_index" id="lock10735ce00" mode="X" associatedObjectId="72057594043629568">
            <owner-list>
                <owner id="process3ab355868" mode="X" />
            </owner-list>
            <waiter-list>
                <waiter id="process14b38c928" mode="X" requestType="wait" />
            </waiter-list>
        </keylock>
    </resource-list>
</deadlock>

Hier ist die Tabelle DDL:

CREATE TABLE [table1](
    [col0] [int] IDENTITY(1,1) NOT NULL,
    [col1] [int] NOT NULL,
    [col2] [int] NOT NULL,
    [col3] [decimal](15, 4) NULL,
    [col4] [datetime2](7) NOT NULL,
    [col5] [varchar](8) NOT NULL,
    [col6] [varchar](30) NOT NULL,
    [col7] [datetime2](7) NOT NULL,
    [col8] [varchar](8) NOT NULL,
    [col9] [varchar](30) NOT NULL,
 CONSTRAINT [PK] PRIMARY KEY CLUSTERED 
(
    [col0] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
 CONSTRAINT [unique_index] UNIQUE NONCLUSTERED 
(
    [col2] ASC,
    [col1] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

SET ANSI_PADDING OFF
GO

ALTER TABLE table1 ADD  DEFAULT (sysdatetime()) FOR [col4]
GO

ALTER TABLE table1 ADD  DEFAULT (sysdatetime()) FOR [col7]
GO
GoodwinSQL
quelle
Sind diese Beilagen Teil einer größeren Transaktion?
Aaron Bertrand
Die Einfügungen sind Teil von Transaktionen, die jeweils etwa 6 oder 7 dieser Einfügungen ausführen.
GoodwinSQL
Könnten Sie den gesamten Code anzeigen? Es ist schwierig, nur mit den Deadlock-Informationen Fehler zu beheben, da nicht alle Details erfasst werden.
Aaron Bertrand
Leider habe ich momentan keinen Zugriff auf den gesamten Code.
GoodwinSQL
Also, wenn die Art und Weise, wie der Code aufgebaut ist, das Problem ist, wie werden Sie es beheben?
Aaron Bertrand

Antworten:

7

Ich beantworte hier meine eigene Frage, weil wir das Problem endlich herausgefunden haben.

Kurzversion : Wir haben dem nicht gruppierten Index eine dritte Spalte hinzugefügt. Deadlocks verschwanden.

Lange Version:

Schauen Sie sich zuerst James Rowland-Jones 'Dynamit-Blog-Post über Lock-Hashing-Kollisionen an (meine Erklärung wird seiner Qualität bei weitem nicht nahe kommen).

Aus dem Blogbeitrag:

Wenn SQL Server eine Zeile sperren muss, wird ein Hashwert erstellt, der auf den Schlüsselwerten der Tabelle basiert. Dieser Hash-Wert wird vom Sperrmanager verwendet und bedeutet, dass ein einzelner Wert angezeigt werden muss, wenn überprüft wird, ob eine Zeile bereits gesperrt ist.

Die Sperren-Hash-Kollision tritt auf, wenn doppelte Hash-Werte generiert werden.

Nach einer eingehenderen Analyse vieler Deadlock-Diagramme stellten wir fest, dass viele der WAITRESOURCE-Schlüssel-Hashwerte (die Werte in Klammern) gleich waren. Ich fing an, eine kurze Liste zu erstellen, um den Überblick zu behalten:

waitresource="KEY: 5:72057594043629568 (a27543d90a1a)
waitresource="KEY: 5:72057594043629568 (a27543d90a1a)
waitresource="KEY: 5:72057594043629568 (8328314847df)
waitresource="KEY: 5:72057594043629568 (bb0d06c12baa)
waitresource="KEY: 5:72057594043629568 (a27543d90a1a)
waitresource="KEY: 5:72057594043629568 (bb0d06c12baa)
waitresource="KEY: 5:72057594043629568 (8328314847df)
waitresource="KEY: 5:72057594043629568 (bb0d06c12baa)
waitresource="KEY: 5:72057594043629568 (a27543d90a1a)
waitresource="KEY: 5:72057594043629568 (5b39284eef16)
waitresource="KEY: 5:72057594043629568 (a27543d90a1a)
waitresource="KEY: 5:72057594043629568 (8328314847df)
waitresource="KEY: 5:72057594043629568 (5b39284eef16)

Sicher genug, wir haben viele doppelte Hash-Werte aus verschiedenen Deadlock-Diagrammen erhalten. Ich beschloss, die Daten in den beiden Spalten (col2 & col1) des unique_index-Index (wo die Deadlocks auftraten) zu untersuchen. Die gesamte Tabelle DDL befindet sich oben in der Frage.

Die Spalte col2 hat immer einen Wert von 1-6 für einen einzelnen Wert in der Spalte col1. Das machte also Sinn. Für SQL war eine begrenzte Anzahl von Daten verfügbar, aus denen Hash-Werte generiert werden konnten. Dies erklärt, warum wir doppelte Hash-Werte erhalten haben.

Eines des Updates JRJ erwähnte im Blog war eine zusätzliche Spalte mit dem Index hinzuzufügen. Dies fügt den Daten eine gewisse Vielfalt hinzu und bietet mehr Optionen für den Hashing-Algorithmus. Glücklicherweise konnten wir dem Index eine create_timestamp-Spalte hinzufügen und die gleiche Eindeutigkeit beibehalten, die wir mit den beiden Spalten hatten. BOOM! Nach dem Hinzufügen der dritten Spalte zum Index verschwanden die Deadlocks.

Nebenbemerkung: In einem der Kommentare im Blog wurde vorgeschlagen, die Zeilensperrung für den Index zu deaktivieren. Wir haben es zuerst versucht. Es hat die Deadlocks beseitigt, aber zu mehr Sperren geführt und den Gesamtdurchsatz um etwa 40-50% gesenkt, sodass uns diese Option für unser System nicht gefallen hat. In einer Datenbank mit geringerer Arbeitslast funktioniert dies jedoch möglicherweise einwandfrei.

Hoffentlich macht das alles Sinn.

GoodwinSQL
quelle
0

Ich bin mir nicht sicher, ob dies tatsächlich eine Antwort sein könnte, aber ich kann dies nicht als Kommentar posten

Wenn Sie Ihren Anwendungscode sehen, ist es ähnlich

@P1 nvarchar(4000)
,@P2 nvarchar(4000),
@P3 datetime2,
@P4 nvarchar(4000),
@P5 nvarchar(4000),
@P6 decimal(38,1),
@P7 int,
@P8 int)
INSERT INTO OBC.MBL_CPU_POS_MSR_ATB (
CRT_S, 
CRT_PGM_C, 
CRT_UID, 
LST_UPD_S, 
LST_UPD_PGM_C,
 LST_UPD_UID, 
MSR_ATB_VAL, 
MBL_CPU_POS_I, 
POS_MSR_TYP_I) 
VALUES (@P0, @P1, @P2, @P3, @P4, @P5, @P6, @P7, @P8)                                                                         select SCOPE_IDENTITY() AS GENERATED_KEYS

Wenn Sie sehen , Sie haben erklärt , @p4,@p5...@P6wie , nvarchar()aber wenn Sie Tabellendefinition sehen

CREATE TABLE [OBC].[MBL_CPU_POS_MSR_ATB](
    [MBL_CPU_POS_MSR_ATB_I] [int] IDENTITY(1,1) NOT NULL,
    [POS_MSR_TYP_I] [int] NOT NULL,
    [MBL_CPU_POS_I] [int] NOT NULL,
    [MSR_ATB_VAL] [decimal](15, 4) NULL,
    [CRT_S] [datetime2](7) NOT NULL,
    [CRT_UID] [varchar](8) NOT NULL,
    [CRT_PGM_C] [varchar](30) NOT NULL,
    [LST_UPD_S] [datetime2](7) NOT NULL,
    [LST_UPD_UID] [varchar](8) NOT NULL,
    [LST_UPD_PGM_C] [varchar](30) NOT NULL,

Die Spalte, in die sie Werte einfügen, wird als deklariert varcharund der Wert, den Sie übergeben, ist nvarchar. Keine Ihrer Tabellenspalten hat den nvarcharDatentyp. Wenn dies zutrifft, erfolgt eine implizite Konvertierung, und es werden Index-Scans durchgeführt, anstatt zu versuchen, Sperren (insbesondere bei Aktualisierungen) für eine längere Dauer zu halten. Ich denke, dies könnte eine mögliche Ursache für den Deadlock sein.

Es gibt auch trancount = 2, was bedeutet, dass nicht festgeschriebene Transaktionen und natürlich Bit vorliegen, wenn eine Abfrageoptimierung erforderlich ist.

Beide Transaktionen, die am Deadlock beteiligt waren, konkurrierten mit derselben Ressource waitresource="KEY: 5:72057594043629568 (6234ed5bf036)"

Shanky
quelle
Guter Fang bei den varchar / nvarchar-Konvertierungen. Wir werden die Entwickler dazu bringen, dies zu beheben. Dies scheint jedoch keine wirklichen Auswirkungen auf die Einfügungen zu haben (Auswahl / Aktualisierung wäre wahrscheinlich eine andere Geschichte). Der allgemeine Konsens scheint jedoch darin zu bestehen, den Rest der Transaktionen genauer zu untersuchen.
GoodwinSQL
Wie ich schon sagte, könnte dies eine der Ursachen sein. Beim Deadlock geht es um fehlenden Index, längere Sperren und schlechten Code. Eine falsche Datentyp kann in Scan suchen konvertieren Sie bitte diesen Link lesen , wie eine falsche Datentyp Problem verursachen können social.technet.microsoft.com/wiki/contents/articles/...
Shanky