Sobald ich das SQL 2016-Feature AT TIME ZONE gesehen habe, über das ich hier bei sqlperformance.com geschrieben habe, a Vor einigen Monaten erinnerte ich mich an einen Bericht, der diese Funktion benötigte. Dieser Beitrag ist eine Fallstudie darüber, wie ich es gesehen habe, die in den T-SQL-Dienstag dieses Monats passt, der von Matt Gordon (@sqlatspeed) moderiert wird. (Heute ist der 87. T-SQL-Dienstag, und ich muss wirklich mehr Blogbeiträge schreiben, insbesondere über Dinge, die nicht von T-SQL-Dienstags angefordert werden.)
Die Situation war folgende, und das kommt Ihnen vielleicht bekannt vor, wenn Sie meinen früheren Beitrag lesen.
Lange bevor LobsterPot Solutions existierte, musste ich einen Bericht über aufgetretene Vorfälle erstellen und insbesondere aufzeigen, wie oft innerhalb des SLA reagiert und wie oft das SLA verfehlt wurde. Beispielsweise müsste auf einen Sev2-Vorfall, der an einem Wochentag um 16:30 Uhr auftrat, innerhalb von 1 Stunde eine Antwort vorliegen, während auf einen Sev2-Vorfall, der an einem Wochentag um 17:30 Uhr auftrat, eine Antwort innerhalb von 3 Stunden erfolgen müsste. Oder so ähnlich – ich habe die Zahlen vergessen, aber ich erinnere mich, dass die Helpdesk-Mitarbeiter erleichtert aufatmeten, wenn es um 17 Uhr ging, weil sie nicht so schnell auf Dinge reagieren mussten. Die 15-minütigen Sev1-Warnungen würden sich plötzlich auf eine Stunde ausdehnen, und die Dringlichkeit würde verschwinden.
Aber ein Problem würde immer dann auftreten, wenn die Sommerzeit beginnt oder endet.
Ich bin mir sicher, wenn Sie sich mit Datenbanken beschäftigt haben, kennen Sie den Schmerz, den die Sommerzeit bedeutet. Angeblich kam Ben Franklin auf die Idee – und dafür sollte er vom Blitz getroffen werden oder so. Westaustralien hat es kürzlich einige Jahre lang versucht und es vernünftigerweise aufgegeben. Und der allgemeine Konsens besteht darin, Datums-/Uhrzeitdaten in UTC zu speichern.
Wenn Sie Daten nicht in UTC speichern, laufen Sie Gefahr, dass ein Ereignis um 2:45 Uhr beginnt und um 2:15 Uhr endet, nachdem die Uhren zurückgestellt wurden. Oder einen SLA-Vorfall, der um 1:59 Uhr beginnt, kurz bevor die Uhren vorgestellt werden. Nun, diese Zeiten sind in Ordnung, wenn Sie die Zeitzone speichern, in der sie sich befinden, aber die UTC-Zeit funktioniert nur wie erwartet.
…außer für die Berichterstattung.
Denn woher soll ich wissen, ob ein bestimmtes Datum vor Beginn der Sommerzeit oder danach war? Ich weiß vielleicht, dass sich ein Vorfall um 6:30 Uhr UTC ereignete, aber ist das 16:30 Uhr in Melbourne oder 17:30 Uhr? Natürlich kann ich überlegen, in welchem Monat es ist, weil ich weiß, dass in Melbourne vom ersten Sonntag im Oktober bis zum ersten Sonntag im April Sommerzeit gilt, aber wenn es dann Kunden in Brisbane und Auckland und Los Angeles und Phoenix gibt, und an verschiedenen Orten in Indiana werden die Dinge viel komplizierter.
Um dies zu umgehen, gab es nur sehr wenige Zeitzonen, in denen SLAs für dieses Unternehmen definiert werden konnten. Es wurde einfach als zu schwierig angesehen, mehr als das zu bieten. Ein Bericht könnte dann so angepasst werden, dass er sagt:„Beachten Sie, dass an einem bestimmten Datum die Zeitzone von X auf Y geändert wurde“. Es fühlte sich chaotisch an, aber es funktionierte. Es war nicht nötig, die Windows-Registrierung nachzuschlagen, und es funktionierte einfach.
Aber heute hätte ich es anders gemacht.
Jetzt hätte ich AT TIME ZONE verwendet.
Sie sehen, jetzt könnte ich die Zeitzoneninformationen des Kunden als Eigentum des Kunden speichern. Ich könnte dann jede Vorfallszeit in UTC speichern, was es mir ermöglichte, die notwendigen Berechnungen in Bezug auf die Anzahl der Minuten für die Reaktion, Lösung usw. durchzuführen, während ich in der Lage war, unter Verwendung der Ortszeit des Kunden zu melden. Unter der Annahme, dass meine IncidentTime tatsächlich mit datetime und nicht mit datetimeoffset gespeichert wurde, wäre es einfach eine Frage der Verwendung von Code wie:
i.IncidentTime AT TIME ZONE 'UTC' AT TIME ZONE c.tz
…der die zeitzonenlose i.IncidentTime zunächst in UTC setzt, bevor sie in die Zeitzone des Kunden konvertiert wird. Und diese Zeitzone kann „AUS Eastern Standard Time“ oder „Mauritius Standard Time“ oder was auch immer sein. Und die SQL-Engine muss herausfinden, welcher Offset dafür verwendet werden soll.
An diesem Punkt kann ich sehr einfach einen Bericht erstellen, der jeden Vorfall über einen bestimmten Zeitraum hinweg auflistet und ihn in der lokalen Zeitzone des Kunden anzeigt. Ich kann den Wert in den Zeitdatentyp konvertieren und dann berichten, wie viele Vorfälle innerhalb der Geschäftszeiten aufgetreten sind oder nicht.
Und all das ist sehr nützlich, aber was ist mit der Indizierung, um dies gut zu handhaben? Schließlich ist AT TIME ZONE eine Funktion. Das Ändern der Zeitzone ändert jedoch nicht die Reihenfolge, in der die Vorfälle tatsächlich aufgetreten sind, also sollte es in Ordnung sein.
Um dies zu testen, habe ich eine Tabelle mit dem Namen dbo.Incidents erstellt und die Spalte IncidentTime indiziert. Dann habe ich diese Abfrage ausgeführt und bestätigt, dass eine Indexsuche verwendet wurde.
select i.IncidentTime, itz.LocalTime from dbo.Incidents i cross apply (select i.IncidentTime AT TIME ZONE 'UTC' AT TIME ZONE 'Cen. Australia Standard Time') itz (LocalTime) where i.IncidentTime >= '20170201' and i.IncidentTime < '20170301';
Aber ich möchte nach itz.LocalTime…
filternselect i.IncidentTime, itz.LocalTime from dbo.Incidents i cross apply (select i.IncidentTime AT TIME ZONE 'UTC' AT TIME ZONE 'Cen. Australia Standard Time') itz (LocalTime) where itz.LocalTime >= '20170201' and itz.LocalTime < '20170301';
Kein Glück. Der Index gefiel ihm nicht.
Die Warnungen sind darauf zurückzuführen, dass viel mehr als die Daten, die mich interessieren, durchsucht werden müssen.
Ich habe sogar versucht, eine Tabelle mit einem datetimeoffset-Feld zu verwenden. Schließlich kann AT TIME ZONE die Reihenfolge ändern, wenn von datetime zu datetimeoffset gewechselt wird, obwohl die Reihenfolge nicht geändert wird, wenn von datetimeoffset zu einem anderen datetimeoffset gewechselt wird. Ich habe sogar versucht sicherzustellen, dass das Ding, mit dem ich es verglichen habe, in der Zeitzone liegt.
select i.IncidentTime, itz.LocalTime from dbo.IncidentsOffset i cross apply (select i.IncidentTime AT TIME ZONE 'Cen. Australia Standard Time') itz (LocalTime) where itz.LocalTime >= cast('20170201' as datetimeoffset) AT TIME ZONE 'Cen. Australia Standard Time' and itz.LocalTime < cast('20170301' as datetimeoffset) AT TIME ZONE 'Cen. Australia Standard Time';
Immer noch kein Glück!
Also hatte ich jetzt zwei Möglichkeiten. Einer war, die konvertierte Version neben der UTC-Version zu speichern und diese zu indizieren. Ich denke, das ist ein Schmerz. Es ist sicherlich eine viel größere Datenbankänderung, als ich möchte.
Die andere Option bestand darin, das zu verwenden, was ich Hilfsprädikate nenne. Dies sind die Dinge, die Sie sehen, wenn Sie LIKE verwenden. Sie sind Prädikate, die als Suchprädikate verwendet werden können, aber nicht genau das, wonach Sie fragen.
Ich gehe davon aus, dass unabhängig von der Zeitzone, die mich interessiert, die IncidentTimes, die mir wichtig sind, in einem ganz bestimmten Bereich liegen. Diese Reichweite ist auf beiden Seiten nicht mehr als einen Tag größer als meine bevorzugte Reichweite.
Also füge ich zwei zusätzliche Prädikate hinzu.
select i.IncidentTime, itz.LocalTime from dbo.IncidentsOffset i cross apply (select i.IncidentTime AT TIME ZONE 'Cen. Australia Standard Time') itz (LocalTime) where itz.LocalTime >= cast('20170201' as datetimeoffset) AT TIME ZONE 'Cen. Australia Standard Time' and itz.LocalTime < cast('20170301' as datetimeoffset) AT TIME ZONE 'Cen. Australia Standard Time and i.IncidentTime >= dateadd(day,-1,'20170201') and i.IncidentTime < dateadd(day, 1,'20170301');
Jetzt kann mein Index verwendet werden. Es muss 30 Zeilen durchsehen, bevor es auf die 28 gefiltert wird, die ihm wichtig sind – aber das ist viel besser, als das Ganze zu scannen.
Und wissen Sie – das ist die Art von Verhalten, die ich ständig bei regulären Abfragen sehe, wie wenn ich CAST(myDateTimeColumns AS DATE) =@SomeDate mache oder LIKE verwende.
Ich bin damit einverstanden. AT TIME ZONE eignet sich hervorragend dafür, dass ich meine Zeitzonenkonvertierungen handhaben kann, und da ich bedenke, was mit meinen Abfragen vor sich geht, muss ich auch keine Leistungseinbußen hinnehmen.
@rob_farley