Bagaimana cara membuktikan kurangnya urutan implisit dalam database?

21

Baru-baru ini saya menjelaskan kepada rekan-rekan tentang pentingnya memiliki kolom yang digunakan untuk mengurutkan data dalam tabel database jika perlu, misalnya untuk data yang dipesan secara kronologis. Ini terbukti agak sulit karena mereka bisa menjalankan kembali kueri mereka tanpa henti dan selalu akan mengembalikan rangkaian baris yang sama dalam urutan yang sama.

Saya telah memperhatikan ini sebelumnya dan semua yang bisa saya lakukan adalah bersikeras bahwa mereka mempercayai saya dan tidak hanya berasumsi bahwa tabel database akan berperilaku seperti file CSV atau Excel tradisional.

Misalnya, menjalankan kueri (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');

akan membuat tabel dengan urutan konseptual yang jelas. Memilih data yang sama dengan cara paling sederhana adalah:

SELECT * FROM mytable;

Selalu memberi saya hasil berikut:

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

Saya bisa melakukan ini berulang-ulang dan itu akan selalu mengembalikan kepada saya data yang sama dalam urutan yang sama. Namun saya tahu bahwa urutan implisit ini dapat dipatahkan, saya telah melihatnya sebelumnya, terutama dalam dataset besar, di mana beberapa nilai acak akan terlempar ke tempat "salah" ketika dipilih. Tetapi saya sadar bahwa saya tidak tahu bagaimana ini terjadi atau bagaimana memperbanyaknya. Saya merasa sulit untuk mendapatkan hasil di Google karena permintaan pencarian cenderung hanya mengembalikan bantuan umum pada pengurutan set hasil.

Jadi, pertanyaan saya pada dasarnya adalah ini:

  1. Bagaimana saya dapat membuktikan dan secara konkret membuktikan bahwa urutan pengembalian baris dari permintaan tanpa ORDER BYpernyataan tidak dapat diandalkan, lebih disukai dengan menyebabkan dan menampilkan rincian urutan implisit bahkan ketika tabel tersebut tidak diperbarui atau diedit ?

  2. Apakah ada bedanya sama sekali jika data hanya dimasukkan sekali secara massal dan kemudian tidak pernah diperbarui lagi?

Saya lebih suka jawaban berbasis postgres karena itu yang paling saya kenal tetapi saya lebih tertarik pada teori itu sendiri.


sumber
6
“Tidak pernah menulis atau memperbarui lagi” - mengapa ini sebuah tabel? Kedengarannya seperti file. Atau enum. Atau sesuatu yang tidak perlu ada dalam database. Jika ini kronologis, bukankah ada kolom tanggal untuk dipesan? Jika kronologi penting, Anda akan berpikir bahwa info akan cukup penting untuk ada dalam tabel. Bagaimanapun, rencana dapat berubah karena seseorang menjatuhkan atau membuat indeks baru, atau acara seperti perubahan memori, jejak bendera, atau pengaruh lainnya. Argumen mereka terdengar seperti, “Saya tidak pernah memakai sabuk pengaman saya dan saya tidak pernah melewati kaca depan saya, jadi saya akan terus tidak memakai sabuk pengaman saya.” :-(
Aaron Bertrand
9
Beberapa masalah logika tidak bisa diselesaikan secara teknis atau tanpa keterlibatan SDM. Jika perusahaan Anda ingin mengizinkan praktik pengembang yang bergantung pada kepercayaan pada voodoo dan mengabaikan dokumentasi, dan case use Anda benar-benar terbatas pada tabel kecil yang tidak pernah diperbarui, biarkan mereka memiliki caranya sendiri dan perbarui resume Anda. Tidak ada gunanya berdebat.
Aaron Bertrand
1
Anda tidak memiliki dasar untuk mengklaim "akan selalu". Anda hanya dapat mengklaim "selalu", "ketika saya memeriksa". Bahasa memiliki definisi - yaitu kontrak dengan pengguna.
philipxy
10
Saya ingin tahu mengapa kolega Anda ini menentang menambahkan order byklausa ke pertanyaan mereka? Apakah mereka mencoba menghemat penyimpanan kode sumber? keausan keyboard? waktu yang dibutuhkan untuk mengetik klausa yang ditakuti?
mustaccio
2
Saya selalu berpikir bahwa mesin basis data harus secara acak mengubah beberapa baris pertama permintaan yang semantiknya tidak menjamin pemesanan, untuk membantu memfasilitasi pengujian.
Doug McClean

Jawaban:

30

Saya melihat tiga cara untuk mencoba meyakinkan mereka:

  1. Biarkan mereka mencoba kueri yang sama tetapi dengan tabel yang lebih besar (lebih banyak jumlah baris) atau ketika tabel diperbarui di antara eksekusi. Atau baris baru dimasukkan dan beberapa yang lama dihapus. Atau indeks ditambahkan atau dihapus di antara eksekusi. Atau meja disedot (dalam Postgres). Atau indeks dibangun kembali (dalam SQL Server). Atau tabel diubah dari berkerumun menjadi tumpukan. Atau layanan basis data dimulai kembali.

  2. Anda dapat menyarankan agar mereka membuktikan bahwa eksekusi yang berbeda akan mengembalikan urutan yang sama. Bisakah mereka membuktikannya? Bisakah mereka memberikan serangkaian tes yang membuktikan bahwa permintaan apa pun akan memberikan hasil dalam urutan yang sama, tidak peduli berapa kali dieksekusi?

  3. Berikan dokumentasi berbagai DBMS dalam hal itu. Sebagai contoh:

PostgreSQL :

Mengurutkan Baris

Setelah kueri menghasilkan tabel output (setelah daftar pilih diproses) secara opsional dapat diurutkan. Jika penyortiran tidak dipilih, baris akan dikembalikan dalam urutan yang tidak ditentukan. Urutan aktual dalam kasus itu akan tergantung pada pemindaian dan bergabung dengan jenis paket dan urutan pada disk, tetapi tidak boleh diandalkan. Pemesanan keluaran tertentu hanya dapat dijamin jika langkah sortir dipilih secara eksplisit.

SQL Server :

SELECT- ORDER BYKlausul (Transact-SQL)

Mengurutkan data yang dikembalikan oleh kueri di SQL Server. Gunakan klausa ini untuk:

Pesan rangkaian hasil kueri dengan daftar kolom yang ditentukan dan, secara opsional, batasi baris yang dikembalikan ke rentang yang ditentukan. Urutan di mana baris dikembalikan dalam set hasil tidak dijamin kecuali ORDER BYklausa ditentukan.

Oracle :

order_by_clause

Gunakan ORDER BYklausa untuk memesan baris yang dikembalikan oleh pernyataan. Tanpa order_by_clause, tidak ada jaminan bahwa permintaan yang sama dieksekusi lebih dari satu kali akan mengambil baris dalam urutan yang sama.

ypercubeᵀᴹ
sumber
Dengan tabel yang sangat kecil yang tidak dimodifikasi, Anda mungkin melihat perilaku ini. Itu yang diharapkan. Tapi itu juga tidak dijamin. Urutan dapat berubah karena Anda menambahkan indeks atau Anda memodifikasi indeks atau Anda me-restart database dan mungkin banyak kasus lainnya.
ypercubeᵀᴹ
6
Jika pesanan penting, maka siapa yang pernah bertanggung jawab untuk meninjau kode mereka harus menolak sampai mereka menggunakan ORDER BY. Para pengembang DBMS (Oracle, SQL Server, Postgres) semuanya mengatakan hal yang sama tentang apa yang dijamin oleh produk mereka dan apa yang tidak (dan mereka dibayar jauh lebih banyak daripada saya, sehingga mereka tahu apa yang mereka katakan, selain telah membangun ini sesuatu).
ypercubeᵀᴹ
1
Sekalipun pesanannya terlihat sama sekarang, apakah sudah pasti bahwa tabel-tabel ini tidak akan pernah diperbarui sepanjang masa pakai perangkat lunak yang Anda bangun? Bahwa tidak ada lagi baris yang akan dimasukkan?
ypercubeᵀᴹ
1
Apakah ada jaminan bahwa tabel ini akan selalu sekecil ini? Apakah ada jaminan bahwa tidak ada lagi kolom yang akan ditambahkan? Saya bisa melihat puluhan kasus berbeda di mana tabel dapat diubah di masa mendatang (dan beberapa perubahan ini dapat memengaruhi urutan hasil kueri). Saya sarankan Anda meminta mereka untuk menjawab semua ini. Bisakah mereka menjamin bahwa hal seperti itu tidak akan terjadi? Dan mengapa mereka tidak menambahkan sederhana ORDER BY, yang akan menjamin pesanan, tidak peduli bagaimana tabel akan berubah ? Mengapa tidak menambahkan brankas, yang tidak membahayakan?
ypercubeᵀᴹ
10
Dokumentasi harus memadai. Yang lainnya adalah menebak-nebak, dan bagaimanapun juga, tidak akan pernah dianggap sebagai definitif, apa pun yang Anda buktikan. Itu akan selalu menjadi sesuatu yang Anda lakukan dan jelaskan, mungkin dengan biaya Anda, daripada sesuatu yang ada . Dipersenjatai dengan dokumentasi, kirimkan "garansi" Anda secara tertulis, dan cukup minta izin tertulis untuk tidak mengembalikan baris dalam urutan yang diperlukan (Anda tidak akan mendapatkannya).
19

Ini adalah cerita angsa hitam lagi. Jika Anda belum melihatnya, itu tidak berarti mereka tidak ada. Mudah-mudahan dalam kasus Anda itu tidak akan menyebabkan krisis keuangan lain di seluruh dunia, hanya untuk beberapa pelanggan yang tidak bahagia.

Dokumentasi Postgres mengatakan ini secara eksplisit:

Jika ORDER BY tidak diberikan, baris dikembalikan dalam urutan apa pun yang ditemukan oleh sistem untuk menghasilkan tercepat.

"Sistem" dalam hal ini terdiri dari daemon postgres itu sendiri (termasuk implementasi metode akses data dan pengoptimal kueri), sistem operasi yang mendasarinya, tata letak logis dan fisik dari penyimpanan basis data, bahkan mungkin cache CPU. Karena Anda sebagai pengguna basis data tidak memiliki kendali atas tumpukan itu, Anda tidak boleh mengandalkannya terus berperilaku selamanya seperti saat ini.

Rekan-rekan Anda melakukan kesalahan generalisasi yang terburu - buru . Untuk menyanggah pendapat mereka, cukup menunjukkan bahwa anggapan mereka salah hanya sekali, misalnya oleh dbfiddle ini .

mustaccio
sumber
12

Perhatikan contoh berikut, di mana kami memiliki tiga tabel terkait. Pesanan, Pengguna, dan Rincian Pesanan. OrderDetails ditautkan dengan kunci asing ke tabel Pesanan dan Tabel Pengguna. Ini pada dasarnya adalah pengaturan yang sangat khas untuk database relasional; bisa dibilang seluruh tujuan DBMS relasional .

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

Di sini, kami menanyakan tabel OrderDetails di mana 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

Output dari kueri terlihat seperti:

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

Seperti yang Anda lihat, urutan output baris tidak cocok dengan urutan baris dalam tabel OrderDetails.

Menambahkan eksplisit ORDER BYmemastikan baris akan dikembalikan ke klien dalam urutan yang diinginkan:

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

Jika urutan baris sangat penting, dan teknisi Anda tahu bahwa urutan itu penting, mereka seharusnya hanya ingin menggunakan ORDER BYpernyataan, karena mungkin mereka akan dikenakan biaya penunjukan jika ada kegagalan terkait dengan urutan yang salah.

Contoh kedua, mungkin lebih instruktif, menggunakan OrderDetailstabel dari atas, di mana kami tidak bergabung dengan tabel lain, tetapi memiliki persyaratan sederhana untuk menemukan baris yang cocok dengan OrderID dan UserID, kami melihat masalahnya.

Kami akan membuat indeks untuk mendukung kueri, seperti yang mungkin Anda lakukan di kehidupan nyata jika kinerja sangat penting (kapan bukan?).

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

Inilah pertanyaannya:

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

Dan hasilnya:

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

Menambahkan ORDER BYklausa pasti akan memastikan kita mendapatkan jenis yang benar di sini juga.

Mock-up ini hanyalah contoh sederhana di mana baris tidak dijamin "berurutan" tanpa ORDER BYpernyataan eksplisit . Ada banyak lagi contoh seperti ini, dan karena kode mesin DBMS sering berubah, perilaku spesifik dapat berubah seiring waktu.

Max Vernon
sumber
10

Sebagai contoh praktis, di Postgres, urutan saat ini berubah ketika Anda memperbarui baris:

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

Saya tidak berpikir aturan pemesanan implisit yang ada ini didokumentasikan di mana saja, pasti dapat berubah tanpa pemberitahuan, dan jelas bukan perilaku portabel di mesin DB.

JoL
sumber
Hal ini didokumentasikan: jawaban ypercube ini mengutip dokumentasi mengatakan kepada kita bahwa perintah yang tidak ditentukan.
Lightness Races dengan Monica
@LightnessRacesinOrbit Saya menganggap itu sebagai dokumentasi yang secara eksplisit memberi tahu kami bahwa itu tidak didokumentasikan. Maksud saya, juga benar bahwa apa pun yang tidak ada dalam dokumentasi tidak ditentukan. Ini semacam tautologi. Lagi pula, saya mengedit bagian dari jawaban itu untuk lebih spesifik.
JoL
3

bukan demo, tapi terlalu lama untuk komentar.

Pada tabel besar beberapa basis data akan melakukan pemindaian paralel berselang-seling:

Jika dua kueri ingin memindai tabel yang sama, dan tiba di waktu yang hampir bersamaan, yang pertama mungkin sebagian jalan melalui tabel saat yang kedua dimulai.

Kueri kedua bisa menerima catatan mulai dari tengah tabel (saat kueri pertama selesai) dan kemudian menerima catatan dari awal tabel.

Jasen
sumber
2

Buat indeks berkerumun yang memiliki urutan "salah". Misalnya, klaster aktif ID DESC. Ini akan sering menghasilkan urutan terbalik (meskipun ini juga tidak dijamin).

usr
sumber