SQL Server membagi A <> B menjadi A <B OR A> B, menghasilkan hasil yang aneh jika B adalah non-deterministik

26

Kami telah menemukan masalah yang menarik dengan SQL Server. Pertimbangkan contoh repro berikut:

CREATE TABLE #test (s_guid uniqueidentifier PRIMARY KEY);
INSERT INTO #test (s_guid) VALUES ('7E28EFF8-A80A-45E4-BFE0-C13989D69618');

SELECT s_guid FROM #test
WHERE s_guid = '7E28EFF8-A80A-45E4-BFE0-C13989D69618'
  AND s_guid <> NEWID();

DROP TABLE #test;

biola

Mohon lupakan sejenak bahwa s_guid <> NEWID()kondisinya tampaknya sama sekali tidak berguna - ini hanyalah contoh repro minimal. Karena probabilitas untuk NEWID()mencocokkan beberapa nilai konstan yang diberikan sangat kecil, itu harus mengevaluasi ke TRUE setiap waktu.

Tapi ternyata tidak. Menjalankan kueri ini biasanya mengembalikan 1 baris, tetapi kadang-kadang (cukup sering, lebih dari 1 kali dari 10) mengembalikan 0 baris. Saya telah mereproduksinya dengan SQL Server 2008 di sistem saya, dan Anda dapat mereproduksi secara on-line dengan biola yang ditautkan di atas (SQL Server 2014).

Melihat rencana eksekusi mengungkapkan bahwa penganalisa kueri rupanya membagi kondisi menjadi s_guid < NEWID() OR s_guid > NEWID():

tangkapan layar rencana kueri

... yang sepenuhnya menjelaskan mengapa kadang-kadang gagal (jika ID yang dihasilkan pertama lebih kecil dan yang kedua lebih besar dari ID yang diberikan).

Apakah SQL Server memungkinkan untuk mengevaluasi A <> Bsebagai A < B OR A > B, bahkan jika salah satu ekspresi adalah non-deterministik? Jika ya, di mana dokumen itu didokumentasikan? Atau apakah kami menemukan bug?

Menariknya, AND NOT (s_guid = NEWID())menghasilkan rencana eksekusi yang sama (dan hasil acak yang sama).

Kami menemukan masalah ini ketika pengembang ingin mengecualikan baris tertentu dan digunakan:

s_guid <> ISNULL(@someParameter, NEWID())

sebagai "jalan pintas" untuk:

(@someParameter IS NULL OR s_guid <> @someParameter)

Saya mencari dokumentasi dan / atau konfirmasi bug. Kode tidak semuanya relevan sehingga penyelesaian tidak diperlukan.

Heinzi
sumber
4
Tampaknya mirip dengan pertanyaan ini: Hasil yang tidak terduga dengan angka acak dan tipe gabungan
Erik Darling

Jawaban:

22

Apakah SQL Server memungkinkan untuk mengevaluasi A <> Bsebagai A < B OR A > B, bahkan jika salah satu ekspresi adalah non-deterministik?

Ini adalah poin yang agak kontroversial, dan jawabannya adalah "ya" yang memenuhi syarat.

Diskusi terbaik yang saya sadari diberikan sebagai jawaban atas laporan bug Itzik Ben-Gan Connect Bug dengan NEWID dan Table Expressions , yang ditutup karena tidak akan diperbaiki. Connect telah dipensiunkan, jadi tautannya ada ke arsip web. Sayangnya, banyak materi bermanfaat hilang (atau semakin sulit ditemukan) oleh matinya Connect. Pokoknya, kutipan paling berguna dari Jim Hogg dari Microsoft ada:

Ini sangat menyentuh inti permasalahan - apakah optimisasi diizinkan untuk mengubah semantik program? Yaitu: jika suatu program menghasilkan jawaban tertentu, tetapi berjalan lambat, apakah sah untuk Pengoptimal Kueri membuat program itu berjalan lebih cepat, namun juga mengubah hasil yang diberikan?

Sebelum berteriak "TIDAK!" (Kecenderungan pribadi saya sendiri juga :-), pertimbangkan: kabar baiknya adalah, dalam 99% kasus, jawabannya sama. Jadi Optimasi Kueri adalah kemenangan yang jelas. Berita buruknya adalah, jika kueri berisi kode efek samping, maka paket yang berbeda BISA menghasilkan hasil yang berbeda. Dan NEWID () adalah salah satu 'fungsi' efek samping (non-deterministik) yang memaparkan perbedaan. [Sebenarnya, jika Anda bereksperimen, Anda dapat menyusun yang lain - misalnya, evaluasi hubung singkat klausa AND: membuat klausa kedua membuang aritmatika divide-by-zero - optimasi yang berbeda dapat mengeksekusi klausa kedua SEBELUM klausa pertama] Ini mencerminkan Penjelasan Craig, di tempat lain di utas ini, bahwa SqlServer tidak menjamin ketika operator skalar dieksekusi.

Jadi, kita punya pilihan: jika kita ingin menjamin perilaku tertentu di hadapan kode non-deterministik (efek samping) - sehingga hasil GABUNGAN, misalnya, ikuti semantik dari eksekusi nested-loop - maka kita dapat menggunakan OPSI yang tepat untuk memaksa perilaku itu - seperti yang ditunjukkan UC. Tetapi kode yang dihasilkan akan berjalan lambat - itulah biaya, pada dasarnya, tertatih-tatih Pengoptimal Kueri.

Semua yang dikatakan, kami memindahkan Pengoptimal Kueri ke arah perilaku "seperti yang diharapkan" untuk NEWID () - menukar kinerja untuk "hasil seperti yang diharapkan".

Salah satu contoh perubahan perilaku dalam hal ini dari waktu ke waktu adalah NULLIF bekerja secara salah dengan fungsi-fungsi non-deterministik seperti RAND () . Ada juga kasus serupa lainnya yang digunakan misalnya COALESCEdengan subquery yang dapat menghasilkan hasil yang tidak terduga, dan yang juga sedang ditangani secara bertahap.

Jim melanjutkan:

Menutup lingkaran. . . Saya sudah membahas pertanyaan ini dengan tim Dev. Dan akhirnya kami memutuskan untuk tidak mengubah perilaku saat ini, karena alasan berikut:

1) Pengoptimal tidak menjamin waktu atau jumlah eksekusi fungsi skalar. Ini adalah prinsip yang sudah lama ditetapkan. Ini adalah 'kelonggaran' mendasar yang memungkinkan pengoptimal kebebasan yang cukup untuk mendapatkan peningkatan yang signifikan dalam eksekusi rencana permintaan.

2) "perilaku sekali per baris" ini bukan masalah baru, meskipun tidak banyak dibahas. Kami mulai mengubah perilakunya kembali di rilis Yukon. Tetapi cukup sulit untuk dijabarkan dengan tepat, dalam semua kasus, persis apa artinya! Misalnya, apakah ini berlaku untuk baris sementara yang dihitung 'dalam perjalanan' ke hasil akhir? - dalam hal ini jelas tergantung pada rencana yang dipilih. Atau apakah itu hanya berlaku untuk baris yang pada akhirnya akan muncul dalam hasil yang selesai? - Ada rekursi jahat terjadi di sini, karena saya yakin Anda akan setuju!

3) Seperti yang saya sebutkan sebelumnya, kami default untuk "mengoptimalkan kinerja" - yang bagus untuk 99% kasus. 1% dari kasus di mana ia dapat mengubah hasil cukup mudah dikenali - 'fungsi' efek samping seperti NEWID - dan mudah untuk 'diperbaiki' (perdagangan perf, sebagai konsekuensinya). Default ini untuk "mengoptimalkan kinerja" lagi, sudah lama didirikan, dan diterima. (Ya, itu bukan sikap yang dipilih oleh kompiler untuk bahasa pemrograman konvensional, tetapi jadilah itu).

Jadi, rekomendasi kami adalah:

a) Hindari ketergantungan pada waktu yang tidak dijamin dan jumlah eksekusi semantik. b) Hindari penggunaan NEWID () ekspresi dalam-dalam tabel. c) Gunakan OPTION untuk memaksa perilaku tertentu (perdagangan perf)

Semoga penjelasan ini membantu menjelaskan alasan kami untuk menutup bug ini sebagai "tidak akan diperbaiki".


Menariknya, AND NOT (s_guid = NEWID())menghasilkan rencana eksekusi yang sama

Ini adalah konsekuensi dari normalisasi, yang terjadi sangat awal selama kompilasi permintaan. Kedua ekspresi dikompilasi ke bentuk normal yang sama persis, sehingga rencana eksekusi yang sama dihasilkan.

Paul White mengatakan GoFundMonica
sumber
Dalam hal ini, jika kita ingin memaksakan rencana tertentu yang tampaknya menghindari masalah, kita dapat menggunakan WITH (FORCESCAN). Untuk lebih pasti, kita harus menggunakan variabel untuk menyimpan hasil NEWID () sebelum menjalankan kueri.
Razvan Socol
11

Ini didokumentasikan (semacam) di sini:

Frekuensi fungsi yang ditentukan dalam kueri dieksekusi dapat bervariasi antara rencana eksekusi yang dibuat oleh pengoptimal. Contohnya adalah fungsi yang dipanggil oleh subquery dalam klausa WHERE. Frekuensi subquery dan fungsinya dijalankan dapat bervariasi dengan jalur akses berbeda yang dipilih oleh pengoptimal.

Fungsi Buatan Pengguna

Ini bukan satu-satunya bentuk kueri tempat rencana kueri akan mengeksekusi NEWID () beberapa kali dan mengubah hasilnya. Ini membingungkan, tetapi sebenarnya penting bagi NEWID () agar berguna untuk pembuatan kunci dan penyortiran acak.

Yang paling membingungkan adalah bahwa tidak semua fungsi non-deterministik benar-benar berperilaku seperti ini. Misalnya RAND () dan GETDATE () akan menjalankan hanya sekali per permintaan.

David Browne - Microsoft
sumber
Apakah ada posting blog atau serupa yang menjelaskan mengapa / kapan mesin akan mengkonversi "tidak sama" menjadi suatu rentang?
Tuan Magoo
3
Tidak yang saya tahu. Mungkin rutin karena =,, <dan >dapat dievaluasi secara efisien terhadap BTree.
David Browne - Microsoft
5

Untuk apa nilainya, jika Anda melihat dokumen standar SQL 92 lama ini , persyaratan seputar ketidaksetaraan dijelaskan di bagian " 8.2 <comparison predicate>" sebagai berikut:

1) Misalkan X dan Y menjadi dua <elemen konstruktor nilai baris> yang sesuai. Biarkan XV dan YV menjadi nilai yang diwakili masing-masing oleh X dan Y.

[...]

ii) "X <> Y" benar jika dan hanya jika XV dan YV tidak sama.

[...]

7) Biarkan Rx dan Ry menjadi dua <konstruktor nilai baris> dari <predikat perbandingan> dan biarkan RXi dan RYi menjadi elemen ke-<<konstruktor nilai baris> s masing-masing dari Rx dan Ry. "Rx <comp op> Ry" benar, salah, atau tidak dikenal sebagai berikut:

[...]

b) "x <> Ry" benar jika dan hanya jika RXi <> RYi untuk beberapa i.

[...]

h) "x <> Ry" salah jika dan hanya jika "Rx = Ry" benar.

Catatan: Saya menyertakan 7b dan 7j untuk kelengkapan karena mereka berbicara tentang <>perbandingan - Saya tidak berpikir perbandingan konstruktor nilai baris dengan beberapa nilai diimplementasikan dalam T-SQL, kecuali saya hanya salah paham secara besar-besaran tentang apa yang dikatakan ini - yang sangat mungkin dilakukan

Ini adalah sekelompok sampah yang membingungkan. Tetapi jika Anda ingin terus menyelam sampah ...

Saya pikir 1.ii adalah item yang berlaku dalam skenario ini, karena kami membandingkan nilai "elemen konstruktor nilai baris."

ii) "X <> Y" benar jika dan hanya jika XV dan YV tidak sama.

Pada dasarnya itu mengatakan X <> Ybenar jika nilai yang diwakili oleh X dan Y tidak sama. Karena X < Y OR X > Ypenulisan ulang yang setara secara logis dari predikat itu, sangat bagus bagi pengoptimal untuk menggunakannya.

Standar tidak memberikan batasan pada definisi ini terkait dengan deterministik-ness (atau apa pun, Anda mendapatkannya) elemen konstruktor nilai baris di kedua sisi <>operator perbandingan. Adalah tanggung jawab kode pengguna untuk berurusan dengan fakta bahwa ekspresi nilai di satu sisi mungkin non-deterministik.

Josh Darnell
sumber
1
Saya akan memperbaiki dari pemungutan suara (naik atau turun) tapi saya tidak yakin. Kutipan yang Anda berikan menyebutkan "nilai" . Pemahaman saya adalah perbandingan antara dua nilai, satu di setiap sisi. Bukan antara dua (atau lebih) contoh nilai di setiap sisi. Plus, standar (setidaknya 92 yang Anda kutip) tidak menyebutkan sama sekali fungsi non-deterministik. Dengan alasan yang sama seperti milik Anda, kami dapat mengasumsikan bahwa produk SQL yang sesuai dengan standar tidak menyediakan fungsi non-deterministik tetapi hanya yang disebutkan dalam standar.
ypercubeᵀᴹ
@terima kasih atas umpan baliknya! Saya pikir interpretasi Anda pasti valid. Ini adalah pertama kalinya saya membaca dokumen itu. Ini menyebutkan nilai dalam konteks nilai yang diwakili oleh "konstruktor nilai baris," yang mana di bagian lain dalam dokumen itu dikatakan dapat menjadi subquery skalar (di antara banyak hal lainnya). Subquery skalar khususnya sepertinya bisa non-deterministik. Tapi saya benar-benar tidak tahu apa yang saya bicarakan =)
Josh Darnell