Glorf.it

Glorf IT

Bedenkliches aus dem IT-Alltag

28. Juni 2008 um 12:41

SQL-Server-2005: interne Tabellen einzeln prüfen und reparieren

In den letzten Tagen untersuchten wir eine defekte Datenbank eines Kunden. DBCC CHECKDB bracht nur die vielsagende Meldung, dass eine der internen Verwaltungstabellen defekt sei und man deswegen eine Sicherung einspielen solle. Nun hatte der Kunde aber keine aktuelle Sicherung (die letzte war von April). Wir konnten dem SQL Server nicht auf dokumentierte Weise entlocken wo er Defekt nun war.

Mit einer gezielten Prüfung auf die versteckten Systemtabellen fanden wird schnell den Bösewicht. Wer sich auch mal mit dem Thema befassen will, findet im Folgenden eine Liste der internen Tabellen (mit DBCC CHECKTABLE), die Schritte die zur Reparatur führten und die Ursachenanalyse. Warnung: Nur wen das interessiert, sollte ab hier weiter lesen… 😉

DBCC CHECKTABLE('sys.sysallocunits') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysasymkeys') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysbinobjs') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysbinsubobjs') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.syscerts') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysclsobjs') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.syscolpars') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysconvgroup') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysdbfiles') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysdercv') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysdesend') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysfiles1') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysftinds') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysguidrefs') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.syshobtcolumns') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.syshobts') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysidxstats') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysiscols') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysmultiobjrefs') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysnsobjs') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysobjkeycrypts') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysobjvalues') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysowners') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysprivs') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysqnames') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysremsvcbinds') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysrowsetcolumns') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysrowsetrefs') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysrowsets') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysrts') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysscalartypes') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysschobjs') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysxmitqueue') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysxmlcomponent') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysxmlfacet') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysxmlplacement') WITH NO_INFOMSGS;
DBCC CHECKTABLE('sys.sysxprops') WITH NO_INFOMSGS;

Die Prüfung sagte uns nicht nur, welche Tabelle defekt war (sys.sysallocunits), sondern auch auf welcher Seite in welchem Slot (=Datensatz) das Problem sei. Mit DBCC PAGE oder dem SQL Server Internals Viewer konnten wir leicht sehen, dass ein Wert in dieser Slot völlig untypisch war. Tatsächlich war ein Bitkipper passiert, wir mussten uns nun lediglich den Offset des gekippten Bytes innerhalb der Seite merken und damit dann das Problem komplett beheben:

Mit einem Hex-Editor gingen wir an die Adresse des bösen Bytes (Formel = Seite*8192+Offset in der Seite) und korrigierten den Wert. Danach war die Datenbank wieder völlig in Ordnung. Nun hatten wir natürlich Glück, dass wir anhand eines Vergleichs des bösen Datensatzes mit Korrekten das Problem mit bloßem Auge erkennen konnten. Aber gerade deswegen hat mich das in Hochstimmung versetzt… 😀

Risiken und Nebenwirkungen
Ein Hinweis noch: Am SQL-Server-2005 muss man erst noch die Seiten-Checksummen ausschalten, sonst meckert der SQL-Server, wenn die DB nach der Änderung wieder angehängt wird.
ALTER DATABASE
SET PAGE_VERIFY NONE

Nach dem Eingriff kann man die Checksumme wieder einschalten:
ALTER DATABASE
SET PAGE_VERIFY CHECKSUM

Außerdem sollte man vor solchen Aktionen immer eine Kopie der Dateien machen. Wenn man das falsche Byte erwischt, dann ist die Datenbank erst recht defekt… 😉

Ursachenforschung

Die Checksummenprüfung des SQL-Server (seit 2005) erkennt, wenn Bitkipper aufgrund von Problemen im Speichersystem auftreten, nicht aber im Hauptspeicher auftretende Probleme. Das geht so: Der SQL-Server ändert die Datenseiten im Hauptspeicher ständig, deswegen wird da nicht jedesmal eine Checksumme berechnet, das würde einfach zu lange dauern.

Wird die geänderte Seite auf die Platte geschrieben, dann wird eine Checksumme berechnet und auf der betreffenden Seite mit gespeichert. Beim Lesen wird die Checksumme ebenfalls berechnet und auf Abweichungen kontrolliert. Bei Abweichungen muss ein Problem im Speichersystem aufgetreten sein (Treiber, defektes RAM im IO-Caching oder Festplattenproblem). Das war hier nicht der Fall.

Will man so eine Prüfung auch schon am SQL-Server-2000, dann muss man einen Hotfix einspielen und Trace-Flag 806 setzen.

So ein Bitkipper, wie oben beschrieben, muss daher im Hauptspeicher passiert sein. Deswegen wurde der Kunde darauf hingewiesen, dass sein RAM einen Schlag hat. Es wäre natürlich auch denkbar, dass der SQL-Server-Prozess selber dort Murks hingeschrieben hätte (das nennt man "Scribbler", z.B. die im Prozessraum laufenden Extended-Procedures), aber das ist in unserer Komstellation nicht wahrscheinlich.

Wenn man es ganeu wisse wollte, dann könnte man den SQL-Server eine Zeit lang mit dem Trace-Flag 806 laufen lassen. Damit wird auch im Hauptspeicher die Checksumme gelegentlich überprüft. Weil das aber die Performance ungeheuer beeinträchtigt, sollte man das nur für kurze Zeit machen…

Weiterführende Infos:

27. Juni 2008 um 21:35

SQL Server Internals Viewer

Durch den SQL-Server-Magazine-Artikel "SQL Server Internals Viewer " von Kevin Kline "entdeckte" ich für mich das Tool. Mit diesem Werkzeug kann man sich den Inhalt von Datenbanken seitenweise ansehen. Das klingt trocken, ist aber einfach genial.

Alleine durch das Ansehen habe ich mehr verstanden als durch die Lektüre von guten Büchern, die ich irgendwann abbrach, weil es einfach zu trocken wurde… Natürlich könne man sich das auch alles mit DBCC PAGE ansehen, aber das ist dann doch noch mal eine Spur härter.

Es ist einfach unglaublich, was man auf den Seiten alles erkennen kann. So habe ich heute beispielsweise mal geschaut, ob ich einer beliebigen vom Kunden geschickten MDF ansehen kann aus wie vielen Dateien die DB bestehen sollte und wo sie zuletzt lagen. Das geht und ist gar nicht so schwer. Ich konnte auch die komplette Liste der internen Systemtabellen sehen und studieren. Einfach genial!

Ideal ist es auch zur Analyse von Datenbank-Korruptionen, aber dazu schreibe ich demnächst noch mehr. Auf der Homepage steht ein kurzer, gelungener Überblick, deswegen schenke ich mir den an dieser Stelle… 😉

Hier geht es zum Download. Interessant ich auch noch der noch junge, aber schon lesenswerte Blog des Autors Danny Gould.

21. Juni 2008 um 16:06

Paul Randal antwortet

Bei Microsoft beantwortet Paul Randal im Artikel "SQL – Fragen und Antworten: E/A-Fehler, Datenbankspiegelung und mehr" drei Fragen zu sehr speziellen SQL-Server-Themen. Mich interessierten besonders seine Ausführungen dazu wie man an defekte Testdatenbanken ran kommt, damit man mal die Datenbankprüfung testen kann. Sehr lesenswert.

Er bietet übrigens auch defekte Datenbanken (mit ausführlicher Erklärung) zum Download an.

20. Juni 2008 um 16:59

Danke für die Unterstützung

Neulich beschrieb ich einen Bug im SQL-Server und bat um Unterstützung. Nachdem der deutsche MS-Support mich abgewimmelt hatte, gelang es Itzik Ben-Gan den Bug von MS anerkennen zu lassen und man konnte auf MS-Connect "gegen" den Bug stimmen. Das haben insgesamt 19 Leute gemacht, die Bewertung ist sehr, sehr hoch. Dafür bedanke ich mich ganz herzlich. Ich bin sicher, dass sowohl der Name des Einreichers (Itzik Ben-Gan) als auch diese ungewöhnlich zahlreiche Bewertung eine Rolle bei der Bearbeitung seitens MS spielten…

Das Problem rutschte in der MS-Hierarchie inzwischen bis zur endgültigen Station: Vom Microsoft-Mitarbeiter Jim wurde der Bug bewertet, kommentiert und an zur Bearbeitung an das "Query Optimizer Development Team" weitergeleitet. Das ist sehr erfreulich. 😀

Ich finde auch die Argumentation von Jim interessant. Sie ist hier nachzulesen: Er zieht eine Parallele zum Verhalten von Compilern. Wenn klar ist, in welcher Form und in welcher Version das behoben wird, melde ich mich dazu wieder.

20. Juni 2008 um 16:43

Die Datenbank ist voll – uh uh uh

Als ich noch ziemlich klein war, da sang Helga Feddersen das Lied "Die Wanne ist voll". Keine Ahnung warum ich da jetzt dran denken muss. Aber als ich gerade eine Seite des SQL-Server-Magazines aufrufen wollte, da bekam ich folgende Meldung:

Error Executing Database Query.
[Macromedia][SQLServer JDBC Driver][SQLServer]Could not allocate space for object 'dbo.ArticleViewCount'.'PK_articleViewCountArchive' in database 'NT_ContentDB' because the 'PRIMARY' filegroup is full. Create disk space by deleting unneeded files, dropping objects in the filegroup, adding additional files to the filegroup, or setting autogrowth on for existing files in the filegroup.

Solche Meldungen sind nicht nur mega-peinlich für die Betreiber der Seiten, sondern auch sehr informativ. Hier kann man eine ganze Menge über das CMS erfahren, dass hier betrieben wird (ich meine, außer das "Macromedia" verwendet wird). Ob denen das recht ist?

Offensichtlich ist in der Datenbank 'NT_ContentDB' – in der deren "Content" verwaltet wird – kein Platz mehr, um etwas einen Datensatz in der Tabelle "ArticleViewCount" reinzuschreiben. Genauer gesagt soll in einen vermutlich gruppierten Index, der zugleich Primärschlüssel ist, etwas eingefügt werden, aber das ist bei gruppierten Indexes ja die Tabelle. Da in die betreffende Tabelle offenbar bei jedem Datenzugriff etwas geschrieben wird, ist hier ein echter Hotspot. Weil diese Tabelle in der primären Datei, also der "*.MDF", liegt, wurde das nicht unbedingt für den "Hochleistungsbetrieb" ausgelegt. Insbesondere wurden die Daten nicht auf mehrere Dateien aufgeteilt, was den Datenzugriff parallelisieren könnte und damit den Durchsatz erhöhen würde. 😮

Diese Meldung kommt beim SQL-Server übrigens nicht besonders häufig:

  • Entweder wurde für die betreffende Datenbankdatei eine feste Größe eingestellt, dann ist darin einfach der Platz voll. In diesen Fällen sollte der Admin regelmäßig den freien Platz überprüfen – am besten automatisiert.
  • Oder die Datenbank-Datei wächst automatisch. Das ist der Normalfall. Davon gehe ich daher auch hier aus. Dann ist einfach die Partition voll. Und sowas sollte einem Admin auch nicht so durchgehen… 😉

Wie schön, dass anderen auch peinliche Pannen passieren… 😀

12. Juni 2008 um 21:10

Wen interessiert der SQL-Server-2008?

Das fragte ich mich heute und fand über Google-Trends heraus, dass immer noch erheblich mehr Leute bei Google Infos zum SQL-Server-2000 abrufen als zum SQL-Server-2008. Das sollte Microsoft zu denken geben…

9. Juni 2008 um 20:43

Nürnberg: Aktuelle Infos zum SQL-Server-2008

Morgen am 10.6.2008 gibt es in Nürnberg aktuelle Infos zum SQL-Server-2008. Auf dem Vortrag, der von der SQL-Pass Franken veranstaltet wird, der örtlichen SQL-Server-Anwendergruppe, wird Steffen Krause aus seinem breiten Portfolio plaudern.

Wie immer ist der Eintritt kostenlos und hat auch sonst keine unangenehmen Nebenwirkungen. Ich bin schon sehr gespannt und kann diese Abende nur wärmstens empfehlen!

Weitere Infos gibt es hier.

4. Juni 2008 um 22:17

TSQL: Wo bin ich, auf 64- oder 32-Bit?

Wir haben das Problem, dass unsere SQL-Prozeduren auf ganz unterschiedlichen SQL-Servern laufen müssen. Und je nach Edition oder Windows-Version leicht anders reagieren soll. Um herauszufinden, ob das Skript auf einem 64-Bit- oder einem 32-Bit-SQL-Server läuft, kann man so vorgehen:

-- Laufe ich auf einem 64-Bit- oder 32-Bit-SQL-Server?
SELECT CASE WHEN @@version LIKE N'%(X64)%'
THEN '64-Bit'
ELSE '32-Bit'
END AS "SQL Server"

Ist es ein 32-Bit-SQL-Server, dann ist noch spannend, ob der unter 64-Bit (im Windows-on-Windows, WoW) oder unter 32-Bit-Windows ausgeführt wird. Wie das geht verriet mit mein Kollege Vladimir:

-- Laufe ich unter 64-Bit-Windows odr unter 32-Bit?
DECLARE @output TABLE (txt NVARCHAR(1000) NULL);

EXEC sp_configure 'show ad', 1;
RECONFIGURE WITH OVERRIDE;
EXEC sp_configure 'xp_cmdshell', 1;
RECONFIGURE WITH OVERRIDE;

INSERT INTO @output (txt)
EXEC xp_cmdshell 'systeminfo /FO LIST';

SELECT CASE WHEN EXISTS(SELECT *
FROM @output
WHERE txt LIKE 'Systemtyp: % X86-based PC'
OR txt LIKE 'System type: % X86-based PC')
THEN '32-Bit'
ELSE '64-Bit'
END AS "Windows"

EXEC sp_configure 'xp_cmdshell', 0;
RECONFIGURE WITH OVERRIDE;
EXEC sp_configure 'show ad', 0;
RECONFIGURE WITH OVERRIDE;

Auf die Aussagen aus xp_msvers kann man sich nicht verlassen, weil der 32-Bit-SQL-Server immer denkt er liefe unter Windows-32 (auch wenn er in Wirklichkeit im WoW eines Win64 läuft)…
Kennt jemand einen besseren Trick?

22. Mai 2008 um 16:39

SQL Server 2000 auf Windows Vista

Weil mich jetzt innerhalb kurzer Zeit mehrere darauf ansprachen, poste ich hier mal den Link unter dem Microsoft sagt, welche SQL-Server-Versionen unter Windows Vista laufen. Im Artikel "Running SQL Server on "Microsoft Windows Server Longhorn" or Microsoft Windows Vista" schreibt Microsoft:

In an effort to provide customers with more secure products, Microsoft Windows Server "Longhorn" and Microsoft Windows Vista are supported by SQL Server 2005 [..]. Earlier versions of SQL Server, including SQL Server 2000 (all editions including Desktop Engine edition, a.k.a MSDE), SQL Server 7.0, and SQL Server 6.5, will not be supported on Windows Server "Longhorn" or Windows Vista.

Also muss man wenigstens SQL-Server-2005 haben, wenn die Kunden Vista einsetzen wollen. In den Download-Informationen der MSDE steht es auch noch mal explizit drin:

Wichtiger Hinweis Microsoft SQL Server Desktop Engine (MSDE) wird auf dem Betriebssystem Microsoft Vista nicht unterstützt.

Tja. Das hat natürlich den Absatz des SQL-Servers-2005 enorm angekurbelt.

21. Mai 2008 um 21:16

Prompte Antwort

Ermutigt durch Christoph schickt ich eine Beschreibung des Problems mit den nicht-deterministischen Funktionen an Craig Freedman (zum Glück privat, sonst wäre meine Mail vermutlich nie angekommen). Er antwortete sehr prompt und schrieb mir, dass er das Problem dem für den Teil zuständigen Entwickler gezeigt habe. Hier die Antwort des Entwicklers, die Craig mir weiterleitete:

In general, SQL Server does not guarantee the timing of execution of scalar operators. For non-deterministic scalars (built-in and user-defined), that means the timing semantics (number of times executed and when) is not defined. In addition, it may change from one plan to another, or from one release to the next.

Er selber fügte noch als persönlichen Kommentar an, dass er die Lösung mittels temporärer Tabelle favorisiere:

Unfortunately, I am not aware of (and was unable to identify) a better workaround than simply storing the GUIDs in a temp table before joining with the original table.

Er schlug auch vor, dass ich das Kundenfeedback unter http://connect.microsoft.com melde, obwohl ich da möglicherweise die gleiche Antwort bekomme. Ich stimme ihm in all diesen Punkten zu. Es lohnt sich sicher das dort einzukippen, weil es dann mal in einem Forum öffentlich diskutiert wird. Mal sehen, ob ich morgen dazu komme…

16. Mai 2008 um 11:41

SQL Server Is Not Aware of Nondeterministic Functions

Normally I write in German, but I hope that maybe some Microsoft Engineer might read it and put the problem on their schedule. So, I apologise to my German readers for writing in poor English… 😉

When the SQL Server optimizer internally transforms a SQL Statement to an other, it normally results in a better execution plan. When You write for example a subquery the optimizer tries to make a join instead. This is fine as long a the result set is the same. But now we discovered a situation when this is not true: If you use a non-deterministic function in a query, the optimizer use the same query plan as if the function were deterministic. Let us examine this simple example:

We have a table with 5 rows: the key consists of the tupel (ID1,ID2). We add a new column to add a GUID later.

ID1 ID2 GUID
1 1 NULL
1 2 NULL
2 1 NULL
2 2 NULL
3 1 NULL

For each different value of ID1 we want to add a GUID with the non-deterministic function NewID():

SELECT
ID1
, NEWID() as [NewId]
FROM Table1
GROUP BY ID1

This results in something like:

ID1 NewId
1 B30AD595-926B-40F6-A815-D8871C81CE89
2 6842624D-DC40-4A3F-A2EB-A8178814D12E
3 12E9F691-7F8B-41F8-829B-8CED5E26C127

Now, we want to use the above query to assign the new values to the column GUID. To make the execution plan easier to understand, I used a SELECT, not an UPDATE. But the result is the same:

SELECT
t.ID1
, sub.NewId
FROM (SELECT
ID1
, NEWID() as [NewId]
FROM Table1
GROUP BY ID1) as sub
JOIN Table1 as t
ON t.ID1=sub.ID1

It should result in something like this:

ID1 NewId
1 B30AD595-926B-40F6-A815-D8871C81CE89
1 B30AD595-926B-40F6-A815-D8871C81CE89
2 6842624D-DC40-4A3F-A2EB-A8178814D12E
2 6842624D-DC40-4A3F-A2EB-A8178814D12E
3 12E9F691-7F8B-41F8-829B-8CED5E26C127

But this is not the case. The result is:

ID1 NewId
1 B30AD595-926B-40F6-A815-D8871C81CE89
1 4EC62D6E-8ABC-4563-8A81-2DB8E655D3CA
2 6842624D-DC40-4A3F-A2EB-A8178814D12E
2 73B7386E-688D-48D2-A585-C66C6911EFC3
3 12E9F691-7F8B-41F8-829B-8CED5E26C127

Do you see the difference? The reason for this is the wrong execution plan:

|--Compute Scalar(DEFINE:([Expr1004]=newid()))
|–Merge Join(Inner Join, MERGE:([tempdb].[dbo].[Table1].[ID1])=([t].[ID1]), RESIDUAL:([tempdb].[dbo].[Table1].[ID1]=[tempdb].[dbo].[Table1].[ID1] as [t].[ID1]))
|–Sort(DISTINCT ORDER BY:([tempdb].[dbo].[Table1].[ID1] ASC))
| |–Table Scan(OBJECT:([tempdb].[dbo].[Table1]))
|–Sort(ORDER BY:([t].[ID1] ASC))
|–Table Scan(OBJECT:([tempdb].[dbo].[Table1] AS [t]))

The optimizer choose to do the join first and to execute the function for each row. This is not correct.

So the general rule I recommend: Do not use non-deterministic functions in complex SQL queries. The result depends on the chosen query plan. Today the result may be correct, but if the row numbers change or you add an index the result may get incorrect! This is not as it should be.

Unfortunately I was not able to convince the German support that this is a bug. They send me a workaround like this:

DECLARE @Rows AS BigInt;
SELECT @Rows = Count(*) FROM Table1;

SELECT
t.ID1
, sub.NewId
FROM (SELECT TOP(@Rows)
ID1
, NEWID() as [NewId]
FROM Table1
GROUP BY ID1
ORDER BY NewId) as sub
JOIN Table1 as t
ON t.ID1=sub.ID1

The performance is very poor and I would recommend using a temporary table to store the new GUIDs instead, if you have a big row number. This seems to perform a little better.


Update (21.5.2008): Encouraged by Christoph I send the problem to Craig Freedman. He answered the very next day, that he presented the problem to the developer responsible for this code for his comments on that question. Here is the response of the developer Craig forwarded to me:

In general, SQL Server does not guarantee the timing of execution of scalar operators. For non-deterministic scalars (built-in and user-defined), that means the timing semantics (number of times executed and when) is not defined. In addition, it may change from one plan to another, or from one release to the next.

Craig added:

Unfortunately, I am not aware of (and was unable to identify) a better workaround than simply storing the GUIDs in a temp table before joining with the original table.

(My Thanks to Craig! I am very impressed that he ask the responsible developer. And thanks to the unknown developer.)


Update (11.6.2008): Itzik Ben-Gan did it! Microsoft confirmed to him this is a bug.

Currently I read the very good book "T-SQL Querying" written by Itzik. In the very first chapter he described that the optimizer can make shurtcuts in the physical execution plan if the result set is the same like using the logical execution plan. I asked him about his opinion about this problem and he made Microsoft to accept that this is a bug. He wrote:

I got back a response from Microsoft saying it is a bug, and apparently a regression from SQL Server 2000.

If you think Microsoft should fix this bug, then "vote" for this bug, please. The more people vote, the more likely MS will fix it.


Update (19.6.2008): Thanks for your support. The MS Developer Jim answered to this bug:

Thankyou for this bug report. One of the most interesting discussions around.

This hits to the very heart of the issue – is optimization allowed to change a program's semantics? Ie: if a program yields certain answers, but runs slowly, is it legitimate for a Query Optimizer make that program run faster, yet also change the results given?

Before shouting "NO!" (my own personal inclination too :-), consider: the good news is that, in 99% of cases, the answers ARE the same. So Query Optimization is a clear win. The bad news is that, if the query contains side-effecting code, then different plans CAN indeed yield different results. And NEWID() is one such side-effecting (non-deterministic) 'function' that exposes the difference. [Actually, if you experiment, you can devise others – for example, short-circuit evaluation of AND clauses: make the second clause throw an arithmetic divide-by-zero – different optimizations may execute that second clause BEFORE the first clause] This reflects Craig's explanation, elsewhere in this thread, that SqlServer does not guarantee when scalar operators are executed.

So, we have a choice: if we want to guarantee a certain behavior in the presence of non-deterministic (side-effecting) code – so that results of JOINs, for example, follow the semantics of a nested-loop execution – then we can use appropriate OPTIONs to force that behavior – as UC points out. But the resulting code will run slow – that's the cost of, in effect, hobbling the Query Optimizer.

All that said, we are moving the Query Optimizer in the direction of "as expected" behavior for NEWID() – trading off performance for "results as expected".
[…]
Anyhow, this bug is now assigned to the QO Dev team for a deeper look.

I presume "QO Dev team" is the "Query Optimizer Development team". 😉


Update (27.7.2008): In the last issue of the SQL Server Magazine Newsletter Itzik Ben-Gan wrote some bad news about this bug:

I posted the bug on Microsoft Connect (FeedbackID=350485), and after consideration, Microsoft decided to close the item and mark it as “Won’t Fix”. The reasoning behind the decision not to fix the bug is that in the vast majority of the cases, the optimization aspects that lead to the bug yield better performance without sacrificing the correctness of the query, and if you fall into one of the unusual cases where the correctness of the query is compromised, you can consider alternatives (e.g., physically materialize the data along with the NEWID values in a table).

This was quite unexpected for me. But I think there is nothing we can do about it. Thats a pity…

13. Mai 2008 um 09:57

atemberaubende Arbeitszeiten bei Microsoft

Neulich meldete ich ein Problem zum Microsoft SQL-Server von dem ich immer noch denke, dass es ein Bug und kein Features ist. Ich lieferte ein leichtes Repro und belegte anhand des Zugriffsplans das Fehlverhalten. Der First-Level-Supportmitarbeiter antwortete in einer Mail, dass er das Verhalten auch nicht ganz erwartungskonform fand und wollte sich mit einem Backline-Supporter unterhalten. Das sind Leute hier in Europa, die sich ganz gut auskennen und dann entscheiden, ob das als potentieller Bug nach Redmond gemeldet wird oder nicht. Im nächsten Schritt wird so ein gemeldeter Bug-Verdacht für gewöhnlich von einem der Entwickler begutachtet, der für das entsprechende Modul verantwortlich ist. So war es jedenfalls bisher. Diesmal scheiterte ich an der zweiten Hürde (ohne dass ich mit dem Backline-Suppporter selber sprechen durfte).
Bug or not?
In der zweiten Antwort schrieb der First-Level-Supporter, dass sei ein Feature und kein Bug. Der Backline-Engineer habe ihn davon überzeugt und deswegen würde es nicht als potentieller Bug nach Redmond gemeldet. Ich lieferte weitere Argumente und bekam wieder eine abschlägige Antwort. Das passierte noch ein mal. Es gab dort intern also drei Diskussionen (bzw. manchmal mailen die sich auch bloß gegenseitig, das war diesmal offenbar nicht so).

Als nächstes fragte unser zuständiger Microsoft-Betreuer nach, ob sie an dem Problem wirklich dran bleiben sollen, es seien im MS-Support immerhin schon 11 Arbeitsstunden verbraucht worden, die wir bezahlen müssen (weil es ja kein Bug sei). Und wie viele Stunden uns das noch wert wäre? Ich war ziemlich platt, dass allein für die bisherigen drei Diskussionen zwischen Firstlevel- und Secondlevel-Support 11 Stunden veranschlagt wurden. Da es zwei Leute waren, dauerten die Diskussionen in Summe also 5,5 Stunden. Das zu glauben fällt mir zwar schwer, aber OK – manche verstehen Probleme schneller, manche brauchen etwas länger. Darüber zu urteilen steht mir nicht zu… 😉

Also antwortete ich (gegen Mittag), dass das ein wichtiges Problem sei und wir so lange dran bleiben bis eine abschließende Antwort vom Engineering aus Redmond käme. Um 15:30 Uhr mailte dann der First-Level-Mann, er habe jetzt auch noch mit demjenigen in Redmond gesprochen, der entscheidet ob etwas ein Bug sei oder nicht. Auch er sei der Meinung es sei kein Bug, daher werden sie es nicht als potentiellen Bug nach Redmond melden.
Aber in der Beschreibung des Features sei nicht alles ganz richtig wiedergegeben und das werde als Bug in der Doku gemeldet. Deswegen müssten wir nichts zahlen.

Das löste bei mir eine Reihe von Fragen aus:

  • Klingt nach Kuhhandel – wer kennt das Spiel? Offenbar dachte der Supporter es ginge mir darum, dass wir nichts zahlen wollen. Schade, denn tatsächlich ist das Problem für uns wichtig.
  • Gibt es in Redmond wirklich einen Mann, der entscheidet, ob etwas im SQL-Server ein Bug ist oder nicht? Als ich zuletzt dort war, war es noch anders.
  • Wenn das so ist, warum wurde mit ihm darüber inoffiziell diskutiert, anstelle ihm den Fall mit allen Informationen komplett zu schicken, damit er auch das Repro sieht?
  • Ist der Mann in Redmond wirklich zwischen 3 Uhr nachts und 6:30 Uhr am Morgen (Ortszeit in Redmond) erreichbar? Das nenne ich atemberaubend und mehr als beeindruckend!

Nun hatte ich die Wahl entweder das Ganze höher aufzuhängen (und vielleicht sogar den Supporter persönlich anzugreifen) oder den Fall zu schließen. Ich ließ ihn schließen… und werde versuchen das Problem über andere Kanäle bis Redmond zu bekommen. 🙁