¿Cómo demostrar la falta de orden implícito en una base de datos?

21

Recientemente les estaba explicando a mis colegas la importancia de tener una columna para clasificar los datos en una tabla de base de datos si es necesario hacerlo, por ejemplo, para datos ordenados cronológicamente. Esto resultó algo difícil porque simplemente podían volver a ejecutar su consulta aparentemente sin fin y siempre devolvería el mismo conjunto de filas en el mismo orden.

He notado esto antes y todo lo que realmente puedo hacer es insistir en que confíen en mí y no simplemente asumir que una tabla de base de datos se comportará como un archivo CSV o Excel tradicional.

Por ejemplo, ejecutar la consulta (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');

creará una tabla con un orden conceptual claro. Seleccionar los mismos datos de la manera más simple sería:

SELECT * FROM mytable;

Siempre me da los siguientes resultados:

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

Puedo hacer esto una y otra vez y siempre me devolverá los mismos datos en el mismo orden. Sin embargo, sé que este orden implícito puede romperse, lo he visto antes, particularmente en grandes conjuntos de datos, donde algún valor aleatorio aparentemente se arrojará al lugar "incorrecto" cuando se seleccione. Pero se me ha ocurrido que no sé cómo sucede esto o cómo reproducirlo. Me resulta difícil obtener resultados en Google porque la consulta de búsqueda tiende a devolver ayuda general para ordenar los conjuntos de resultados.

Entonces, mis preguntas son esencialmente estas:

  1. ¿Cómo puedo demostrar de manera demostrable y concreta que el orden de retorno de las filas de una consulta sin una ORDER BYdeclaración no es confiable, preferiblemente al causar y mostrar un desglose del orden implícito incluso cuando la tabla en cuestión no se actualiza o edita ?

  2. ¿Hay alguna diferencia si los datos solo se insertan una vez en masa y luego nunca se actualizan nuevamente?

Preferiría una respuesta basada en postgres ya que esa es la que estoy más familiarizado, pero estoy más interesado en la teoría misma.


fuente
66
"Nunca se ha escrito o actualizado de nuevo": ¿por qué es una tabla? Suena como un archivo. O una enumeración. O algo que no necesita estar en una base de datos. Si es cronológico, ¿no hay una columna de fecha para ordenar? Si la cronología importa, pensaría que esa información sería lo suficientemente importante como para tenerla en la tabla. De todos modos, los planes pueden cambiar debido a que alguien cae o crea un nuevo índice, o eventos como cambios en la memoria, marcas de seguimiento u otras influencias. Su argumento suena como "Nunca uso el cinturón de seguridad y nunca he pasado por el parabrisas, por lo que continuaré sin usar el cinturón de seguridad". :-(
Aaron Bertrand
99
Algunos problemas de lógica simplemente no se pueden resolver técnicamente o sin la participación de RR. HH. Si su empresa quiere permitir prácticas de desarrollo que se basan en creer en el vudú e ignorar la documentación, y su caso de uso realmente se limita a una pequeña tabla que nunca se actualiza, simplemente déjelos seguir su camino y actualice su currículum. No vale la pena discutirlo.
Aaron Bertrand
1
No tiene base para reclamar "siempre lo hará". Solo puede reclamar "siempre ha", "cuando marqué". El lenguaje tiene una definición: ese es el contrato con el usuario.
philipxy
10
Tengo curiosidad por qué estos colegas tuyos están en contra de agregar la order bycláusula a sus consultas. ¿Están tratando de ahorrar en el almacenamiento del código fuente? desgaste del teclado? tiempo que lleva escribir la temida cláusula?
mustaccio
2
Siempre he pensado que los motores de bases de datos deben permutar aleatoriamente las primeras filas de consultas para las que la semántica no garantiza un pedido, para ayudar a facilitar las pruebas.
Doug McClean

Respuestas:

30

Veo tres formas de tratar de convencerlos:

  1. Permítales intentar la misma consulta pero con una tabla más grande (más número de filas) o cuando la tabla se actualiza entre ejecuciones. O se insertan nuevas filas y se eliminan algunas antiguas. O se agrega o elimina un índice entre ejecuciones. O la mesa se aspira (en Postgres). O se reconstruyen los índices (en SQL Server). O la tabla se cambia de agrupada a un montón. O se reinicia el servicio de la base de datos.

  2. Puede sugerir que demuestren que diferentes ejecuciones devolverán el mismo orden. ¿Pueden probarlo? ¿Pueden proporcionar una serie de pruebas que demuestren que cualquier consulta dará el resultado en el mismo orden, sin importar cuántas veces se ejecute?

  3. Proporcione la documentación de varios DBMS en ese asunto. Por ejemplo:

PostgreSQL :

Ordenar filas

Después de que una consulta haya producido una tabla de salida (después de que se haya procesado la lista de selección), se puede ordenar opcionalmente. Si no se elige la ordenación, las filas se devolverán en un orden no especificado. El orden real en ese caso dependerá de los tipos de plan de escaneo y unión y el orden en el disco, pero no se debe confiar en él. Un pedido de salida particular solo puede garantizarse si el paso de clasificación se elige explícitamente.

Servidor SQL :

SELECT- ORDER BYCláusula (Transact-SQL)

Ordena los datos devueltos por una consulta en SQL Server. Use esta cláusula para:

Ordene el conjunto de resultados de una consulta por la lista de columnas especificada y, opcionalmente, limite las filas devueltas a un rango especificado. El orden en el que se devuelven las filas en un conjunto de resultados no está garantizado a menos ORDER BYque se especifique una cláusula.

Oráculo :

order_by_clause

Use la ORDER BYcláusula para ordenar las filas devueltas por la declaración. Sin un order_by_clause, no existe garantía de que la misma consulta ejecutada más de una vez recupere filas en el mismo orden.

ypercubeᵀᴹ
fuente
Con tablas muy pequeñas que no se modifican, puede ver este comportamiento. Eso se espera. Pero tampoco está garantizado. El orden puede cambiar porque agregó un índice o modificó un índice o reinició la base de datos y posiblemente muchos otros casos.
ypercubeᵀᴹ
66
Si el pedido es importante, entonces quien sea responsable de revisar su código debe rechazarlo hasta que use ORDER BY. Los desarrolladores de los DBMS (Oracle, SQL Server, Postgres) dicen lo mismo sobre lo que garantiza su producto y lo que no (y se les paga mucho más de lo que yo pagaré, por lo que saben lo que dicen, además de haber construido estos malditos cosas).
ypercubeᵀᴹ
1
Incluso si el orden se ve igual ahora, ¿es seguro que estas tablas nunca se actualizarán durante toda la vida útil del software que está creando? ¿Que nunca más se insertarán filas?
ypercubeᵀᴹ
1
¿Hay alguna garantía de que esta tabla siempre será tan pequeña? ¿Existe una garantía de que no se agregarán más columnas? Puedo ver decenas de casos diferentes en los que la tabla puede modificarse en el futuro (y algunos de estos cambios pueden afectar el orden del resultado de una consulta). Te sugiero que les pidas que respondan a todo esto. ¿Pueden garantizar que nunca pasará algo así? ¿Y por qué no agregarán un simple ORDER BY, que garantizará el orden, sin importar cómo vaya a cambiar la tabla ? ¿Por qué no tener una caja fuerte agregada, que no hace daño?
ypercubeᵀᴹ
10
La documentación debería ser suficiente. Cualquier otra cosa es adivinar, y en cualquier caso, nunca se verá como definitivo, no importa lo que demuestres. Siempre será algo que hiciste y explicable, probablemente a tu costa, en lugar de algo que sea . Armado con la documentación, presente su "garantía" por escrito y simplemente solicite un permiso por escrito para no devolver filas en el orden requerido (no lo obtendrá).
19

Esta es la historia del cisne negro de nuevo. Si aún no has visto uno, no significa que no existan. Esperemos que en su caso no conduzca a otra crisis financiera mundial, simplemente a unos pocos clientes descontentos.

La documentación de Postgres dice esto explícitamente:

Si ORDER BY no se proporciona, las filas se devuelven en el orden que el sistema encuentre más rápido para producir.

"El sistema" en este caso comprende el propio demonio postgres (incluida la implementación de sus métodos de acceso a datos y el optimizador de consultas), el sistema operativo subyacente, el diseño lógico y físico del almacenamiento de la base de datos, posiblemente incluso cachés de CPU. Como usted, como usuario de la base de datos, no tiene control sobre esa pila, no debe confiar en que continuará comportándose para siempre como se comporta en este mismo momento.

Sus colegas están cometiendo la falacia apresurada de generalización . Para refutar su punto, es suficiente demostrar que su suposición es incorrecta solo una vez, por ejemplo, por este dbfiddle .

mustaccio
fuente
12

Considere el siguiente ejemplo, donde tenemos tres tablas relacionadas. Pedidos, usuarios y detalles del pedido. OrderDetails está vinculado con claves externas a la tabla de pedidos y la tabla de usuarios. Esta es esencialmente una configuración muy típica para bases de datos relacionales; posiblemente el propósito completo de un DBMS relacional .

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

Aquí, estamos consultando la tabla OrderDetails donde el UserID es 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

El resultado de la consulta se ve así:

╔════════════════╦═════════╦════════╗
║ OrderDetailsID ║ OrderID ║ UserID ║
╠════════════════╬═════════╬════════╣
║ 2200115 ║ 2 ║ 15 ║
║ 630215 ║ 3 ║ 15 ║
║ 1990215 ║ 3 ║ 15 ║
60 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 ║
18 221815 ║ 19 ║ 15 ║
║ 3381915 ║ 20 ║ 15 ║
║ 4471915 ║ 20 ║ 15 ║
╚════════════════╩═════════╩════════╝

Como puede ver, el orden de salida de las filas no coincide con el orden de las filas en la tabla OrderDetails.

Agregar un explícito ORDER BYgarantiza que las filas se devolverán al cliente en el orden deseado:

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 ║
18 221815 ║ 19 ║ 15 ║
║ 299915 ║ 100 ║ 15 ║
82 368215 ║ 83 ║ 15 ║
38 603815 ║ 39 ║ 15 ║
║ 630215 ║ 3 ║ 15 ║
║ 728515 ║ 86 ║ 15 ║
║ 972215 ║ 23 ║ 15 ║
20 992015 ║ 21 ║ 15 ║
17 1017115 ║ 72 ║ 15 ║
13 1113815 ║ 39 ║ 15 ║
╚════════════════╩═════════╩════════╝

Si el orden de las filas es imperativo, y sus ingenieros saben que el orden es imperativo, solo deberían querer usar una ORDER BYdeclaración, ya que podría costarles su designación si hubiera una falla relacionada con un orden incorrecto.

Un segundo, quizá más instructivo ejemplo, utilizando la OrderDetailstabla de arriba, donde estamos no unirse a ninguna otra tabla, pero tiene un requisito sencillo encontrar las filas coincidentes tanto en el IdPedido y el ID de usuario, vemos el problema.

Crearemos un índice para admitir la consulta, como probablemente haría en la vida real si el rendimiento es de alguna manera importante (¿cuándo no lo es?).

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

Aquí está la consulta:

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

Y los resultados:

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

Agregar una ORDER BYcláusula definitivamente asegurará que obtengamos el tipo correcto aquí también.

Estas maquetas son solo ejemplos simples donde no se garantiza que las filas estén "en orden" sin una ORDER BYdeclaración explícita . Hay muchos más ejemplos como este, y dado que el código del motor DBMS cambia con bastante frecuencia, el comportamiento específico puede cambiar con el tiempo.

Max Vernon
fuente
10

Como ejemplo práctico, en Postgres, el orden cambia actualmente cuando actualiza una fila:

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

No creo que las reglas de este ordenamiento implícito existente estén documentadas en ningún lado, definitivamente están sujetas a cambios sin previo aviso y definitivamente no es un comportamiento portátil en los motores de base de datos.

JoL
fuente
Se está documentada: La respuesta de ypercube cita a la documentación que nos dice que la orden no se especifica.
Lightness compite con Monica el
@LightnessRacesinOrbit tomaría eso como la documentación que nos dice explícitamente que no está documentado. Quiero decir, también es cierto que cualquier cosa que no esté en la documentación no está especificada. Es una especie de tautología. De todos modos, edité esa parte de la respuesta para que sea más específica.
JoL
3

No es exactamente una demostración, pero es demasiado larga para un comentario.

En tablas grandes, algunas bases de datos harán escaneos paralelos intercalados:

Si dos consultas desean escanear la misma tabla y llegar casi al mismo tiempo, la primera podría estar en la mitad de la tabla cuando comience la segunda.

La segunda consulta podría recibir registros comenzando desde el medio de la tabla (a medida que se completa la primera consulta) y luego recibir los registros desde el comienzo de la tabla.

Jasen
fuente
2

Cree un índice agrupado que tenga el orden "incorrecto". Por ejemplo, clúster activado ID DESC. Esto a menudo generará el orden inverso (aunque esto tampoco está garantizado).

usr
fuente