web-dev-qa-db-ja.com

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

最近、同僚に、時系列順に並べられたデータなど、必要に応じてデータベーステーブルのデータを並べ替えるための列があることの重要性を説明していました。これは、クエリを無限に再実行するだけで、同じ行のセットを常に同じ順序で返すため、いくぶん困難であることがわかりました。

私は以前にこれに気づきました。私が本当にできることは、彼らが私を信頼していて、データベーステーブルが従来の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ベースの答えを選びますが、理論自体にもっと興味があります。

21
anon

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

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

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

  3. その際、さまざまなDBMSのドキュメントを提供してください。例えば:

PostgreSQL

行の並べ替え

クエリが出力テーブルを生成した後(選択リストが処理された後)、オプションでソートできます。 ソートが選択されていない場合、行は不特定の順序で返されます。その場合の実際の順序依存するスキャンおよび結合プランのタイプとディスク上の順序、しかし、これに依存することはできません。特定の出力順序は、ソートステップが明示的に選択されている場合にのみ保証されます。

SQLサーバー

SELECT-ORDER BY句(Transact-SQL)

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

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

Oracle

order_by_clause

使用 ORDER BY句は、ステートメントによって返される行を並べ替えます。 order_by_clauseがないと、同じクエリが複数回実行されて同じ順序で行が取得されるという保証はありません。

30
ypercubeᵀᴹ

これはブラックスワンの物語です。まだ見ていなくても、それらが存在しないという意味ではありません。うまくいけば、あなたの場合、それが別の世界的な金融危機につながることはなく、単に少数の不幸な顧客につながるでしょう。

Postgres ドキュメントにはこれが記載されています 明示的に:

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

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

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

19
mustaccio

3つの関連テーブルがある次の例を考えます。 Orders、Users、およびOrde​​rDetails。 OrderDetailsは、OrdersテーブルとUsersテーブルへの外部キーにリンクされています。これは、本質的にリレーショナルデータベースの非常に典型的なセットアップです。間違いなくrelational 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║
║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║
║2 601715║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ステートメントを使用するのはwantのみです。誤った順序に関連しています。

上からOrderDetailsテーブルを使用した2番目の、おそらくより有益な例。ここではnotを他のテーブルに結合していますが、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エンジンのコードはかなり頻繁に変更されるため、特定の動作は時間とともに変化する可能性があります。

12
Max Vernon

実用的な例として、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エンジン間での移植可能な動作ではありません。

10
JoL

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

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

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

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

3
Jasen

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

2
usr