Diagnose von Deadlocks in SQL Server 2005


82

In der Stack Overflow SQL Server 2005-Datenbank werden einige schädliche, aber seltene Deadlock-Bedingungen angezeigt.

Ich habe den Profiler angehängt, mithilfe dieses hervorragenden Artikels zur Fehlerbehebung bei Deadlocks ein Ablaufverfolgungsprofil erstellt und eine Reihe von Beispielen erfasst. Das Seltsame ist, dass das Deadlocking-Schreiben immer dasselbe ist :

UPDATE [dbo].[Posts]
SET [AnswerCount] = @p1, [LastActivityDate] = @p2, [LastActivityUserId] = @p3
WHERE [Id] = @p0

Die andere Deadlocking-Anweisung variiert, aber es ist normalerweise eine Art triviales, einfaches Lesen der Posts-Tabelle. Dieser wird immer in der Sackgasse getötet. Hier ist ein Beispiel

SELECT
[t0].[Id], [t0].[PostTypeId], [t0].[Score], [t0].[Views], [t0].[AnswerCount], 
[t0].[AcceptedAnswerId], [t0].[IsLocked], [t0].[IsLockedEdit], [t0].[ParentId], 
[t0].[CurrentRevisionId], [t0].[FirstRevisionId], [t0].[LockedReason],
[t0].[LastActivityDate], [t0].[LastActivityUserId]
FROM [dbo].[Posts] AS [t0]
WHERE [t0].[ParentId] = @p0

Um ganz klar zu sein, sehen wir keine Deadlocks beim Schreiben / Schreiben, sondern beim Lesen / Schreiben.

Wir haben momentan eine Mischung aus LINQ- und parametrisierten SQL-Abfragen. Wir haben with (nolock)alle SQL-Abfragen hinzugefügt . Dies mag einigen geholfen haben. Wir hatten auch eine einzige (sehr) schlecht geschriebene Abzeichenabfrage, die ich gestern behoben habe. Die Ausführung dauerte jedes Mal mehr als 20 Sekunden und lief jede Minute darüber hinaus. Ich hatte gehofft, dass dies die Ursache für einige der Schließprobleme war!

Leider habe ich vor ca. 2 Stunden einen weiteren Deadlock-Fehler erhalten. Gleiche exakte Symptome, gleiche exakte Schuldige schreiben.

Das wirklich Seltsame ist, dass die oben gezeigte SQL-Anweisung zum Sperren von Schreibvorgängen Teil eines sehr spezifischen Codepfads ist. Es wird nur ausgeführt, wenn einer Frage eine neue Antwort hinzugefügt wird. Die übergeordnete Frage wird mit der neuen Antwortanzahl und dem letzten Datum / Benutzer aktualisiert. Dies ist offensichtlich nicht so häufig im Vergleich zu der enormen Anzahl von Lesevorgängen, die wir durchführen! Soweit ich das beurteilen kann, schreiben wir nirgendwo in der App eine große Anzahl von Schreibvorgängen.

Mir ist klar, dass NOLOCK eine Art Riesenhammer ist, aber die meisten Abfragen, die wir hier ausführen, müssen nicht so genau sein. Interessiert es Sie, wenn Ihr Benutzerprofil einige Sekunden veraltet ist?

Die Verwendung von NOLOCK mit Linq ist etwas schwieriger, wie Scott Hanselman hier erläutert .

Wir flirten mit der Idee zu verwenden

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

im Basisdatenbankkontext, sodass alle unsere LINQ-Abfragen diesen Satz haben. Ohne das müssten wir jeden LINQ-Aufruf, den wir tätigen (also die einfach lesenden, die die überwiegende Mehrheit von ihnen sind), in einen hässlichen Transaktionscodeblock mit 3-4 Zeilen packen, was hässlich ist.

Ich glaube, ich bin ein wenig frustriert darüber, dass triviale Lesevorgänge in SQL 2005 beim Schreiben zum Stillstand kommen können. Ich konnte sehen, dass Deadlocks beim Schreiben / Schreiben ein großes Problem sind, aber liest? Wir betreiben hier keine Bank-Site, wir brauchen nicht jedes Mal perfekte Genauigkeit.

Ideen? Gedanken?


Instanziieren Sie für jede Operation ein neues LINQ to SQL DataContext-Objekt oder teilen Sie möglicherweise für alle Ihre Aufrufe denselben statischen Kontext?

Jeremy, wir teilen größtenteils einen statischen Datenkontext im Basis-Controller:

private DBContext _db;
/// <summary>
/// Gets the DataContext to be used by a Request's controllers.
/// </summary>
public DBContext DB
{
    get
    {
        if (_db == null)
        {
            _db = new DBContext() { SessionName = GetType().Name };
            //_db.ExecuteCommand("SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED");
        }
        return _db;
    }
}

Empfehlen Sie, dass wir für jeden Controller oder pro Seite oder öfter einen neuen Kontext erstellen?


2
Welchen <a href=" en.wikipedia.org/wiki/… Modus</a> verwenden Sie "pessimistisch" (sperrenbasiert) oder "optimistisch" (<a href = " en.wikipedia.org/wiki/) … )?
John Siracusa

Ich stimme der obigen Antwort von Guy zu - anstatt zu versuchen, das Symptom zu umgehen, warum nicht die zugrunde liegenden Ursachen ansprechen? Durch Hinzufügen der laufenden Summe von AnswerCount zur Posts-Tabelle haben Sie eine potenzielle Blockierungsressource erstellt. Möchte Jeff seine ERD für StackOverflow veröffentlichen, damit die Leute kritisieren können?
Andyp

2
Wow - habe davon in deinem Podcast mit Scott gehört. Ich kann nicht glauben, dass es nicht sofort mit einer besseren Konfiguration ausgeliefert wird. Ich werde dies unseren Datenbankadministratoren zeigen (weil sie auch 'nolock' ausgiebig verwenden)
Dan Esparza,

3
Siehe: samsaffron.com/archive/2008/08/27/Deadlocked+ für den Grund, warum dies Deadlock war. Das Aktivieren der Snapshot-Isolation ist eine gute Lösung für dieses Problem.
Sam Saffron

Antworten:


44

Laut MSDN:

http://msdn.microsoft.com/en-us/library/ms191242.aspx

Wenn entweder die Datenbankoptionen READ COMMITTED SNAPSHOT oder ALLOW SNAPSHOT ISOLATION aktiviert sind, werden logische Kopien (Versionen) für alle in der Datenbank vorgenommenen Datenänderungen beibehalten. Jedes Mal, wenn eine Zeile durch eine bestimmte Transaktion geändert wird, speichert die Instanz des Datenbankmoduls eine Version des zuvor festgeschriebenen Abbilds der Zeile in tempdb. Jede Version ist mit der Transaktionssequenznummer der Transaktion gekennzeichnet, die die Änderung vorgenommen hat. Die Versionen geänderter Zeilen werden mithilfe einer Linkliste verkettet. Der neueste Zeilenwert wird immer in der aktuellen Datenbank gespeichert und mit den in tempdb gespeicherten versionierten Zeilen verkettet.

Bei Transaktionen mit kurzer Laufzeit wird eine Version einer geänderten Zeile möglicherweise im Pufferpool zwischengespeichert, ohne in die Datenträgerdateien der Tempdb-Datenbank geschrieben zu werden. Wenn die Notwendigkeit für die versionierte Zeile nur von kurzer Dauer ist, wird sie einfach aus dem Pufferpool entfernt und verursacht möglicherweise nicht unbedingt E / A-Overhead.

Es scheint eine leichte Leistungsminderung für den zusätzlichen Overhead zu geben, die jedoch vernachlässigbar sein kann. Wir sollten testen, um sicherzugehen.

Versuchen Sie, diese Option festzulegen, und entfernen Sie alle NOLOCKs aus Code-Abfragen, es sei denn, dies ist wirklich erforderlich. NOLOCKs oder die Verwendung globaler Methoden im Datenbankkontext-Handler zur Bekämpfung der Isolationsebenen von Datenbanktransaktionen sind Band-Aids für das Problem. NOLOCKS maskiert grundlegende Probleme mit unserer Datenschicht und führt möglicherweise zur Auswahl unzuverlässiger Daten, wobei die automatische Versionsauswahl zum Auswählen / Aktualisieren von Zeilen die Lösung zu sein scheint.

ALTER Database [StackOverflow.Beta] SET READ_COMMITTED_SNAPSHOT ON

3
"NOLOCKS maskiert grundlegende Probleme mit unserer Datenschicht" ... Welche Probleme maskiert NOLOCK? Wenn ich denke, ich brauche NOLOCK, nach welchen Problemen sollte ich suchen?
Matt Hamilton

3
Was ist mit dieser Antwort, die es zur "Antwort" macht? Ich verstehe immer noch nicht, warum der Millisekunden-Lesevorgang den Schreibvorgang sofort zum Stillstand bringen würde. Ich vermute die Antwort von "user13484" schade, es gibt keinen Hinweis darauf, wenn das der Fall ist.
RichardTheKiwi

37

NOLOCK und READ UNCOMMITTED sind ein rutschiger Hang. Sie sollten sie niemals verwenden, es sei denn, Sie verstehen, warum der Deadlock zuerst auftritt. Es würde mich beunruhigen, wenn Sie sagen: "Wir haben alle SQL-Abfragen mit (nolock) ergänzt." Das Hinzufügen von WITH NOLOCK überall ist ein sicheres Zeichen dafür, dass Sie Probleme in Ihrer Datenschicht haben.

Die Update-Anweisung selbst sieht etwas problematisch aus. Bestimmen Sie die Anzahl früher in der Transaktion oder ziehen Sie sie einfach aus einem Objekt? AnswerCount = AnswerCount+1Wenn eine Frage hinzugefügt wird, ist dies wahrscheinlich eine bessere Möglichkeit, damit umzugehen. Dann benötigen Sie keine Transaktion, um die richtige Anzahl zu erhalten, und Sie müssen sich keine Gedanken über das Problem der Parallelität machen, dem Sie sich möglicherweise aussetzen.

Eine einfache Möglichkeit, diese Art von Deadlock-Problem ohne viel Arbeit und ohne Aktivierung von Dirty Reads zu umgehen, ist die Verwendung "Snapshot Isolation Mode"(neu in SQL 2005), mit der Sie immer die letzten unveränderten Daten sauber lesen können. Sie können festgefahrene Anweisungen auch ziemlich einfach abfangen und erneut versuchen, wenn Sie sie ordnungsgemäß verarbeiten möchten.


4
Ich bin bei JEzell - das erste, worauf ich mich konzentriert habe, ist 'SET AnswerCount = <fester Wert>'. Woher kommt dieser Wert? Ich frage mich daher, ob Sie sie an anderer Stelle in der Transaktion auf eine Weise abgerufen haben, die eine Reihe von Sperren erfasst. Ich würde damit anfangen. Und ja, globales NOLOCK ist ein Pflaster.
Cowan

25

Die OP-Frage war zu fragen, warum dieses Problem aufgetreten ist. Dieser Beitrag hofft, dies zu beantworten, während mögliche Lösungen von anderen ausgearbeitet werden können.

Dies ist wahrscheinlich ein indexbezogenes Problem. Angenommen, die Tabelle Posts hat einen nicht gruppierten Index X, der die ParentID und eines (oder mehrere) der zu aktualisierenden Felder enthält (AnswerCount, LastActivityDate, LastActivityUserId).

Ein Deadlock würde auftreten, wenn das SELECT-Cmd eine Shared-Read-Sperre für Index X ausführt, um nach der ParentId zu suchen, und dann eine Shared-Read-Sperre für den Clustered-Index durchführen muss, um die verbleibenden Spalten abzurufen, während das UPDATE-Cmd eine exklusive Schreibsperre ausführt Sperren Sie den Clustered-Index und benötigen Sie eine schreibgeschützte Sperre für Index X, um ihn zu aktualisieren.

Sie haben jetzt eine Situation, in der A X gesperrt hat und versucht, Y zu erhalten, während B Y gesperrt hat und versucht, X zu erhalten.

Natürlich muss das OP seine Veröffentlichung mit weiteren Informationen darüber aktualisieren, welche Indizes im Spiel sind, um zu bestätigen, ob dies tatsächlich die Ursache ist.


Ich stimme dieser Analyse zu - SELECT und UPDATE verarbeiten die Zeilen in einer anderen Reihenfolge, sodass jeder versucht, eine Zeilensperre zu erhalten, die der andere hat.
Mike Dimmick

Dies ist die beste Antwort auf diesen gesamten Thread und die einzige Erklärung dafür, warum der Deadlock tatsächlich auftritt. Schade, dass es nicht die Antwort Nr. 1 ist, weil es die beste hier ist.
Jonathan Kehayias

Bei einigen Antworten fehlt der Punkt, dass die Sperre zwischen zwei einfachen atomaren Anweisungen liegt. Dies ist der einzige Beitrag, der versucht, dies zu erklären. Während die Anweisung einfach ist, kann eine Tabellenaktualisierung mehrere CIX- und NCIX-Aktualisierungen umfassen, die sich auf viele Vorgänge ausweiten. Gleiches gilt für READ mit NCIX-Traversal und CIX-Lesezeichen-Suche. Dies hat nichts mit dem Verbinden von Tabellen in der gleichen Reihenfolge usw. zu tun (lesen die Leute Fragen?)
RichardTheKiwi

18

Diese Frage und die damit verbundenen Antworten sind mir ziemlich unangenehm. Es gibt eine Menge "Probieren Sie diesen magischen Staub! Nein, diesen magischen Staub!"

Ich kann nirgendwo sehen, dass Sie die genommenen Schlösser analysiert und festgestellt haben, welche Art von Schlössern genau blockiert sind.

Sie haben lediglich angegeben, dass einige Sperren auftreten - nicht das, was blockiert.

In SQL 2005 können Sie weitere Informationen darüber erhalten, welche Sperren aufgehoben werden, indem Sie Folgendes verwenden:

DBCC TRACEON (1222, -1)

Wenn der Deadlock auftritt, haben Sie eine bessere Diagnose.


13
Ein Deadlock wird sofort vom Deadlock-Monitor in SQL Server behandelt. Die DMVs sind für die Fehlerbehebung bei einem Deadlock unbrauchbar, da das Opfer ausgewählt und getötet wird, bevor Sie es jemals erkennen können.
Jonathan Kehayias

14

Instanziieren Sie für jede Operation ein neues LINQ to SQL DataContext-Objekt oder teilen Sie möglicherweise für alle Ihre Aufrufe denselben statischen Kontext? Ich habe ursprünglich den letzteren Ansatz ausprobiert, und soweit ich mich erinnere, verursachte er unerwünschte Sperren in der Datenbank. Ich erstelle jetzt einen neuen Kontext für jede atomare Operation.


10

Bevor Sie das Haus niederbrennen, um mit NOLOCK überall eine Fliege zu fangen, sollten Sie sich das Deadlock-Diagramm ansehen, das Sie mit Profiler hätten erfassen sollen.

Denken Sie daran, dass für einen Deadlock (mindestens) 2 Sperren erforderlich sind. Verbindung 1 hat Sperre A, möchte Sperre B - und umgekehrt für Verbindung 2. Dies ist eine unlösbare Situation, die jemand geben muss.

Was Sie bisher gezeigt haben, wird durch einfaches Sperren gelöst, was SQL Server gerne den ganzen Tag lang tut.

Ich vermute, Sie (oder LINQ) starten eine Transaktion mit dieser UPDATE-Anweisung und wählen vorher eine andere Information aus. Sie müssen jedoch wirklich durch das Deadlock-Diagramm zurückverfolgen, um die von jedem Thread gehaltenen Sperren zu finden , und dann durch Profiler zurückverfolgen, um die Anweisungen zu finden, die dazu geführt haben, dass diese Sperren gewährt wurden.

Ich gehe davon aus, dass es mindestens 4 Anweisungen gibt, um dieses Rätsel zu lösen (oder eine Anweisung, die mehrere Sperren erfordert - vielleicht gibt es einen Auslöser für die Posts-Tabelle?).


7

Interessiert es Sie, wenn Ihr Benutzerprofil einige Sekunden veraltet ist?

Nein - das ist vollkommen akzeptabel. Das Festlegen der Isolationsstufe für Basistransaktionen ist wahrscheinlich der beste / sauberste Weg.


5

Ein typischer Lese- / Schreib-Deadlock ergibt sich aus dem Zugriff auf die Indexreihenfolge. Read (T1) sucht die Zeile in Index A und sucht dann die projizierte Spalte in Index B (normalerweise gruppiert). Write (T2) ändert den Index B (den Cluster) und muss dann den Index A aktualisieren. T1 hat S-Lck auf A, will S-Lck auf B, T2 hat X-Lck auf B, will U-Lck auf A. Deadlock , puff. T1 wird getötet. Dies ist in Umgebungen mit starkem OLTP-Verkehr und nur ein bisschen zu vielen Indizes der Fall :). Die Lösung besteht darin, entweder dafür zu sorgen, dass der Lesevorgang nicht von A nach B springen muss (dh die Spalte in A enthalten ist oder die Spalte aus der projizierten Liste entfernt wird), oder dass T2 nicht von B nach A springen muss (die indizierte Spalte nicht aktualisieren). Leider ist linq hier nicht dein Freund ...


Übrigens A und B sind Indizes derselben Tabelle
Remus Rusanu

3

@ Jeff - Ich bin definitiv kein Experte in diesem Bereich, aber ich habe gute Ergebnisse erzielt, indem ich bei fast jedem Anruf einen neuen Kontext instanziiert habe. Ich denke, es ähnelt dem Erstellen eines neuen Verbindungsobjekts bei jedem Aufruf mit ADO. Der Overhead ist nicht so hoch, wie Sie denken würden, da das Verbindungspooling ohnehin weiterhin verwendet wird.

Ich benutze einfach einen globalen statischen Helfer wie diesen:

public static class AppData
{
    /// <summary>
    /// Gets a new database context
    /// </summary>
    public static CoreDataContext DB
    {
        get
        {
            var dataContext = new CoreDataContext
            {
                DeferredLoadingEnabled = true
            };
            return dataContext;
        }
    }
}

und dann mache ich so etwas:

var db = AppData.DB;

var results = from p in db.Posts where p.ID = id select p;

Und ich würde das Gleiche für Updates tun. Wie auch immer, ich habe nicht annähernd so viel Verkehr wie Sie, aber ich bekam definitiv einige Sperren, als ich einen gemeinsam genutzten DataContext mit nur einer Handvoll Benutzern verwendete. Keine Garantie, aber es könnte sich lohnen, es zu versuchen.

Update : Wenn Sie sich Ihren Code ansehen, teilen Sie den Datenkontext nur für die Lebensdauer dieser bestimmten Controller-Instanz. Dies scheint im Grunde genommen in Ordnung zu sein, es sei denn, er wird gleichzeitig von mehreren Aufrufen innerhalb des Controllers verwendet. In einem Thread zum Thema sagte ScottGu:

Controller leben nur für eine einzelne Anforderung - am Ende der Verarbeitung einer Anforderung werden sie also mit Müll gesammelt (was bedeutet, dass der DataContext gesammelt wird) ...

Das ist es vielleicht nicht, aber es ist wahrscheinlich einen Versuch wert, vielleicht in Verbindung mit einigen Lasttests.


3

Frage: Warum speichern Sie das überhaupt AnswerCountin der PostsTabelle?

Ein alternativer Ansatz besteht darin, das "Zurückschreiben" in die PostsTabelle zu eliminieren, indem das nicht AnswerCountin der Tabelle gespeichert wird, sondern die Anzahl der Antworten auf den Beitrag nach Bedarf dynamisch berechnet wird.

Ja, dies bedeutet, dass Sie eine zusätzliche Abfrage ausführen:

SELECT COUNT(*) FROM Answers WHERE post_id = @id

oder typischer (wenn Sie dies für die Startseite anzeigen):

SELECT p.post_id, 
     p.<additional post fields>,
     a.AnswerCount
FROM Posts p
    INNER JOIN AnswersCount_view a
    ON <join criteria>
WHERE <home page criteria>

Dies führt jedoch in der Regel zu einem INDEX SCANund kann bei der Verwendung von Ressourcen effizienter sein als bei der Verwendung READ ISOLATION.

Es gibt mehr als einen Weg, eine Katze zu häuten. Eine vorzeitige De-Normalisierung eines Datenbankschemas kann zu Skalierbarkeitsproblemen führen.


3

Sie möchten auf jeden Fall, dass READ_COMMITTED_SNAPSHOT aktiviert ist, was nicht standardmäßig der Fall ist. Das gibt Ihnen MVCC-Semantik. Es ist dasselbe, was Oracle standardmäßig verwendet. Eine MVCC-Datenbank zu haben ist so unglaublich nützlich, dass es verrückt ist, KEINE zu verwenden. Auf diese Weise können Sie innerhalb einer Transaktion Folgendes ausführen:

USERS aktualisieren Set FirstName = 'foobar'; // entscheide dich für ein Jahr zu schlafen.

In der Zwischenzeit kann jeder ohne diese Verpflichtung weiter aus dieser Tabelle auswählen. Wenn Sie mit MVCC nicht vertraut sind, werden Sie schockiert sein, dass Sie jemals ohne MVCC leben konnten. Ernsthaft.


3

Es ist keine gute Idee, die Standardeinstellung so festzulegen, dass sie nicht festgeschrieben ist. Ihr Wille führt zweifellos zu Inkonsistenzen und führt zu einem Problem, das schlimmer ist als das, was Sie jetzt haben. Die Snapshot-Isolation mag gut funktionieren, ist jedoch eine drastische Änderung der Funktionsweise von SQL Server und stellt eine enorme Belastung für Tempdb dar.

Folgendes sollten Sie tun: Verwenden Sie try-catch (in T-SQL), um die Deadlock-Bedingung zu erkennen. Führen Sie in diesem Fall die Abfrage erneut aus. Dies ist die Standardpraxis für die Datenbankprogrammierung.

Es gibt gute Beispiele für diese Technik in Paul Nielsons Sql Server 2005 Bible .

Hier ist eine kurze Vorlage, die ich verwende:

-- Deadlock retry template

declare @lastError int;
declare @numErrors int;

set @numErrors = 0;

LockTimeoutRetry:

begin try;

-- The query goes here

return; -- this is the normal end of the procedure

end try begin catch
    set @lastError=@@error
    if @lastError = 1222 or @lastError = 1205 -- Lock timeout or deadlock
    begin;
        if @numErrors >= 3 -- We hit the retry limit
        begin;
            raiserror('Could not get a lock after 3 attempts', 16, 1);
            return -100;
        end;

        -- Wait and then try the transaction again
        waitfor delay '00:00:00.25';
        set @numErrors = @numErrors + 1;
        goto LockTimeoutRetry;

    end;

    -- Some other error occurred
    declare @errorMessage nvarchar(4000), @errorSeverity int
    select    @errorMessage = error_message(),
            @errorSeverity = error_severity()

    raiserror(@errorMessage, @errorSeverity, 1)

    return -100
end catch;    

2
Warum erschreckt mich diese Lösung? !! Ich würde mir ansehen, WARUM es einen Stillstand gibt. Nicht die Pflasterung eines wirklich armen Mannes wegen des Problems.
Pure.Krome

2

Eine Sache, die in der Vergangenheit für mich funktioniert hat, ist sicherzustellen, dass alle meine Abfragen und Aktualisierungen auf Ressourcen (Tabellen) in derselben Reihenfolge zugreifen.

Das heißt, wenn eine Abfrage in der Reihenfolge Tabelle1, Tabelle2 und eine andere Abfrage in der Reihenfolge Tabelle2, Tabelle1 aktualisiert wird, werden möglicherweise Deadlocks angezeigt.

Sie sind sich nicht sicher, ob Sie die Reihenfolge der Aktualisierungen ändern können, da Sie LINQ verwenden. Aber es ist etwas zu sehen.


1

Interessiert es Sie, wenn Ihr Benutzerprofil einige Sekunden veraltet ist?

Ein paar Sekunden wären definitiv akzeptabel. Es scheint sowieso nicht so lange zu dauern, es sei denn, eine große Anzahl von Menschen gibt gleichzeitig Antworten.


1

Da stimme ich Jeremy zu. Sie fragen, ob Sie für jeden Controller oder pro Seite einen neuen Datenkontext erstellen sollen. Ich neige dazu, für jede unabhängige Abfrage einen neuen zu erstellen.

Ich baue derzeit eine Lösung, die den statischen Kontext wie Sie implementiert hat, und als ich während Stresstests Tonnen von Anfragen an das Biest eines Servers (über Millionen) warf, bekam ich auch zufällig Lese- / Schreibsperren.

Sobald ich meine Strategie geändert hatte, um auf Abfrageebene einen anderen Datenkontext auf LINQ-Ebene zu verwenden, und darauf vertraute, dass SQL Server seine Magie des Verbindungspoolings anwenden konnte, schienen die Sperren zu verschwinden.

Natürlich stand ich unter Zeitdruck, also habe ich eine Reihe von Dingen gleichzeitig ausprobiert, daher kann ich nicht 100% sicher sein, dass dies das Problem behoben hat, aber ich habe ein hohes Maß an Selbstvertrauen - sagen wir es so .


1

Sie sollten Dirty Reads implementieren.

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

Wenn Sie bei Ihren Abfragen nicht unbedingt eine perfekte Transaktionsintegrität benötigen, sollten Sie beim Zugriff auf Tabellen mit hoher Parallelität Dirty Reads verwenden. Ich gehe davon aus, dass Ihre Posts-Tabelle eine davon wäre.

Dies kann zu sogenannten "Phantom Reads" führen, wenn Ihre Abfrage auf Daten aus einer Transaktion reagiert, die nicht festgeschrieben wurde.

Wir betreiben hier keine Bank-Site, wir brauchen nicht jedes Mal perfekte Genauigkeit

Verwenden Sie Dirty Reads. Sie haben Recht damit, dass sie Ihnen keine perfekte Genauigkeit bieten, aber sie sollten Ihre Probleme mit toten Verriegelungen klären.

Ohne das müssten wir jeden LINQ-Aufruf, den wir tätigen (also die einfach lesenden, die die überwiegende Mehrheit von ihnen sind), in einen hässlichen Transaktionscodeblock mit 3-4 Zeilen packen, was hässlich ist

Wenn Sie Dirty Reads im "Basisdatenbankkontext" implementieren, können Sie Ihre einzelnen Aufrufe immer mit einer höheren Isolationsstufe umschließen, wenn Sie die Transaktionsintegrität benötigen.


1

Was ist das Problem bei der Implementierung eines Wiederholungsmechanismus? Es wird immer die Möglichkeit eines Deadlocks geben. Warum also nicht eine Logik haben, um ihn zu identifizieren und es einfach erneut zu versuchen?

Werden nicht zumindest einige der anderen Optionen Leistungseinbußen einführen, die immer dann auftreten, wenn ein Wiederholungssystem selten eingesetzt wird?

Vergessen Sie auch nicht eine Art Protokollierung, wenn ein erneuter Versuch stattfindet, damit Sie nicht in die Situation geraten, dass Sie selten werden.


1

Nachdem ich Jeremys Antwort gesehen habe, erinnere ich mich, dass ich gehört habe, dass die beste Vorgehensweise darin besteht, für jede Datenoperation einen neuen DataContext zu verwenden. Rob Conery hat mehrere Posts über DataContext geschrieben, und er informiert sie immer, anstatt einen Singleton zu verwenden.

Hier ist das Muster, das wir für Video.Show verwendet haben ( Link zur Quellansicht in CodePlex ):

using System.Configuration;
namespace VideoShow.Data
{
  public class DataContextFactory
  {
    public static VideoShowDataContext DataContext()
    {
        return new VideoShowDataContext(ConfigurationManager.ConnectionStrings["VideoShowConnectionString"].ConnectionString);
    }
    public static VideoShowDataContext DataContext(string connectionString)
    {
        return new VideoShowDataContext(connectionString);
    }
  }
}

Dann auf Service-Ebene (oder noch detaillierter für Updates):

private VideoShowDataContext dataContext = DataContextFactory.DataContext();

public VideoSearchResult GetVideos(int pageSize, int pageNumber, string sortType)
{
  var videos =
  from video in DataContext.Videos
  where video.StatusId == (int)VideoServices.VideoStatus.Complete
  orderby video.DatePublished descending
  select video;
  return GetSearchResult(videos, pageSize, pageNumber);
}

0

Ich müsste Greg zustimmen, solange das Einstellen der Isolationsstufe zum Lesen ohne Festschreiben keine negativen Auswirkungen auf andere Abfragen hat.

Es würde mich interessieren, Jeff, wie sich das Festlegen auf Datenbankebene auf eine Abfrage wie die folgende auswirkt:

Begin Tran
Insert into Table (Columns) Values (Values)
Select Max(ID) From Table
Commit Tran

0

Es ist in Ordnung für mich, wenn mein Profil sogar einige Minuten veraltet ist.

Versuchen Sie den Lesevorgang erneut, nachdem er fehlgeschlagen ist? Es ist sicherlich möglich, dass einige wenige zufällige Lesevorgänge ausführen, wenn sie nicht lesen können. Die meisten Anwendungen, mit denen ich arbeite, sind im Vergleich zur Anzahl der Lesevorgänge nur sehr wenige Schreibvorgänge, und ich bin sicher, dass die Lesevorgänge nicht annähernd der Anzahl entsprechen, die Sie erhalten.

Wenn die Implementierung von "READ UNCOMMITTED" Ihr Problem nicht löst, ist es schwierig zu helfen, ohne viel mehr über die Verarbeitung zu wissen. Möglicherweise gibt es eine andere Optimierungsoption, die dieses Verhalten unterstützt. Sofern kein MSSQL-Guru zur Rettung kommt, empfehle ich, das Problem beim Anbieter einzureichen.


0

Ich würde weiterhin alles stimmen; Wie funktioniert das Festplattensubsystem? Was ist die durchschnittliche Länge der Festplattenwarteschlange? Wenn E / A-Vorgänge gesichert werden, besteht das eigentliche Problem möglicherweise nicht in diesen beiden Deadlocks, sondern in einer anderen Abfrage, die das System zum Engpass macht. Sie haben eine Abfrage erwähnt, die 20 Sekunden dauert und optimiert wurde. Gibt es noch andere?

Wenn Sie sich darauf konzentrieren, die lang laufenden Abfragen zu verkürzen, werden die Deadlock-Probleme wahrscheinlich verschwinden.


0

Hatte das gleiche Problem und kann "TransationLevel = IsolationLevel.ReadUncommitted" in TransactionScope nicht verwenden, da auf dem Server DTS nicht aktiviert ist (!).

Das habe ich mit einer Erweiterungsmethode gemacht:

public static void SetNoLock(this MyDataContext myDS)
{
    myDS.ExecuteCommand("SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED");
}

Für ausgewählte Benutzer, die kritische Parallelitätstabellen verwenden, aktivieren wir den "Nolock" wie folgt:

using (MyDataContext myDS = new MyDataContext())
{
   myDS.SetNoLock();

   //  var query = from ...my dirty querys here...
}

Vorschläge sind willkommen!

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.