データベースに暗黙的な順序がないことを証明する方法は?


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. データがまとめて一度だけ挿入され、その後再び更新されない場合、違いはありますか?

それは私が最もよく知っているものですが、理論自体にもっと興味があるので、私はpostgresベースの答えを好むでしょう。


6
「二度と書かれたり更新されたりすることはありません」-なぜこれは表なのですか?ファイルのように聞こえます。または列挙型。または、データベースにある必要がないもの。年代順の場合、注文する日付列はありませんか?年表が重要な場合、情報は表に含めるのに十分重要だと思います。とにかく、誰かが新しいインデックスを削除または作成したり、メモリの変更、トレースフラグ、またはその他の影響などのイベントが原因で、計画が変更される可能性があります。彼らの議論は、「私はシートベルトを着用せず、フロントガラスを一度も通ったことがないので、シートベルトを着用し続けません。」:
アーロン・バートランド

9
一部のロジックの問題は、技術的に、またはHRの関与なしでは解決できない場合があります。あなたの会社が、ブードゥー教を信じてドキュメントを無視することに依存する開発者プラクティスを許可したい場合、そしてユースケースが本当に更新されることのない小さなテーブルに本当に制限されているなら、彼らに道を譲って履歴書を更新してください。議論する価値はありません。
アーロンバートランド

1
あなたは「常に」と主張する根拠がありません。「常に持っている」、「チェックしたとき」のみを主張できます。言語には定義があります。つまり、ユーザーとの契約です。
philipxy

10
私は好奇心が強い、なぜあなたのこれらの同僚が追加に反対しているorder by彼らのクエリに句を?彼らはソースコードストレージに保存しようとしていますか?キーボードの損耗?恐ろしい句を入力するのにかかる時間は?
ムスタッチョ

2
データベースエンジンは、テストを容易にするために、セマンティクスが順序を保証しないクエリの最初の数行をランダムに置換する必要があると常に考えてきました。
ダグマックリーン

回答:


30

私はそれらを説得しようとする3つの方法を見ます:

  1. 同じクエリを試してみましょう。ただし、より大きなテーブル(より多くの行数)を使用するか、実行間でテーブルを更新します。または、新しい行が挿入され、古い行がいくつか削除されます。または、実行の間にインデックスが追加または削除されます。または、テーブルは(Postgresで)バキュームされます。または、インデックスが再構築されます(SQL Serverで)。または、テーブルがクラスター化からヒープに変更されます。または、データベースサービスが再起動されます。

  2. 異なる実行が同じ順序を返すことを証明することを提案できます。彼らはそれを証明できますか?クエリが何回実行されても、クエリが同じ順序で結果を与えることを証明する一連のテストを提供できますか?

  3. その点でさまざまなDBMSのドキュメントを提供します。例えば:

PostgreSQL

行の並べ替え

クエリが出力テーブルを生成した後(選択リストが処理された後)、オプションでソートできます。ソートが選択されていない場合、行は指定されていない順序で返されます。その場合の実際の順序は、スキャンおよび結合プランの種類とディスク上の順序によって異なりますが、依存ないでください。特定の出力順序は、並べ替え手順が明示的に選択されている場合にのみ保証されます。

SQL Server

SELECT- ORDER BY句(Transact-SQL)

SQL Serverのクエリによって返されたデータを並べ替えます。この句を使用して:

クエリの結果セットを指定された列リストで並べ、オプションで、返された行を指定された範囲に制限します。結果セットで行が返される順序は、ORDER BY句が指定されない限り保証されません。

オラクル

order_by_clause

ORDER BY句を使用して、文によって返される行を順序付けます。order_by_clauseがないと、同じクエリが複数回実行されて同じ順序で行が取得されるという保証はありません。


変更されていない非常に小さなテーブルでは、この動作見られる場合があります。それは期待されています。しかし、それも保証されていません。インデックスを追加したか、インデックスを変更したか、データベースを再起動したため、場合によっては他の多くの場合、順序が変わる可能性があります。
ypercubeᵀᴹ

6
順序が重要な場合、コードのレビューを担当する人は、ORDER BYを使用するまで拒否する必要があります。DBMS(Oracle、SQL Server、Postgres)の開発者は、彼らの製品が保証するものとそうでないものについて同じことを言っています(そして、彼らは私よりもはるかに多く支払われているので、彼らは彼らが言ったことを知っていますもの)。
ypercubeᵀᴹ

1
順序が同じように見えても、これらのテーブルは、構築中のソフトウェアの全ライフタイム中に更新されないことは確かですか?これ以上行が挿入されることはありませんか?
ypercubeᵀᴹ

1
このテーブルが常にこれほど小さいという保証はありますか?列が追加されないという保証はありますか?将来、テーブルが変更される可能性のあるさまざまなケースを確認できます(これらの変更の一部はクエリ結果の順序に影響する可能性があります)。これらすべてに答えるように依頼することをお勧めします。彼らはそのようなことは決して起こらないことを保証できますか?そして、テーブルがどのように変化してもORDER BY、順序を保証するsimpleを追加しないのはなぜですか?なぜ安全なものを追加しないのですか?
ypercubeᵀᴹ

10
ドキュメントで十分なはずです。それ以外のものは二次推測であり、とにかく、あなたが何を証明しても、決定的なものとは決して見なされません。それは、常になりますあなたがやった何かと説明できる、おそらくあなたの費用ではなく、何か。ドキュメントを準備し、書面で「保証」を提出し、必要な順序で行を返さないように書面による許可を求めるだけです(取得できません)。

19

これは再びブラックスワンの物語です。まだ見ていなくても、存在しないわけではありません。願わくば、それが別の世界的な金融危機につながるのではなく、単に少数の不幸な顧客につながることを願っています。

Postgresのドキュメントはこれを明示的に述べています

ORDER BYが指定されていない場合、システムが生成するのに最も速い順序で行が返されます。

この場合の「システム」は、postgresデーモン自体(データアクセスメソッドとクエリオプティマイザーの実装を含む)、基盤となるオペレーティングシステム、データベースストレージの論理的および物理的レイアウト、場合によってはCPUキャッシュで構成されます。データベースユーザーとしてのあなたはそのスタックを制御できないので、この分だけ動作するように永続的に動作し続けることに頼るべきではありません。

あなたの同僚は性急な一般化の誤りを犯しています。彼らの主張を反証するには、このdbfiddleによって、彼らの仮定が一度だけ間違っていることを示すだけで十分です


12

関連する3つのテーブルがある次の例を考えてみましょう。Orders、Users、およびOrde​​rDetails。OrderDetailsは、外部キーを使用してOrdersテーブルとUsersテーブルにリンクされます。これは、本質的にリレーショナルデータベースの非常に典型的なセットアップです。おそらく、リレーショナル 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);

ここでは、UserIDが15であるOrderDetailsテーブルを照会しています。

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║
630 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║
368 368215║83║15║
603 603815║39║15║
630 630215║3║15║
728 728515║86║15║
972 972215║23║15║
║992015║21║15║
║1017115║72║15║
║1113815║39║15║
╚================╩========╩========╝

行の順序が不可欠である、とあなたのエンジニアはその順序が不可欠であることがわかっている場合、彼らは今まで必要がありますしたい使用することをORDER BY間違ったために関連する障害が発生した場合、それはそれらに彼らの指定を要する可能性があるため、文を。

OrderDetails上記のテーブルを使用した、おそらくより有益な2番目の例では、他のテーブル結合しませが、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ステートメントがない場合、行が「順序どおり」になることは保証されません。このような例は他にもたくさんあり、DBMSエンジンのコードは頻繁に変更されるため、特定の動作は時間とともに変化する可能性があります。


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)

この既存の暗黙的な順序付けのルールはどこにも文書化されておらず、間違いなく予告なく変更される可能性があり、DBエンジン間での移植性のある動作ではないと思います。


文書化されています:ypercubeの答えは、順序が指定されていないことを告げる文書を引用しています。
モニカとの軽度レース

@LightnessRacesinOrbit私はそれが文書化されていないことを明示的に伝える文書としてそれを取ると思います。つまり、ドキュメントに記載されていないものが指定されていないことも事実です。それは一種のトートロジーです。とにかく、回答のその部分をより具体的に編集しました。
JoL

3

デモではありませんが、コメントするには長すぎます。

大きなテーブルでは、一部のデータベースはインターリーブされた並列スキャンを実行します。

2つのクエリが同じテーブルをスキャンし、ほぼ同時に到着する場合、最初のクエリは2番目のクエリが開始されたときにテーブルを途中で通過する可能性があります。

2番目のクエリは、テーブルの中央からレコードを受信し(最初のクエリが完了したとき)、テーブルの先頭からレコードを受信します。


2

「間違った」順序のクラスター化インデックスを作成します。たとえば、上のクラスタID DESC。これはしばしば逆順を出力します(ただし、これも保証されません)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.