Come dimostrare la mancanza di ordine implicito in un database?


21

Recentemente ho spiegato ai colleghi l'importanza di disporre di una colonna in base alla quale ordinare i dati in una tabella di database se è necessario farlo, ad esempio per i dati ordinati cronologicamente. Ciò si è rivelato alquanto difficile perché potevano semplicemente rieseguire la loro query apparentemente all'infinito e avrebbe sempre restituito lo stesso set di righe nello stesso ordine.

L'ho notato prima e tutto ciò che potevo davvero fare è insistere sul fatto che si fidano di me e non semplicemente suppongono che una tabella di database si comporti come un file CSV o Excel tradizionale.

Ad esempio, eseguendo la query (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');

creerà una tabella con un chiaro ordine concettuale. Selezionare gli stessi dati nel modo più semplice sarebbe:

SELECT * FROM mytable;

Mi dà sempre i seguenti risultati:

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

Posso farlo più e più volte e mi restituirà sempre gli stessi dati nello stesso ordine. Tuttavia, so che questo ordine implicito può essere infranto, l'ho già visto in precedenza, in particolare in set di dati di grandi dimensioni, in cui un valore casuale apparentemente verrà gettato nel posto "sbagliato" quando selezionato. Ma mi è venuto in mente che non so come questo accada o come riprodurlo. Trovo difficile ottenere risultati su Google perché la query di ricerca tende a restituire un aiuto generale sull'ordinamento dei set di risultati.

Quindi, le mie domande sono essenzialmente queste:

  1. Come posso dimostrare in modo dimostrabile e concreto che l'ordine di restituzione delle righe da una query senza ORDER BYun'istruzione non è affidabile, preferibilmente causando e mostrando una suddivisione dell'ordine implicito anche quando la tabella in questione non viene aggiornata o modificata ?

  2. Fa alcuna differenza se i dati vengono inseriti una sola volta in massa e non vengono mai aggiornati di nuovo?

Preferirei una risposta basata su Postgres poiché è quella con cui ho più familiarità, ma sono più interessato alla teoria stessa.


6
"Mai scritto o aggiornato di nuovo" - perché è una tabella? Sembra un file. O un enum. O qualcosa che non ha bisogno di essere in un database. Se è cronologico, non esiste una colonna di date per ordinare? Se la cronologia conta, penseresti che le informazioni sarebbero abbastanza importanti da avere nella tabella. Ad ogni modo, i piani possono cambiare a causa della caduta o della creazione di un nuovo indice da parte di qualcuno o di eventi come cambiamenti di memoria, tracce di tracciamento o altre influenze. La loro discussione suona come "Non indosso mai la cintura di sicurezza e non ho mai attraversato il parabrezza, quindi continuerò a non indossare la cintura di sicurezza." :-(
Aaron Bertrand

9
Alcuni problemi logici non possono essere risolti tecnicamente o senza il coinvolgimento delle risorse umane. Se la tua azienda vuole consentire pratiche di sviluppo che si basano sul credere nel voodoo e sull'ignorare la documentazione e il tuo caso d'uso è davvero limitato a una piccola tabella che non viene mai aggiornata, lascia che facciano strada e aggiorni il tuo curriculum. Non vale la pena discutere.
Aaron Bertrand

1
Non hai basi per rivendicare "sempre". Puoi solo rivendicare "ha sempre", "quando ho controllato". La lingua ha una definizione: questo è il contratto con l'utente.
Philipxy,

10
Sono curioso di sapere perché questi tuoi colleghi sono contrari ad aggiungere la order byclausola alle loro domande? Stanno cercando di salvare sulla memoria del codice sorgente? usura della tastiera? tempo impiegato per digitare la clausola temuta?
Mustaccio

2
Ho sempre pensato che i motori di database dovrebbero consentire casualmente le prime righe di query per le quali la semantica non garantisce un ordine, per facilitare i test.
Doug McClean,

Risposte:


30

Vedo tre modi per provare a convincerli:

  1. Lascia che provino la stessa query ma con una tabella più grande (più numero di righe) o quando la tabella viene aggiornata tra le esecuzioni. Oppure vengono inserite nuove righe e alcune vecchie vengono eliminate. Oppure un indice viene aggiunto o rimosso tra le esecuzioni. Oppure il tavolo viene aspirato (in Postgres). O gli indici vengono ricostruiti (in SQL Server). Oppure la tabella viene modificata da un cluster a un heap. Oppure il servizio di database viene riavviato.

  2. Puoi suggerire che dimostrano che esecuzioni diverse restituiranno lo stesso ordine. Possono provarlo? Possono fornire una serie di test che dimostrano che qualsiasi query darà il risultato nello stesso ordine, indipendentemente da quante volte viene eseguita?

  3. Fornire la documentazione di vari DBMS in quella materia. Per esempio:

PostgreSQL :

Ordinamento delle righe

Dopo che una query ha prodotto una tabella di output (dopo che l'elenco di selezione è stato elaborato) può facoltativamente essere ordinato. Se non viene scelto l'ordinamento, le righe verranno restituite in un ordine non specificato. L'ordine effettivo in quel caso dipenderà dalla scansione e dai tipi di piano di join e dall'ordine su disco, ma non deve essere invocato. Un particolare ordinamento di output può essere garantito solo se la fase di ordinamento viene scelta esplicitamente.

SQL Server :

SELECT- ORDER BYClausola (Transact-SQL)

Ordina i dati restituiti da una query in SQL Server. Utilizzare questa clausola per:

Ordinare il set di risultati di una query dall'elenco di colonne specificato e, facoltativamente, limitare le righe restituite a un intervallo specificato. L'ordine in cui le righe vengono restituite in un set di risultati non è garantito se ORDER BYnon viene specificata una clausola.

Oracle :

order_by_clause

Utilizzare la ORDER BYclausola per ordinare le righe restituite dall'istruzione. Senza un order_by_clause, non esiste alcuna garanzia che la stessa query eseguita più di una volta recupererà le righe nello stesso ordine.


Con tabelle molto piccole che non vengono modificate, è possibile che venga visualizzato questo comportamento. Questo è previsto. Ma non è nemmeno garantito. L'ordine può cambiare perché hai aggiunto un indice o hai modificato un indice o hai riavviato il database e probabilmente molti altri casi.
ypercubeᵀᴹ

6
Se l'ordine conta, allora chi è mai responsabile della revisione del proprio codice dovrebbe rifiutare fino a quando non usano ORDER BY. Gli sviluppatori dei DBMS (Oracle, SQL Server, Postgres) dicono tutti la stessa cosa su ciò che il loro prodotto garantisce e cosa no (e sono pagati molto più di me, quindi sanno cosa stanno dicendo, oltre a aver costruito questi dannati cose).
ypercubeᵀᴹ

1
Anche se l'ordine sembra lo stesso ora, è certo che queste tabelle non verranno mai aggiornate per tutta la durata del software che stai costruendo? Che mai più righe verranno inserite, mai?
ypercubeᵀᴹ

1
C'è una garanzia che questo tavolo sarà sempre così piccolo? C'è una garanzia che non verranno aggiunte altre colonne? Vedo decine di casi diversi in cui la tabella potrebbe essere modificata in futuro (e alcune di queste modifiche potrebbero influire sull'ordine del risultato di una query). Ti suggerisco di chiedere loro di rispondere a tutti questi. Possono garantire che non accadrà mai niente del genere? E perché non aggiungeranno un semplice ORDER BY, che garantirà l'ordine, indipendentemente da come cambierà la tabella ? Perché non avere un sicuro aggiunto, che non fa male?
ypercubeᵀᴹ

10
La documentazione dovrebbe essere sufficiente. Qualcos'altro è una seconda ipotesi, e in ogni caso, non sarà mai visto come definitivo, qualunque cosa tu dimostri. Sarà sempre qualcosa che hai fatto e spiegabile, probabilmente a tue spese, piuttosto che qualcosa che lo sia . Armato della documentazione, invia la tua "garanzia" per iscritto e cerca semplicemente l'autorizzazione scritta per non restituire le righe nell'ordine richiesto (non lo otterrai).

19

Questa è di nuovo la storia del cigno nero. Se non ne hai ancora visto uno, ciò non significa che non esistano. Spero che nel tuo caso non porti a un'altra crisi finanziaria mondiale, semplicemente a pochi clienti scontenti.

La documentazione di Postgres lo dice esplicitamente:

Se ORDER BY non viene specificato, le righe vengono restituite nell'ordine che il sistema trova più rapidamente da produrre.

"Il sistema" in questo caso comprende il demone postgres stesso (inclusa l'implementazione dei suoi metodi di accesso ai dati e l'ottimizzatore di query), il sistema operativo sottostante, il layout logico e fisico della memoria del database, possibilmente anche cache della CPU. Dato che l'utente del database non ha alcun controllo su quello stack, non dovresti fare affidamento sul fatto che continuerà a comportarsi per sempre nel modo in cui si comporta in questo preciso istante.

I tuoi colleghi stanno commettendo il fallace errore di generalizzazione . Per confutare il loro punto è sufficiente dimostrare che la loro assunzione è sbagliata una sola volta, ad esempio da questo dbfiddle .


12

Considera l'esempio seguente, in cui abbiamo tre tabelle correlate. Ordini, utenti e dettagli dell'ordine. OrderDetails è collegato con chiavi esterne alla tabella Ordini e alla tabella Utenti. Questa è essenzialmente una configurazione molto tipica per i database relazionali; probabilmente l'intero scopo di un DBMS relazionale .

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);

Qui, stiamo interrogando la tabella OrderDetails in cui UserID è 15:

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

L'output della query è simile a:

╔════════════════╦═════════╦════════╗
║ OrderDetailsID ║ OrderID ║ UserID ║
╠════════════════╬═════════╬════════╣
║ 2200115 ║ 2 ║ 15 ║
║ 630215 ║ 3 ║ 15 ║
║ 1990215 ║ 3 ║ 15 ║
║ 4960215 ║ 3 ║ 15 ║
║ 100715 ║ 8 ║ 15 ║
║ 3930815 ║ 9 ║ 15 ║
║ 6310815 ║ 9 ║ 15 ║
41 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 ║
7 4471915 ║ 20 ║ 15 ║
╚════════════════╩═════════╩════════╝

Come puoi vedere, l'ordine delle righe non corrisponde all'ordine delle righe nella tabella OrderDetails.

L'aggiunta di un esplicito ORDER BYgarantisce che le righe vengano restituite al client nell'ordine desiderato:

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 ║
╚════════════════╩═════════╩════════╝

Se l'ordine delle righe è indispensabile e i tuoi ingegneri sanno che l'ordine è indispensabile, dovrebbero sempre e comunque voler usare una ORDER BYdichiarazione, poiché potrebbe costare loro la designazione in caso di errore correlato a un ordine errato.

Un secondo, forse esempio più istruttivo, utilizzando il OrderDetailstavolo dall'alto, dove stiamo non entrare in altri tavoli, ma hanno un requisito semplice per trovare le righe corrispondenti sia l'IDOrdine e l'UserID, vediamo il problema.

Creeremo un indice per supportare la query, come faresti probabilmente nella vita reale se le prestazioni sono in qualche modo importanti (quando non lo è?).

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

Ecco la domanda:

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

E i risultati:

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

L'aggiunta di una ORDER BYclausola garantirà sicuramente anche il corretto ordinamento anche qui.

Questi modelli sono solo dei semplici esempi in cui non è garantito che le righe siano "in ordine" senza un'istruzione esplicita ORDER BY. Esistono molti altri esempi come questo e poiché il codice del motore DBMS cambia abbastanza frequentemente, il comportamento specifico può cambiare nel tempo.


10

Come esempio pratico, in Postgres, l'ordine al momento cambia quando aggiorni una riga:

% 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)

Non credo che le regole di questo ordinamento implicito esistente siano documentate ovunque, siano sicuramente soggette a modifiche senza preavviso e non siano sicuramente comportamenti portatili tra i motori DB.


Si è documentato: la risposta di ypercube cita la documentazione che ci dice che l'ordine non è specificato.
Lightness Races con Monica il

@LightnessRacesinOrbit Lo prenderei come la documentazione che ci dice esplicitamente che non è documentata. Voglio dire, è anche vero che tutto ciò che non è nella documentazione non è specificato. È una specie di tautologia. Ad ogni modo, ho modificato quella parte della risposta per essere più specifici.
JoL

3

non esattamente una demo, ma troppo a lungo per un commento.

Su tabelle di grandi dimensioni alcuni database eseguiranno scansioni parallele interfogliate:

Se due query vogliono scansionare la stessa tabella e arrivano quasi nello stesso momento, la prima potrebbe essere a metà della tabella quando inizia la seconda.

La seconda query potrebbe ricevere i record a partire dalla metà della tabella (al completamento della prima query) e quindi ricevere i record dall'inizio della tabella.


2

Creare un indice cluster con l'ordine "sbagliato". Ad esempio, cluster on ID DESC. Ciò genererà spesso l'ordine inverso (anche se neanche questo è garantito).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.