Leistungsfehler des SQL Server 2008-Datetime-Index

11

Wir verwenden SQL Server 2008 R2 und haben eine sehr große Tabelle (über 100 datetimeMillionen Zeilen) mit einem primären ID-Index und eine Spalte mit einem nicht gruppierten Index. Wir sehen ein sehr ungewöhnliches Client / Server-Verhalten, das auf der Verwendung einer order byKlausel speziell für eine indizierte Datetime-Spalte basiert .

Ich habe den folgenden Beitrag gelesen: /programming/1716798/sql-server-2008-ordering-by-datetime-is-too-slow, aber mit dem Client / Server ist mehr los als das, was ist beginnen hier beschrieben.

Wenn wir die folgende Abfrage ausführen (bearbeitet, um einige Inhalte zu schützen):

select * 
from [big table] 
where serial_number = [some number] 
order by test_date desc

Die Abfrage läuft jedes Mal ab. Im SQL Server Profiler sieht die ausgeführte Abfrage für den Server folgendermaßen aus:

exec sp_cursorprepexec @p1 output,@p2 output,NULL,N'select * .....

Wenn Sie nun die Abfrage ändern, sagen Sie Folgendes:

declare @temp int;
select * from [big table] 
where serial_number = [some number] 
order by test_date desc

Der SQL Server-Profiler zeigt an, dass die ausgeführte Abfrage für den Server folgendermaßen aussieht und sofort FUNKTIONIERT:

exec sp_prepexec @p1 output, NULL, N'declare @temp int;select * from .....

Tatsächlich können Sie anstelle einer nicht verwendeten Deklarationsanweisung sogar einen leeren Kommentar ('-;') einfügen und das gleiche Ergebnis erzielen. Daher haben wir anfangs auf den SP-Vorprozessor als Hauptursache für dieses Problem hingewiesen, aber wenn Sie dies tun:

select * 
from [big table] 
where serial_number = [some number] 
order by Cast(test_date as smalldatetime) desc

Es funktioniert auch sofort (Sie können es wie jeden anderen datetimeTyp umwandeln) und gibt das Ergebnis in Millisekunden zurück. Der Profiler zeigt die Anforderung an den Server wie folgt an:

exec sp_cursorprepexec @p1 output, @p2 output, NULL, N'select * from .....

Damit wird das sp_cursorprepexecVerfahren etwas von der vollständigen Ursache des Problems ausgeschlossen. Hinzu kommt, dass das sp_cursorprepexecauch aufgerufen wird, wenn keine 'order by' verwendet wird, und das Ergebnis auch sofort zurückgegeben wird.

Wir haben ziemlich viel nach diesem Problem gegoogelt, und ich sehe ähnliche Probleme von anderen, aber keine, die es auf dieses Niveau herunterbrechen.

Haben andere dieses Verhalten gesehen? Hat jemand eine bessere Lösung, als bedeutungsloses SQL vor die select-Anweisung zu stellen, um das Verhalten zu ändern? Da SQL Server die Reihenfolge nach dem Sammeln der Daten aufrufen sollte, scheint dies ein Fehler auf dem Server zu sein, der lange Zeit bestehen geblieben ist. Wir haben festgestellt, dass dieses Verhalten in vielen unserer großen Tabellen konsistent und reproduzierbar ist.

Bearbeitungen:

Ich sollte auch hinzufügen, forceseekdass durch das Einfügen auch das Problem verschwindet.

Ich sollte hinzufügen, um den Suchenden zu helfen. Der ODBC-Timeout-Fehler lautet: [Microsoft] [ODBC SQL Server-Treiber] Vorgang abgebrochen

Hinzugefügt am 12.10.2012: Ich bin immer noch auf der Suche nach der Grundursache (zusammen mit der Erstellung eines Beispiels für Microsoft werde ich hier alle Ergebnisse veröffentlichen, nachdem ich sie eingereicht habe). Ich habe in der ODBC-Tracedatei zwischen einer funktionierenden Abfrage (mit einem hinzugefügten Kommentar / einer Deklarationsanweisung) und einer nicht funktionierenden Abfrage gesucht. Der grundlegende Trace-Unterschied ist unten angegeben. Es tritt beim Aufruf von SQLExtendedFetch auf, nachdem alle SQLBindCol-Diskussionen abgeschlossen sind. Der Aufruf schlägt mit dem Rückkehrcode -1 fehl und der übergeordnete Thread gibt dann SQLCancel ein. Da wir dies sowohl mit dem Native Client- als auch mit dem Legacy-ODBC-Treiber erstellen können, weise ich immer noch auf ein Kompatibilitätsproblem auf der Serverseite hin.

(clip)
MSSQLODBCTester 1664-1718   EXIT  SQLBindCol  with return code 0 (SQL_SUCCESS)
        HSTMT               0x001EEA10
        UWORD                       16 
        SWORD                        1 <SQL_C_CHAR>
        PTR                0x03259030
        SQLLEN                    51
        SQLLEN *            0x0326B820 (0)

MSSQLODBCTester 1664-1718   ENTER SQLExtendedFetch 
        HSTMT               0x001EEA10
        UWORD                        1 <SQL_FETCH_NEXT>
        SQLLEN                     1
        SQLULEN *           0x032677C4
        UWORD *             0x032679B0

MSSQLODBCTester 1664-1fd0   ENTER SQLCancel 
        HSTMT               0x001EEA10

MSSQLODBCTester 1664-1718   EXIT  SQLExtendedFetch  with return code -1 (SQL_ERROR)
        HSTMT               0x001EEA10
        UWORD                        1 <SQL_FETCH_NEXT>
        SQLLEN                     1
        SQLULEN *           0x032677C4
        UWORD *             0x032679B0

        DIAG [S1008] [Microsoft][ODBC SQL Server Driver]Operation canceled (0) 

MSSQLODBCTester 1664-1fd0   EXIT  SQLCancel  with return code 0 (SQL_SUCCESS)
        HSTMT               0x001EEA10

MSSQLODBCTester 1664-1718   ENTER SQLErrorW 
        HENV                0x001E7238
        HDBC                0x001E7B30
        HSTMT               0x001EEA10
        WCHAR *             0x08BFFC5C
        SDWORD *            0x08BFFF08
        WCHAR *             0x08BFF85C 
        SWORD                      511 
        SWORD *             0x08BFFEE6

MSSQLODBCTester 1664-1718   EXIT  SQLErrorW  with return code 0 (SQL_SUCCESS)
        HENV                0x001E7238
        HDBC                0x001E7B30
        HSTMT               0x001EEA10
        WCHAR *             0x08BFFC5C [       5] "S1008"
        SDWORD *            0x08BFFF08 (0)
        WCHAR *             0x08BFF85C [      53] "[Microsoft][ODBC SQL Server Driver]Operation canceled"
        SWORD                      511 
        SWORD *             0x08BFFEE6 (53)

MSSQLODBCTester 1664-1718   ENTER SQLErrorW 
        HENV                0x001E7238
        HDBC                0x001E7B30
        HSTMT               0x001EEA10
        WCHAR *             0x08BFFC5C
        SDWORD *            0x08BFFF08
        WCHAR *             0x08BFF85C 
        SWORD                      511 
        SWORD *             0x08BFFEE6

MSSQLODBCTester 1664-1718   EXIT  SQLErrorW  with return code 100 (SQL_NO_DATA_FOUND)
        HENV                0x001E7238
        HDBC                0x001E7B30
        HSTMT               0x001EEA10
        WCHAR *             0x08BFFC5C
        SDWORD *            0x08BFFF08
        WCHAR *             0x08BFF85C 
        SWORD                      511 
        SWORD *             0x08BFFEE6
(clip)

Microsoft Connect-Fall 10/12/2012 hinzugefügt:

https://connect.microsoft.com/SQLServer/feedback/details/767196/order-by-datetime-in-odbc-fails-for-clean-sql-statements#details

Ich sollte auch beachten, dass wir die Abfragepläne sowohl für funktionierende als auch für nicht funktionierende Abfragen nachgeschlagen haben. Sie werden beide entsprechend der Ausführungsanzahl entsprechend wiederverwendet. Das Leeren der zwischengespeicherten Pläne und das erneute Ausführen ändern nichts am Erfolg der Abfrage.

DBtheDBA
quelle
Was passiert, wenn Sie es versuchen? select id, test_date from [big table] where serial_number = ..... order by test_dateIch frage mich nur, ob SELECT *sich dies negativ auf Ihre Leistung auswirkt. Wenn Sie einen nicht gruppierten Index auf haben test_dateund einen Clustered - Index auf id(das ist vorausgesetzt , was es heißt), diese Abfrage sollte werden abgedeckt durch diese nicht gruppierten Index und sollte daher recht schnell wieder
marc_s
Entschuldigung, guter Punkt. Ich hätte einschließen sollen, dass wir versucht haben, den ausgewählten Spaltenraum (Entfernen des '*' usw.) stark mit verschiedenen Kombinationen zu ändern. Das oben beschriebene Verhalten blieb durch diese Änderungen bestehen.
DBtheDBA
Ich habe meine Konten jetzt mit dieser Site verknüpft. Wenn ein Moderator den Beitrag auf diese Site verschieben möchte, geht es mir so oder so gut. Einer meiner Entwickler hat mich auf diese Seite hingewiesen, nachdem ich hier gepostet habe.
DBtheDBA
Welcher Client-Stack wird hier verwendet? Ohne den gesamten Trace-Text scheint das das Problem zu sein. Versuchen Sie, den ursprünglichen Anruf einzuschließen, sp_executesqlund sehen Sie, was passiert.
Jon Seigel
1
Wie sieht der langsame Ausführungsplan aus? Parameter schnüffeln?
Martin Smith

Antworten:

6

Es gibt kein Rätsel, Sie erhalten einen guten (er) oder (wirklich) schlechten Plan im Grunde genommen zufällig, da es keine eindeutige Auswahl für den Index gibt. Obwohl Sie für die ORDER BY-Klausel zwingend sind und daher die Sortierung vermeiden, ist Ihr nicht gruppierter Index für die datetime-Spalte eine sehr schlechte Wahl für diese Abfrage. Was einen viel besseren Index für diese Abfrage ergeben würde, wäre einer (serial_number, test_date). Noch besser wäre dies ein sehr guter Kandidat für einen Clustered- Indexschlüssel.

Als Faustregel sollten Zeitreihen nach Zeitspalten gruppiert werden, da die überwiegende Mehrheit der Anfragen an bestimmten Zeitbereichen interessiert ist. Wenn die Daten auch inhärent in einer Spalte mit geringer Selektivität partitioniert sind, wie dies bei Ihrer Seriennummer der Fall zu sein scheint, sollte diese Spalte in der Definition des Clusterschlüssels ganz links hinzugefügt werden.

Remus Rusanu
quelle
Ich bin hier etwas verwirrt. Warum sollte der Plan auf einer the orderKlausel basieren ? Sollte sich der Plan nicht auf die whereBedingungen beschränken, da die Reihenfolge erst nach dem Abrufen der Zeilen erfolgen sollte? Warum sollte der Server versuchen, die Datensätze zu sortieren, bevor die gesamte Ergebnismenge vorliegt?
DBtheDBA
5
Dies erklärt auch nicht, warum das Hinzufügen eines Kommentars am Anfang der Abfrage die Ausführungsdauer beeinflusst.
Cfradenburg
Außerdem werden unsere Tabellen fast immer nach Seriennummer und nicht nach test_date abgefragt. Wir haben nicht gruppierte Indizes für beide und einen Cluster nur für die ID-Spalte in der Tabelle. Es handelt sich um einen betrieblichen Datenspeicher, und das Hinzufügen von Clustered-Indizes zu anderen Spalten würde nur zu Seitensplits und einer schlechteren Leistung führen.
DBtheDBA
1
@DBtheDBA: Wenn Sie einen Anspruch auf einen "Fehler" erheben möchten, müssen Sie eine ordnungsgemäße Untersuchung und Offenlegung durchführen. Das genaue Schema Ihrer Tabelle und der exportierten Statistiken finden Sie unter So generieren Sie ein Skript mit den erforderlichen Datenbankmetadaten, um in SQL Server 2005 und SQL Server 2008 eine reine Statistikdatenbank zu erstellen , insbesondere die wichtigsten Skriptstatistiken : Skriptstatistiken und Histogramme . Fügen Sie diese zusammen mit den Schritten, die das Problem reproduzieren, zu den Post-Informationen hinzu.
Remus Rusanu
1
Wir haben das bereits während unserer Suche gelesen, und ich verstehe, was Sie sagen, aber es gibt einen grundlegenden Fehler in etwas, das der Server hier tut. Wir haben die Tabelle und die Indizes neu erstellt und in einer neuen Tabelle reproduziert. Die Option zum erneuten Kompilieren behebt das Problem nicht. Dies ist ein großer Hinweis darauf, dass etwas nicht stimmt. Ich bezweifle nicht, dass das Setzen von Clustered-Indizes für alles dieses Problem möglicherweise beheben könnte, aber es ist keine Lösung für die Grundursache, es ist eine Problemumgehung und eine teure Lösung für eine große Tabelle.
DBtheDBA
0

Dokumentieren Sie die Details zur Reproduktion des Fehlers und senden Sie ihn auf connect.microsoft.com. Ich habe nachgesehen und konnte dort draußen noch nichts sehen, was damit zu tun hätte.

cfradenburg
quelle
Ich werde meinen DBA morgen dazu bringen, ein Skript einzugeben, um eine Umgebung für die Reproduktion zu erstellen. Ich denke nicht, dass es so schwierig ist. Ich werde es auch hier posten, falls jemand daran interessiert sein sollte, es selbst zu versuchen.
DBtheDBA
Veröffentlichen Sie das Verbindungselement auch, wenn es geöffnet wird. Auf diese Weise wird jemand anderes, der dieses Problem hat, direkt darauf hingewiesen. Und jeder, der sich diese Frage ansieht, möchte den Artikel möglicherweise abstimmen, damit Microsoft eher darauf achtet.
Cfradenburg
0

Meine Hypothese ist, dass Sie dem Abfrageplan-Cache zuwiderlaufen. (Remus sagt vielleicht dasselbe wie ich, aber auf andere Weise.)

Hier finden Sie eine Menge Details dazu, wie SQL das Caching plant .

Beschönigung der Details: Jemand hat diese Abfrage früher für eine bestimmte [eine bestimmte Anzahl] ausgeführt. SQL untersuchte den bereitgestellten Wert, die Indizes und Statistiken für die relevanten Tabellen / Spalten usw. und erstellte einen Plan, der für diese bestimmte [einige Anzahl] gut funktionierte. Anschließend wurde der Plan zwischengespeichert, ausgeführt und die Ergebnisse an den Anrufer zurückgegeben.

Später führt eine andere Person dieselbe Abfrage für einen anderen Wert von [eine Zahl] aus. Dieser bestimmte Wert führt zu einer völlig anderen Anzahl von Ergebniszeilen, und die Engine sollte einen anderen Plan für diese Instanz der Abfrage erstellen. Aber so funktioniert es nicht. Stattdessen nimmt SQL die Abfrage entgegen und durchsucht (mehr oder weniger) den Abfragecache nach Groß- und Kleinschreibung, um nach einer bereits vorhandenen Version der Abfrage zu suchen. Wenn es das von früher findet, verwendet es nur diesen Plan.

Die Idee ist, dass es Zeit spart, sich für den Plan zu entscheiden und ihn zu erstellen. Die Lücke in der Idee besteht darin, dass dieselbe Abfrage mit Werten ausgeführt wird, die zu sehr unterschiedlichen Ergebnissen führen. Sie sollten andere Pläne haben, aber sie tun es nicht. Wer zuerst die Abfrage ausgeführt hat, hilft dabei, das Verhalten für alle festzulegen, die sie anschließend ausführen.

Ein kurzes Beispiel: Wählen Sie * aus [Personen], wobei Nachname = 'SMITH' - sehr beliebter Nachname in den USA. GO Wählen Sie * aus [Personen], wobei Nachname = 'BONAPARTE' - NICHT beliebter Nachname in den USA

Wenn die Abfrage für BONAPARTE ausgeführt wird, wird der für SMITH erstellte Plan erneut verwendet. Wenn SMITH einen Tabellenscan verursacht hat (was gut sein kann , wenn die Zeilen in der Tabelle zu 99% SMITH sind), erhält BONAPARTE auch einen Tabellenscan. Wenn BONAPARTE vor SMITH ausgeführt wurde, wird möglicherweise ein Plan mit einem Index erstellt und verwendet und dann erneut für SMITH verwendet (was beim Tabellenscan möglicherweise besser ist). Menschen bemerken möglicherweise nicht, dass die Leistung für SMITH schlecht ist, da sie eine schlechte Leistung erwarten, da die gesamte Tabelle gelesen werden muss und das Lesen des Index und das Springen zur Tabelle nicht direkt bemerkt wird.

In Bezug auf Ihre Änderungen, die irgendetwas ändern sollten, vermute ich, dass SQL dies nur als eine völlig andere Abfrage ansieht und einen neuen Plan erstellt, der spezifisch für Ihren Wert von [einer bestimmten Zahl] ist.

Um dies zu testen, nehmen Sie eine unsinnige Änderung an der Abfrage vor, z. B. das Hinzufügen einiger Leerzeichen zwischen FOR und dem Tabellennamen, oder setzen Sie am Ende einen Kommentar. Ist es schnell Wenn ja, liegt das daran, dass sich diese Abfrage geringfügig von der im Cache befindet. Daher hat SQL das getan, was es für "neue" Abfragen tut.

Für eine Lösung würde ich drei Dinge betrachten. Stellen Sie zunächst sicher, dass Ihre Statistiken auf dem neuesten Stand sind. Dies sollte wirklich das erste sein, was Sie tun, wenn eine Abfrage seltsam oder zufällig zu wirken scheint. Ihr DBA sollte dies tun, aber es passieren Dinge. Die übliche Methode, um aktuelle Statistiken sicherzustellen, besteht darin, Ihre Tabellen neu zu indizieren. Dies ist nicht unbedingt einfach, aber es gibt auch Optionen, um die Statistiken einfach zu aktualisieren.

Das zweite, worüber Sie nachdenken sollten, ist das Hinzufügen von Indizes gemäß Remus 'Vorschlägen. Bei einem besseren / anderen Index ist ein Wert möglicherweise stabiler und variiert nicht so stark.

Wenn dies nicht hilft, müssen Sie als Drittes jedes Mal, wenn Sie die Anweisung ausführen, einen neuen Plan mit dem Schlüsselwort RECOMPILE erzwingen:

Wählen Sie * aus [große Tabelle], wobei Seriennummer = [eine Nummer] Reihenfolge nach test_date desc OPTION (RECOMPILE)

Es gibt einen Artikel, der eine ähnliche Situation hier beschreibt . Ehrlich gesagt hatte ich RECOMPILE bisher nur auf gespeicherte Prozeduren angewendet gesehen, aber es scheint mit "regulären" SELECT-Anweisungen zu funktionieren. Kimberly Tripp hat mich nie falsch gelenkt.

Sie können sich auch die Funktion " Plananleitungen " ansehen , diese ist jedoch komplexer und möglicherweise übertrieben.

darin Meerenge
quelle
Um einige dieser Probleme abzudecken: 1. Statistiken wurden aktualisiert und werden aktualisiert. 2. Wir haben versucht, auf verschiedene Arten zu indizieren (einschließlich Indizes usw.), aber das Problem scheint eher mit der order byVerwendung eines Datetime-Index verbunden zu sein. 3. Ich habe gerade Ihre Idee mit der Option RECOMPILE ausprobiert, sie ist immer noch fehlgeschlagen, was mich ein wenig überrascht hat. Ich hatte gehofft, dass sie funktionieren würde, obwohl ich nicht weiß, ob es sich um eine Lösung für die Produktion handelt.
DBtheDBA