Wie kann der Mangel an impliziter Ordnung in einer Datenbank nachgewiesen werden?

21

Kürzlich habe ich Kollegen erklärt, wie wichtig es ist, eine Spalte zu haben, nach der Daten in einer Datenbanktabelle sortiert werden können, wenn dies beispielsweise für chronologisch geordnete Daten erforderlich ist. Dies erwies sich als etwas schwierig, da sie ihre Abfrage einfach scheinbar endlos wiederholen konnten und immer dieselbe Reihe von Zeilen in derselben Reihenfolge zurückgaben.

Ich habe das schon einmal bemerkt und konnte nur darauf bestehen, dass sie mir vertrauen und nicht einfach davon ausgehen, dass sich eine Datenbanktabelle wie eine herkömmliche CSV- oder Excel-Datei verhält.

Beispiel: Ausführen der Abfrage (PostgreSQL)

create table mytable (
    id INTEGER PRIMARY KEY,
    data TEXT
);
INSERT INTO mytable VALUES
    (0, 'a'),
    (1, 'b'),
    (2, 'c'),
    (3, 'd'),
    (4, 'e'),
    (5, 'f'),
    (6, 'g'),
    (7, 'h'),
    (8, 'i'),
    (9, 'j');

erstellt eine Tabelle mit einer klaren konzeptuellen Reihenfolge. Dieselben Daten auf einfachste Weise auszuwählen, wäre:

SELECT * FROM mytable;

Gibt mir immer die folgenden Ergebnisse:

 id | data 
----+------
  0 | a
  1 | b
  2 | c
  3 | d
  4 | e
  5 | f
  6 | g
  7 | h
  8 | i
  9 | j
(10 rows)

Ich kann dies immer und immer wieder tun und es werden mir immer die gleichen Daten in der gleichen Reihenfolge zurückgegeben. Ich weiß jedoch, dass diese implizite Reihenfolge gebrochen werden kann. Ich habe sie bereits zuvor gesehen, insbesondere bei großen Datenmengen, bei denen ein zufälliger Wert bei Auswahl anscheinend an die "falsche" Stelle geworfen wird. Mir ist aber aufgefallen, dass ich nicht weiß, wie das passiert oder wie ich es reproduzieren soll. Ich finde es schwierig, Ergebnisse bei Google zu erhalten, da die Suchanfrage in der Regel nur allgemeine Informationen zum Sortieren von Ergebnismengen enthält.

Meine Fragen lauten also im Wesentlichen:

  1. Wie kann ich nachweislich und konkret nachweisen, dass die Rückgabereihenfolge von Zeilen aus einer Abfrage ohne ORDER BYAnweisung nicht zuverlässig ist, indem ich vorzugsweise eine Aufschlüsselung der impliziten Reihenfolge verursache und zeige, auch wenn die betreffende Tabelle nicht aktualisiert oder bearbeitet wird ?

  2. Macht es überhaupt einen Unterschied, ob die Daten nur einmal massenweise eingefügt und dann nie wieder aktualisiert werden?

Ich würde eine postgres-basierte Antwort vorziehen, da dies diejenige ist, mit der ich am vertrautesten bin, aber ich bin mehr an der Theorie selbst interessiert.


quelle
6
"Nie wieder geschrieben oder aktualisiert" - warum ist das eine Tabelle? Klingt nach einer Datei. Oder eine Aufzählung. Oder etwas, das sich nicht in einer Datenbank befinden muss. Wenn es chronologisch ist, gibt es keine Datumsspalte, nach der sortiert werden kann? Wenn es auf die Chronologie ankommt, würden Sie denken, dass Informationen wichtig genug sind, um sie in der Tabelle zu haben. Wie auch immer, Pläne können sich ändern, wenn jemand einen neuen Index löscht oder erstellt oder Ereignisse wie Speicheränderungen, Ablaufverfolgungsflags oder andere Einflüsse. Ihr Argument klingt wie: „Ich habe nie meinen Sicherheitsgurt angelegt und bin nie durch die Windschutzscheibe gegangen, daher werde ich auch weiterhin nicht angeschnallt.“ :-(
Aaron Bertrand
9
Einige logische Probleme können einfach nicht technisch oder ohne HR-Beteiligung gelöst werden. Wenn Ihr Unternehmen Entwicklerpraktiken zulassen möchte, bei denen es darauf ankommt, an Voodoo zu glauben und die Dokumentation zu ignorieren, und Ihr Anwendungsfall sich wirklich auf eine winzige Tabelle beschränkt, die niemals aktualisiert wird, lassen Sie sie einfach ihren Weg und aktualisieren Sie Ihren Lebenslauf. Es lohnt sich nicht zu streiten.
Aaron Bertrand
1
Sie haben keine Grundlage, um "immer" zu behaupten. Sie können nur "hat immer", "wenn ich überprüft" behaupten. Die Sprache hat eine Definition - das ist der Vertrag mit dem Benutzer.
philipxy
10
Ich bin neugierig, warum Ihre Kollegen es ablehnen, die order byKlausel zu ihren Abfragen hinzuzufügen . Versuchen sie, im Quellcode-Speicher zu sparen? Tastaturverschleiß? Wie lange dauert es, die gefürchtete Klausel einzugeben?
Mustaccio
2
Ich habe immer gedacht, dass Datenbank-Engines die ersten Abfragereihen, für die die Semantik keine Reihenfolge garantiert, zufällig durchlaufen sollten, um das Testen zu erleichtern.
Doug McClean

Antworten:

30

Ich sehe drei Möglichkeiten, um sie zu überzeugen:

  1. Lassen Sie sie dieselbe Abfrage ausführen, jedoch mit einer größeren Tabelle (mehr Zeilen) oder wenn die Tabelle zwischen den Ausführungen aktualisiert wird. Oder es werden neue Zeilen eingefügt und einige alte gelöscht. Oder ein Index wird zwischen den Ausführungen hinzugefügt oder entfernt. Oder der Tisch wird abgesaugt (in Postgres). Oder Indizes werden neu erstellt (in SQL Server). Oder die Tabelle wird von einem Cluster in einen Heap geändert. Oder der Datenbankdienst wird neu gestartet.

  2. Sie können vorschlagen, dass sie beweisen, dass verschiedene Ausführungen dieselbe Reihenfolge zurückgeben. Können sie es beweisen? Können sie eine Reihe von Tests bereitstellen, die belegen, dass eine Abfrage das Ergebnis in derselben Reihenfolge liefert, unabhängig davon, wie oft sie ausgeführt wird?

  3. Stellen Sie die Dokumentation verschiedener DBMS in dieser Angelegenheit bereit. Beispielsweise:

PostgreSQL :

Zeilen sortieren

Nachdem eine Abfrage eine Ausgabetabelle erstellt hat (nachdem die Auswahlliste verarbeitet wurde), kann sie optional sortiert werden. Wenn die Sortierung nicht ausgewählt ist, werden die Zeilen in einer nicht angegebenen Reihenfolge zurückgegeben. Die tatsächliche Reihenfolge hängt in diesem Fall von den Typen des Scan- und Join-Plans sowie von der Reihenfolge auf der Festplatte ab, auf die Sie sich jedoch nicht verlassen müssen. Eine bestimmte Ausgabereihenfolge kann nur garantiert werden, wenn der Sortierschritt explizit ausgewählt wird.

SQL Server :

SELECT- ORDER BYKlausel (Transact-SQL)

Sortiert die von einer Abfrage in SQL Server zurückgegebenen Daten. Verwenden Sie diese Klausel, um:

Ordnen Sie die Ergebnismenge einer Abfrage nach der angegebenen Spaltenliste und begrenzen Sie optional die zurückgegebenen Zeilen auf einen angegebenen Bereich. Die Reihenfolge, in der Zeilen in einer Ergebnismenge zurückgegeben werden, kann nur garantiert werden, wenn eine ORDER BYKlausel angegeben ist.

Oracle :

order_by_clause

Verwenden Sie die ORDER BYKlausel, um die von der Anweisung zurückgegebenen Zeilen zu ordnen. Ohne order_by_clause kann nicht garantiert werden, dass dieselbe Abfrage, die mehr als einmal ausgeführt wurde, Zeilen in derselben Reihenfolge abruft.

ypercubeᵀᴹ
quelle
Bei sehr kleinen Tabellen, die nicht geändert werden, kann dieses Verhalten auftreten. Das wird erwartet. Es ist aber auch nicht garantiert. Die Reihenfolge kann sich ändern, weil Sie einen Index hinzugefügt oder geändert haben oder die Datenbank und möglicherweise viele andere Fälle neu gestartet haben.
Ypercubeᵀᴹ
6
Wenn die Bestellung von Bedeutung ist, sollte jeder, der für die Überprüfung seines Codes verantwortlich ist, diese ablehnen, bis er ORDER BY verwendet. Die Entwickler der DBMS (Oracle, SQL Server, Postgres) sagen alle dasselbe darüber, was ihre Produktgarantie ist und was nicht (und sie werden viel mehr bezahlt als ich, damit sie wissen, was sie sagen, abgesehen davon, dass sie diese verdammt gebaut haben Dinge).
ypercubeᵀᴹ
1
Auch wenn die Reihenfolge jetzt gleich aussieht, ist es sicher, dass diese Tabellen während der gesamten Lebensdauer der von Ihnen erstellten Software niemals aktualisiert werden? Dass nie mehr Zeilen eingefügt werden?
Ypercubeᵀᴹ
1
Gibt es eine Garantie, dass dieser Tisch immer so klein bleibt? Gibt es eine Garantie, dass keine weiteren Spalten hinzugefügt werden? Ich kann Dutzende verschiedener Fälle beobachten, in denen die Tabelle in Zukunft möglicherweise geändert wird (und einige dieser Änderungen können die Reihenfolge eines Abfrageergebnisses beeinflussen). Ich schlage vor, Sie bitten sie, alle diese Fragen zu beantworten. Können sie garantieren, dass so etwas niemals passieren wird? Und warum fügen sie kein einfaches hinzu ORDER BY, das die Reihenfolge garantiert, egal wie sich die Tabelle ändern wird ? Warum nicht einen Safe hinzufügen lassen, der nicht schadet?
Ypercubeᵀᴹ
10
Die Dokumentation sollte ausreichend sein. Alles andere ist eine zweite Vermutung und wird auf keinen Fall als endgültig angesehen, egal was Sie beweisen. Es wird immer etwas sein, was Sie getan haben und das wahrscheinlich auf Ihre Kosten erklärt werden kann, und nicht etwas, das es ist . Bewaffnen Sie sich mit der Dokumentation, reichen Sie Ihre "Garantie" schriftlich ein und holen Sie einfach die schriftliche Erlaubnis ein, Zeilen nicht in der erforderlichen Reihenfolge zurückzugeben (Sie erhalten sie nicht).
19

Dies ist wieder die Geschichte mit dem schwarzen Schwan. Wenn Sie noch keine gesehen haben, heißt das nicht, dass sie nicht existieren. Hoffentlich führt dies in Ihrem Fall nicht zu einer weiteren weltweiten Finanzkrise, nur zu einigen unglücklichen Kunden.

In der Postgres- Dokumentation heißt es ausdrücklich:

Wenn ORDER BY nicht angegeben ist, werden die Zeilen in der Reihenfolge zurückgegeben, in der das System sie am schnellsten erstellt.

"Das System" umfasst in diesem Fall den Postgres-Daemon selbst (einschließlich der Implementierung seiner Datenzugriffsmethoden und des Abfrageoptimierers), das zugrunde liegende Betriebssystem, das logische und physische Layout des Datenbankspeichers, möglicherweise sogar CPU-Caches. Da Sie als Datenbankbenutzer keine Kontrolle über diesen Stapel haben, sollten Sie sich nicht darauf verlassen, dass er sich für immer so verhält, wie er sich in dieser Minute verhält.

Ihre Kollegen begehen den hastigen Generalisierungsfehler . Um ihre Aussage zu widerlegen, genügt es zu zeigen, dass ihre Annahme nur einmal falsch ist, zB durch diese dbfiddle .

mustaccio
quelle
12

Betrachten Sie das folgende Beispiel, in dem wir drei verwandte Tabellen haben. Bestellungen, Benutzer und Bestelldetails. OrderDetails ist mit Fremdschlüsseln an die Orders-Tabelle und die Users-Tabelle gebunden. Dies ist im Wesentlichen eine sehr typische Konfiguration für relationale Datenbanken. wohl der ganze Zweck eines relationalen DBMS.

USE tempdb;

IF OBJECT_ID(N'dbo.OrderDetails', N'U') IS NOT NULL
DROP TABLE dbo.OrderDetails;

IF OBJECT_ID(N'dbo.Orders', N'U') IS NOT NULL
DROP TABLE dbo.Orders;

IF OBJECT_ID(N'dbo.Users', N'U') IS NOT NULL
DROP TABLE dbo.Users;

CREATE TABLE dbo.Orders
(
    OrderID int NOT NULL
        CONSTRAINT OrderTestPK
        PRIMARY KEY
        CLUSTERED
    , SomeOrderData varchar(1000)
        CONSTRAINT Orders_somedata_df
        DEFAULT (CRYPT_GEN_RANDOM(1000))
);

CREATE TABLE dbo.Users
(
    UserID int NOT NULL
        CONSTRAINT UsersPK
        PRIMARY KEY
        CLUSTERED
    , SomeUserData varchar(1000)
        CONSTRAINT Users_somedata_df
        DEFAULT (CRYPT_GEN_RANDOM(1000))
);

CREATE TABLE dbo.OrderDetails
(
    OrderDetailsID int NOT NULL
        CONSTRAINT OrderDetailsTestPK
        PRIMARY KEY
        CLUSTERED
    , OrderID int NOT NULL
        CONSTRAINT OrderDetailsOrderID
        FOREIGN KEY
        REFERENCES dbo.Orders(OrderID)
    , UserID int NOT NULL
        CONSTRAINT OrderDetailsUserID
        FOREIGN KEY
        REFERENCES dbo.Users(UserID)
    , SomeOrderDetailsData varchar(1000)
        CONSTRAINT OrderDetails_somedata_df
        DEFAULT (CRYPT_GEN_RANDOM(1000))
);

INSERT INTO dbo.Orders (OrderID)
SELECT TOP(100) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM sys.syscolumns sc;

INSERT INTO dbo.Users (UserID)
SELECT TOP(100) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM sys.syscolumns sc;

INSERT INTO dbo.OrderDetails (OrderDetailsID, OrderID, UserID)
SELECT TOP(10000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
    , o.OrderID
    , u.UserID
FROM sys.syscolumns sc
    CROSS JOIN dbo.Orders o
    CROSS JOIN dbo.Users u
ORDER BY NEWID();

CREATE INDEX OrderDetailsOrderID ON dbo.OrderDetails(OrderID);
CREATE INDEX OrderDetailsUserID ON dbo.OrderDetails(UserID);

Hier fragen wir die OrderDetails-Tabelle mit der Benutzer-ID 15 ab:

SELECT od.OrderDetailsID
    , o.OrderID
    , u.UserID
FROM dbo.OrderDetails od
    INNER JOIN dbo.Users u ON u.UserID = od.UserID
    INNER JOIN dbo.Orders o ON od.OrderID = o.OrderID
WHERE u.UserID = 15

Die Ausgabe der Abfrage sieht folgendermaßen aus:

╔════════════════╦═════════╦════════╗
║ OrderDetailsID ║ OrderID ║ UserID ║
╠════════════════╬═════════╬════════╣
║ 2200115 ║ 2 ║ 15 ║
║ 630215 ║ 3 ║ 15 ║
║ 1990215 ║ 3 ║ 15 ║
4960215, 3, 15
║ 100715 ║ 8 ║ 15 ║
║ 3930815 ║ 9 ║ 15 ║
║ 6310815 ║ 9 ║ 15 ║
4441015, 11, 15
║ 2171315 ║ 14 ║ 15 ║
║ 3431415 ║ 15 ║ 15 ║
║ 4571415 ║ 15 ║ 15 ║
║ 6421515 ║ 16 ║ 15 ║
║ 2271715 ║ 18 ║ 15 ║
║ 2601715 ║ 18 ║ 15 ║
║ 3521715 ║ 18 ║ 15 ║
║ 221815 ║ 19 ║ 15 ║
║ 3381915 ║ 20 ║ 15 ║
4471915 ║ 20 ║ 15 ║
╚════════════════╩═════════╩════════╝

Wie Sie sehen, stimmt die Reihenfolge der Zeilenausgabe nicht mit der Reihenfolge der Zeilen in der Tabelle "OrderDetails" überein.

Durch Hinzufügen eines expliziten ORDER BYBefehls wird sichergestellt, dass die Zeilen in der gewünschten Reihenfolge an den Client zurückgegeben werden:

SELECT od.OrderDetailsID
    , o.OrderID
    , u.UserID
FROM dbo.OrderDetails od
    INNER JOIN dbo.Users u ON u.UserID = od.UserID
    INNER JOIN dbo.Orders o ON od.OrderID = o.OrderID
WHERE u.UserID = 15
ORDER BY od.OrderDetailsID;
╔════════════════╦═════════╦════════╗
║ OrderDetailsID ║ OrderID ║ UserID ║
╠════════════════╬═════════╬════════╣
║ 3915 ║ 40 ║ 15 ║
║ 100715 ║ 8 ║ 15 ║
║ 221815 ║ 19 ║ 15 ║
║ 299915 ║ 100 ║ 15 ║
║ 368215 ║ 83 ║ 15 ║
║ 603815 ║ 39 ║ 15 ║
║ 630215 ║ 3 ║ 15 ║
║ 728515 ║ 86 ║ 15 ║
║ 972215 ║ 23 ║ 15 ║
║ 992015 ║ 21 ║ 15 ║
║ 1017115 ║ 72 ║ 15 ║
║ 1113815 ║ 39 ║ 15 ║
╚════════════════╩═════════╩════════╝

Wenn Reihenfolge der Zeilen ist zwingend notwendig, und Ihre Ingenieure wissen , dass , um zwingend notwendig ist, sollten sie immer nur wollen eine verwenden ORDER BYAussage, da sie könnte sie ihre Bezeichnung kosten , wenn ein Fehler ist zu falscher Reihenfolge zusammen.

Eine zweite, vielleicht noch lehrreiches Beispiel, die mit OrderDetailsTabelle von oben, wo wir nicht alle anderen Tabellen verknüpft werden , sondern eine einfache Anforderung sowohl die OrderID und die Benutzer - ID zu finden haben Zeilen übereinstimmt, sehen wir das Problem.

Wir erstellen einen Index zur Unterstützung der Abfrage, wie Sie es wahrscheinlich im wirklichen Leben tun würden, wenn die Leistung in irgendeiner Weise wichtig ist (wann nicht?).

CREATE INDEX OrderDetailsOrderIDUserID ON dbo.OrderDetails(OrderID, UserID);

Hier ist die Abfrage:

SELECT od.OrderDetailsID
FROM dbo.OrderDetails od
WHERE od.OrderID = 15
    AND (od.UserID = 21 OR od.UserID = 22)

Und die Ergebnisse:

╔════════════════╗
║ OrderDetailsID ║
╠════════════════╣
║ 21421 ║
5061421
║ 7091421 ║
691422
3471422
7241422
╚════════════════╝

Das Hinzufügen einer ORDER BYKlausel stellt sicher, dass wir auch hier die richtige Sortierung erhalten.

Diese Modelle sind nur einfache Beispiele, bei denen Zeilen ohne explizite ORDER BYAnweisung nicht garantiert "in Ordnung" sind . Es gibt noch viele weitere Beispiele, und da sich der Code der DBMS-Engine häufig ändert, kann sich das spezifische Verhalten im Laufe der Zeit ändern.

Max Vernon
quelle
10

Als praktisches Beispiel ändert sich in Postgres derzeit die Reihenfolge, wenn Sie eine Zeile aktualisieren:

% SELECT * FROM mytable;
 id | data 
----+------
  0 | a
  1 | b
  2 | c
  3 | d
  4 | e
  5 | f
  6 | g
  7 | h
  8 | i
  9 | j
(10 rows)

% UPDATE mytable SET data = 'ff' WHERE id = 5;
UPDATE 1
% SELECT * FROM mytable;
 id | data 
----+------
  0 | a
  1 | b
  2 | c
  3 | d
  4 | e
  6 | g
  7 | h
  8 | i
  9 | j
  5 | ff
(10 rows)

Ich denke nicht, dass die Regeln dieser bestehenden impliziten Reihenfolge irgendwo dokumentiert sind, definitiv ohne Vorankündigung geändert werden können und definitiv kein portables Verhalten zwischen DB-Engines ist.

JoL
quelle
Es ist dokumentiert: In der Antwort von ypercube wird die Dokumentation zitiert, aus der hervorgeht, dass die Bestellung nicht spezifiziert ist.
Leichtigkeit Rennen mit Monica
@LightnessRacesinOrbit Ich nehme das als Dokumentation, die uns ausdrücklich sagt, dass es nicht dokumentiert ist. Ich meine, es ist auch wahr, dass alles, was nicht in der Dokumentation steht, nicht spezifiziert ist. Es ist eine Art Tautologie. Jedenfalls habe ich diesen Teil der Antwort bearbeitet, um genauer zu sein.
JoL
3

Nicht gerade eine Demo, aber zu lang für einen Kommentar.

Bei großen Tabellen führen einige Datenbanken verschachtelte parallele Überprüfungen durch:

Wenn zwei Abfragen dieselbe Tabelle durchsuchen möchten und fast zur selben Zeit eintreffen, befindet sich die erste möglicherweise auf dem Weg durch die Tabelle, wenn die zweite gestartet wird.

Die zweite Abfrage kann Datensätze ab der Tabellenmitte empfangen (wenn die erste Abfrage abgeschlossen ist) und die Datensätze dann ab dem Tabellenanfang empfangen.

Jasen
quelle
2

Erstellen Sie einen gruppierten Index mit der "falschen" Reihenfolge. Beispiel: Cluster ein ID DESC. Dies gibt häufig die umgekehrte Reihenfolge aus (obwohl dies auch nicht garantiert ist).

usr
quelle