데이터베이스에서 암시 적 순서가 없다는 것을 증명하는 방법은 무엇입니까?


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
“절대로 쓰거나 다시 업데이트하지 마십시오”– 왜 이것이 표입니까? 파일처럼 들립니다. 또는 열거 형. 또는 데이터베이스에있을 필요가없는 것. 연대순 인 경우 주문 날짜 열이 없습니까? 연대기가 중요한 경우 정보가 테이블에 있어야 할만 큼 중요하다고 생각할 것입니다. 어쨌든 누군가 새 인덱스를 삭제하거나 작성하거나 메모리 변경, 추적 플래그 또는 기타 영향과 같은 이벤트로 인해 계획이 변경 될 수 있습니다. 그들의 주장은“나는 안전 벨트를 착용하지 않고 앞 유리창을 뚫어 본 적이 없으므로 계속 안전 벨트를 착용하지 않을 것입니다.”:-(
Aaron Bertrand

9
일부 논리 문제는 기술적으로 또는 HR 개입 없이는 해결할 수 없습니다. 귀사가 부두를 믿고 문서를 무시하는 것에 의존하는 개발자 관행을 허용하고 유스 케이스가 절대 업데이트되지 않은 작은 테이블로 제한되는 경우, 귀사의 길을 찾고 이력서를 업데이트하십시오. 논쟁 할 가치가 없습니다.
Aaron Bertrand

1
"항상 의지 할 것"이라고 주장 할 근거가 없습니다. "항상 체크 한 경우"만 항상 표시 할 수 있습니다. 언어는 정의, 즉 사용자와의 계약입니다.
philipxy

10
이 동료들이 order by 쿼리에 조항을 추가하지 않는지 궁금 합니다. 그들은 소스 코드 스토리지에 저장하려고합니까? 키보드 마모? 무서운 조항을 입력하는 데 걸리는 시간은?
mustaccio

2
필자는 데이터베이스 엔진이 시맨틱이 순서를 보장하지 않는 처음 몇 행의 쿼리를 무작위로 퍼밋하여 테스트를 용이하게한다고 생각했습니다.
더그 맥클린

답변:


30

나는 그들을 설득하려고 세 가지 방법을 봅니다.

  1. 동일한 쿼리를 시도하지만 더 큰 테이블 (더 많은 행 수) 또는 테이블이 실행간에 업데이트 될 때 시도하십시오. 또는 새 행이 삽입되고 일부 이전 행이 삭제됩니다. 또는 실행 사이에 인덱스가 추가되거나 제거됩니다. 또는 테이블이 청소됩니다 (Postgres에서). 또는 인덱스가 다시 작성됩니다 (SQL Server에서). 또는 테이블이 클러스터에서 힙으로 변경됩니다. 또는 데이터베이스 서비스가 다시 시작되었습니다.

  2. 다른 실행이 동일한 순서를 반환한다는 것을 증명할 수 있습니다. 그들은 그것을 증명할 수 있습니까? 쿼리 실행 횟수에 관계없이 모든 쿼리 가 동일한 순서로 결과를 제공 한다는 것을 증명하는 일련의 테스트를 제공 할 수 있습니까 ?

  3. 그 문제에 대한 다양한 DBMS의 문서를 제공하십시오. 예를 들면 다음과 같습니다.

PostgreSQL :

행 정렬

조회가 출력 테이블을 생성 한 후 (선택 목록이 처리 된 후) 선택적으로 정렬 될 수 있습니다. 정렬을 선택하지 않으면 행이 지정되지 않은 순서로 반환됩니다. 이 경우의 실제 순서는 따라 달라집니다 스캔과 계획의 유형과 디스크에 조인 순서를, 그러나에 의존해서는 안됩니다. 정렬 단계가 명시 적으로 선택된 경우에만 특정 출력 순서를 보장 할 수 있습니다.

SQL 서버 :

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 순서를 보장 하는 간단한 추가 하지 않습니까? 안전 장치가없는 이유는 무엇입니까?
ypercubeᵀᴹ

10
문서가 충분해야합니다. 다른 것이 무엇이든 간과하고 어쨌든, 당신이 무엇을 증명하든 절대 결정적인 것으로 보이지 않을 것입니다. 그것은 항상 당신이했던 것보다 오히려 당신이 하고 설명 할 수 있는 것입니다 . 문서로 무장하고 "보증"을 서면으로 제출 한 후 필요한 순서대로 행을 반환하지 않는 서면 허가를 받으십시오.

19

이것은 다시 검은 백조 이야기입니다. 아직 보지 못했다고해서 존재하지 않는 것은 아닙니다. 바라건대 당신의 경우에는 또 다른 세계적인 금융 위기로 이어지지 않고 단순히 불행한 고객 몇 명에게로 이어지지 않을 것입니다.

Postgres 설명서에 다음과 같이 명시되어 있습니다.

ORDER BY를 지정하지 않으면 시스템에서 가장 빨리 생성 한 순서대로 행이 리턴됩니다.

이 경우 "시스템"은 postgres 데몬 자체 (데이터 액세스 방법 및 쿼리 옵티 마이저 구현 포함), 기본 운영 체제, 데이터베이스 스토리지의 논리적 및 물리적 레이아웃, 가능하면 CPU 캐시로 구성됩니다. 데이터베이스 사용자는 해당 스택을 제어 할 수 없으므로이 스택이 바로 그 동작을 계속하는 방식에 의존해서는 안됩니다.

당신의 동료들은 성급한 일반화 오류를 저지르고 있습니다. 그들의 요점을 반증하기 위해서는 그들의 가정이 한 번만 잘못되었다는 것을 보여주는 것으로 충분합니다 (예 : 이 dbfiddle) .


12

세 개의 관련 테이블이있는 다음 예를 고려하십시오. 주문, 사용자 및 주문 세부 사항. 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

쿼리 출력은 다음과 같습니다.

╔ =======================================
De OrderDetailsID ║ OrderID ║ 사용자 ID ║
╠ =======================================
║ 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;
╔ =======================================
De OrderDetailsID ║ OrderID ║ 사용자 ID ║
╠ =======================================
║ 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잘못된 순서와 관련된 오류가 있다면 그것은 그들에게 그들의 지정을 비용 수 있으므로, 문을.

다른 테이블을 조인하지 않고 OrderID와 UserID 모두와 일치하는 행을 찾아야하는 간단한 요구 사항이있는 OrderDetails위 의 테이블을 사용 하는 두 번째로 유익한 예입니다. 문제가 있습니다.

성능이 어떤 식 으로든 중요하지 않은 경우 실제 상황에서와 같이 쿼리를 지원하기위한 인덱스를 만듭니다.

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)

그리고 결과 :

╔ ================== ╗
De OrderDetailsID ║
╠ ================== ╣
421 21421 ║
║ 5061421 ║
║ 7091421 ║
║ 691422 ║
║ 3471422 ║
241 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의 대답은 순서가 지정되지 않은 것을 우리에게 알려주는 문서를 인용한다.
Monica와

@LightnessRacesinOrbit 나는 문서화되지 않았다고 명시 적으로 알려주는 문서로 사용합니다. 문서에없는 내용이 지정되지 않은 것도 사실입니다. 일종의 타우 톨 로지입니다. 어쨌든, 나는 대답의 그 부분을 더 구체적으로 편집했습니다.
JoL

3

정확히 데모가 아니라 설명하기에는 너무 깁니다.

큰 테이블에서 일부 데이터베이스는 인터리브 병렬 스캔을 수행합니다.

두 개의 쿼리가 같은 테이블을 스캔하고 거의 동시에 도착하려는 경우 두 번째 쿼리가 시작될 때 첫 번째 테이블이 부분적으로 나올 수 있습니다.

두 번째 쿼리는 테이블 중간에서 시작하여 (첫 번째 쿼리가 완료됨에 따라) 레코드를 수신 한 다음 테이블 시작에서 레코드를 수신 할 수 있습니다.


2

"잘못된"순서의 클러스터 된 인덱스를 만듭니다. 예를 들어 cluster on ID DESC입니다. 이것은 종종 역순을 출력합니다 (이것도 보장되지는 않습니다).

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.