Як довести відсутність неявного порядку в базі даних?


21

Нещодавно я пояснював колегам важливість створення стовпчика, за яким слід сортувати дані в таблиці бази даних, якщо це потрібно зробити, наприклад, за хронологічно упорядкованими даними. Це виявилося дещо складним, оскільки вони могли просто повторно виконувати запит, здавалося б, нескінченно, і він завжди повертав би той самий набір рядків у тому ж порядку.

Я це помічав раніше, і все, що я міг реально зробити, - це наполягати на тому, що вони мені довіряють, а не просто припускають, що таблиця бази даних буде вести себе як традиційний файл CSV або Excel.

Наприклад, виконання запиту (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');

створить таблицю з чітким концептуальним порядком. Вибрати ті самі дані найпростішим способом було б:

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)

Я можу це робити знову і знову, і мені завжди будуть повертати ті самі дані в тому ж порядку. Однак я знаю, що цей неявний порядок може бути порушений, я його бачив і раніше, особливо у великих наборах даних, де якесь випадкове значення буде, мабуть, викинуте у "неправильне" місце при виборі. Але мені спало на думку, що я не знаю, як це відбувається або як це відтворити. Мені важко отримати результати в Google, тому що пошуковий запит, як правило, повертає загальну допомогу щодо сортування наборів результатів.

Отже, мої запитання по суті такі:

  1. Як я можу наочно і конкретно довести, що порядок повернення рядків із запиту без ORDER BYвисловлювання не є надійним, бажано, викликаючи та показуючи розбиття неявного порядку, навіть коли таблиця, про яку йде мова, не оновлюється чи редагується ?

  2. Чи має взагалі якась різниця, якщо дані вставляються лише один раз масово і потім більше ніколи не оновлюються?

Я вважаю за краще відповідь на основі постгресів, оскільки саме з цим я найбільше знайомий, але мене більше цікавить сама теорія.


6
"Ніколи більше не записується та не оновлюється" - чому це таблиця? Звучить як файл. Або перерахунок. Або щось, що не потрібно знаходитись у базі даних. Якщо це хронологічно, чи не можна замовити стовпчик дат? Якщо хронологія має значення, ви вважаєте, що ця інформація буде досить важливою, щоб мати її в таблиці. У будь-якому випадку плани можуть змінитися через те, що хтось скидає або створює новий індекс, або такі події, як зміни пам’яті, прапорці слідів чи інші впливи. Їх аргумент звучить так: "Я ніколи не надягаю на ремінь безпеки, і ніколи не проходив крізь лобове скло, тому я продовжую не носити ременя безпеки" :-(
Аарон Бертран

9
Деякі логічні проблеми просто неможливо вирішити технічно або без участі HR. Якщо ваша компанія хоче дозволити розробникам практик, які покладаються на віру в вуду та ігнорування документації, а ваш випадок використання дійсно обмежений крихітною таблицею, яка ніколи не оновлюється, просто дозвольте їм знайти свій шлях та оновити ваше резюме. Сперечатися не варто.
Аарон Бертран

1
У вас немає підстав претендувати на "завжди". Ви можете стверджувати лише "завжди", "коли я перевірив". Мова має визначення - це договір з користувачем.
philipxy

10
Мені цікаво, чому ваші колеги проти того, order byщоб додавати до них запити? Вони намагаються заощадити на сховищі вихідного коду? знос клавіатури? час, який потрібен для введення жахливого пункту?
мустаччо

2
Я завжди думав, що двигуни бази даних повинні випадковим чином перетворювати перші рядки запитів, для яких семантика не гарантує впорядкування, щоб полегшити тестування.
Дуг МакКлін

Відповіді:


30

Я бачу три способи спробувати їх переконати:

  1. Нехай вони спробують той самий запит, але з більшою таблицею (більше число рядків) або коли таблиця оновлюється між виконаннями. Або вставляються нові рядки, а деякі старі видаляються. Або індекс додається або видаляється між стратами. Або таблицю вакуумують (у Postgres). Або індекси перебудовуються (у SQL Server). Або таблицю змінюють з кластеризованої на купу. Або послуга бази даних перезапущена.

  2. Ви можете запропонувати їм довести, що різні страти повернуть один і той же порядок. Чи можуть вони це довести? Чи можуть вони надати ряд тестів, які підтверджують, що будь-який запит дасть результат у тому ж порядку, незалежно від того, скільки разів він виконується?

  3. Надайте документацію різних СУБД з цього питання. Наприклад:

PostgreSQL :

Сортування рядків

Після того, як запит створив таблицю виводу (після обробки списку вибору), його можна необов'язково сортувати. Якщо сортування не вибрано, рядки будуть повернуті в не визначеному порядку. Фактичний порядок у такому випадку залежатиме від типів сканування та приєднання плану та порядку на диску, але на нього не слід покладатися. Конкретне впорядкування виводу можна гарантувати лише у тому випадку, якщо чітко обраний крок сортування.

SQL Server :

SELECT- ORDER BYпункт (Transact-SQL)

Сортування даних, повернених запитом у SQL Server. Використовуйте цей пункт для:

Упорядкуйте набір результатів запиту за вказаним списком стовпців і, за бажанням, обмежте рядки, що повертаються, до визначеного діапазону. Порядок повернення рядків у наборі результатів не гарантується, якщо не вказано ORDER BYпункт.

Oracle :

order_by_clause

Використовуйте ORDER BYпункт, щоб упорядкувати рядки, повернені випискою. Без order_by_clause не існує гарантії, що той самий запит, який виконується більше одного разу, отримає рядки в тому ж порядку.


З дуже маленькими таблицями, які не модифікуються, ви можете бачити таку поведінку. Це очікується. Але це також не гарантується. Порядок може змінитися, тому що ви додали індекс або змінили індекс або перезапустили базу даних і, можливо, багато інших випадків.
ypercubeᵀᴹ

6
Якщо замовлення має значення, то хто коли-небудь несе відповідальність за перегляд свого коду, слід відхилити, поки вони не використають ЗАМОВЛЕННЯ. Розробники СУБД (Oracle, SQL Server, Postgres) говорять одне і те ж про те, що їх продукт гарантує, а що ні (і вони платять набагато більше, ніж я буду, тому вони знають, що говорять, окрім того, що побудували ці чорти речі).
ypercubeᵀᴹ

1
Навіть якщо замовлення зараз виглядає таким же, чи впевнене, що ці таблиці ніколи не оновлюються протягом усього життя програмного забезпечення, яке ви будуєте? Що більше ніколи не буде вставлено рядки?
ypercubeᵀᴹ

1
Чи є гарантія, що ця таблиця завжди буде такою маленькою? Чи є гарантія, що більше стовпців не буде додано? Я бачу десятки різних випадків, коли таблиця може бути змінена в майбутньому (і деякі з цих змін можуть вплинути на порядок результату запиту). Я пропоную вам попросити їх відповісти на все це. Чи можуть вони гарантувати, що нічого подібного ніколи не відбудеться? І чому вони не додадуть простий ORDER BY, що гарантуватиме замовлення, незалежно від того, як зміниться таблиця ? Чому б не додати сейф, який не шкодить?
ypercubeᵀᴹ

10
Документації має бути достатньо. Все, що є другим здогадком, і, в будь-якому випадку, ніколи не буде розглядатися як остаточне, незалежно від того, що ви докажете. Це завжди буде щось, що ви зробили і пояснили, мабуть, за ваш рахунок, а не те, що є . Озброївшись документацією, подайте свою «гарантію» у письмовій формі та просто вимагайте письмового дозволу не повертати рядки у необхідному порядку (ви її не отримаєте).

19

Це історія чорного лебедя знову. Якщо ви ще цього не бачили, це не означає, що їх не існує. Сподіваємось, у вашому випадку це не призведе до чергової світової фінансової кризи, просто до кількох нещасних клієнтів.

У документації Postgres це прямо написано :

Якщо ORDER BY не задано, рядки повертаються в будь-якому порядку, який система вважає найшвидшим.

"Система" в цьому випадку включає в себе сам демон постгресу (включаючи реалізацію його методів доступу до даних та оптимізатор запитів), базову операційну систему, логічне та фізичне розташування сховища бази даних, можливо, навіть кеші процесора. Оскільки ви як користувач бази даних не маєте контролю над цим стеком, вам не слід покладатися на нього, продовжуючи вести себе так, як він поводиться в цю саму хвилину.

Ваші колеги здійснюють поспішні помилки узагальнення . Щоб спростувати їхню думку, достатньо показати, що їх припущення є помилковим лише один раз, наприклад, цим dbfiddle .


12

Розглянемо наступний приклад, де у нас є три пов’язані таблиці. Замовлення, користувачі та OrderDetails. OrderDetails пов'язаний із зовнішніми ключами до таблиці Orders та Table Users. Це по суті дуже типова установка для реляційних баз даних; Можливо, вся мета реляційної СУБД.

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

Тут ми запитуємо таблицю OrderDetails, де 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

Вихід із запиту виглядає так:

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

Як бачимо, порядок виведення рядків не відповідає порядку рядків у таблиці OrderDetails.

Додавання явного ORDER BYгарантії, що рядки будуть повернені клієнту в потрібному порядку:

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

Якщо порядок рядків є обов'язковим, і ваші інженери знають, що порядок є обов'язковим, вони повинні завжди хотіти використовувати ORDER BYвиписку, оскільки це може коштувати їм їх позначення, якщо стався збій, пов’язаний з неправильним порядком.

Другий, можливо, більш повний приклад, використовуючи OrderDetailsтаблицю зверху, де ми не приєднуємось до жодних інших таблиць, але у нас є проста вимога знайти рядки, що відповідають як OrderID, так і UserID, ми бачимо проблему.

Ми створимо індекс для підтримки запиту, як ви, ймовірно, зробите в реальному житті, якщо продуктивність будь-якого значення важлива (коли це не так?).

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

Ось запит:

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

І результати:

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

Додавання ORDER BYпропозиції, безумовно, гарантуватиме, що ми отримаємо правильний сорт і тут.

Ці макети - це просто прості приклади, коли рядки не гарантовано є "в порядку" без явного ORDER BYтвердження. Існує ще багато подібного прикладу, і оскільки код двигуна СУБД змінюється досить часто, конкретна поведінка може змінюватися з часом.


10

Як практичний приклад, у Postgres порядок наразі змінюється під час оновлення рядка:

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

Я не думаю, що правила цього наявного неявного впорядкування документовані де завгодно, вони, безумовно, можуть змінюватися без попереднього повідомлення і, безумовно, не є портативною поведінкою в системах БД.


Це буде документовано: відповідь ypercube цитує документацію говорять нам про те , що порядок не визначено.
Гонки легкості з Монікою

@LightnessRacesinOrbit Я вважаю, що це документація, яка прямо говорить нам про те, що це не документально. Я маю на увазі, правда також, що нічого, що не в документації, не визначено. Це свого роду тавтологія. У всякому разі, я змінив цю частину відповіді, щоб бути більш конкретною.
JoL

3

не зовсім демо, але занадто довго для коментарів.

У великих таблицях деякі бази даних будуть робити перемежоване паралельне сканування:

Якщо два запити хочуть сканувати одну і ту ж таблицю і надходять майже в один і той же час, перший може пройти шлях через таблицю при запуску другого.

Другий запит може отримувати записи, починаючи з середини таблиці (у міру завершення першого запиту), а потім отримувати записи з початку таблиці.


2

Створіть кластерний індекс, що має "неправильний" порядок. Наприклад, кластер на ID DESC. Це часто виводить зворотний порядок (хоча це також не гарантується).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.