Wir verwenden SQL Server 2008 R2 und haben eine sehr große Tabelle (über 100 datetime
Millionen 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 by
Klausel 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 datetime
Typ 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_cursorprepexec
Verfahren etwas von der vollständigen Ursache des Problems ausgeschlossen. Hinzu kommt, dass das sp_cursorprepexec
auch 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, forceseek
dass 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:
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.
quelle
select id, test_date from [big table] where serial_number = ..... order by test_date
Ich frage mich nur, obSELECT *
sich dies negativ auf Ihre Leistung auswirkt. Wenn Sie einen nicht gruppierten Index auf habentest_date
und einen Clustered - Index aufid
(das ist vorausgesetzt , was es heißt), diese Abfrage sollte werden abgedeckt durch diese nicht gruppierten Index und sollte daher recht schnell wiedersp_executesql
und sehen Sie, was passiert.Antworten:
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.
quelle
the order
Klausel basieren ? Sollte sich der Plan nicht auf diewhere
Bedingungen 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?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.
quelle
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.
quelle
order by
Verwendung 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.