NEWID () Dalam Bergabung Tabel Virtual Menyebabkan Perilaku Silang yang Tidak Disengaja Terapkan

9

Permintaan pekerjaan saya yang sebenarnya adalah gabungan internal, tetapi contoh sederhana ini dengan cross join tampaknya hampir selalu mereproduksi masalah.

SELECT *
FROM (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )
CROSS JOIN (
    SELECT NEWID() TEST_ID
) BB ( B )

Dengan gabungan internal saya, saya memiliki banyak baris yang saya tambahkan ke masing-masing GUID menggunakan fungsi NEWID (), dan untuk sekitar 9 dari 10 baris tersebut perkalian dengan tabel virtual 2-baris menghasilkan hasil yang diharapkan, hanya 2 salinan dari GUID yang sama, sedangkan 1 dari 10 akan menghasilkan hasil yang berbeda. Ini tidak terduga untuk sedikitnya dan memberi saya waktu yang sangat sulit untuk mencoba menemukan bug ini dalam skrip pembuatan data pengujian saya.

Jika Anda melihat kueri berikut menggunakan fungsi getdate dan sysdatetime yang juga non-deterministik, Anda tidak akan melihat ini, saya juga tidak. Saya selalu melihat nilai datetime yang sama di kedua baris hasil akhir.

SELECT *
FROM (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )
CROSS JOIN (
    SELECT GETDATE() TEST_ID
) BB ( B )

SELECT *
FROM (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )
CROSS JOIN (
    SELECT SYSDATETIME() TEST_ID
) BB ( B )

Saat ini saya menggunakan SQL Server 2008 dan pekerjaan saya untuk saat ini adalah memuat baris saya dengan GUID ke dalam variabel tabel sebelum menyelesaikan skrip pembuatan data acak saya. Setelah saya memilikinya sebagai nilai dalam tabel yang bertentangan dengan tabel virtual, masalahnya hilang.

Saya punya solusi, tapi saya mencari cara untuk mengatasinya tanpa tabel atau variabel tabel aktual.

Saat menulis ini saya mencoba tanpa keberhasilan kemungkinan ini: 1) menempatkan newid () ke dalam tabel virtual bersarang:

SELECT *
FROM (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )
CROSS JOIN (
    SELECT TEST_ID
    FROM (
        SELECT NEWID() TEST_ID
    ) TT
) BB ( B )

2) membungkus newid () dalam ekspresi pemeran seperti:

SELECT CAST(NEWID() AS VARCHAR(100)) TEST_ID

3) membalik urutan tampilan tabel virtual dalam ekspresi gabungan

SELECT *
FROM (
    SELECT NEWID() TEST_ID
) BB ( B )
CROSS JOIN (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )

4) menggunakan lintas tidak berkorelasi berlaku

SELECT *
FROM (
    SELECT NEWID() TEST_ID
) BB ( B )
CROSS APPLY (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )

Tepat sebelum akhirnya memposting pertanyaan ini, sekarang saya mencoba ini dengan sukses tampaknya, sebuah salib berkorelasi berlaku:

SELECT *
FROM (
    SELECT NEWID() TEST_ID
) BB ( B )
CROSS APPLY (
    SELECT A
    FROM (
        SELECT 1 UNION ALL
        SELECT 2
    ) TT ( A )
    WHERE BB.B IS NOT NULL
) AA ( A )

Adakah yang punya solusi lain yang lebih elegan dan lebih sederhana? Saya benar-benar tidak ingin menggunakan lintas berlaku atau korelasi untuk perkalian baris sederhana jika saya tidak perlu.

JM Hicks
sumber

Jawaban:

20

Perilaku ini dirancang, seperti dijelaskan secara terperinci pada laporan bug Connect ini . Balasan Microsoft yang paling relevan direproduksi di bawah untuk kenyamanan (dan jika tautannya mati di beberapa titik):

Diposting oleh Microsoft pada 7/7/2008 pada jam 9:27

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 permintaan-rencana.

  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:

  1. Hindari ketergantungan pada waktu yang tidak dijamin dan jumlah eksekusi semantik.
  2. Hindari penggunaan NEWID () ekspresi dalam-dalam tabel.
  3. Gunakan OPTION untuk memaksakan perilaku tertentu (perdagangan perf)

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

The GETDATEdan SYSDATETIMEfungsi memang non-deterministik, tetapi mereka diperlakukan sebagai konstanta runtime untuk query tertentu. Secara umum, ini berarti nilai fungsi di-cache ketika eksekusi permintaan dimulai, dan hasilnya digunakan kembali untuk semua referensi dalam kueri.

Tak satu pun dari 'solusi' dalam pertanyaan ini aman; tidak ada jaminan perilaku tidak akan berubah saat rencana dikompilasi, ketika Anda selanjutnya menerapkan paket layanan atau pembaruan kumulatif ... atau karena alasan lain.

Satu-satunya solusi yang aman adalah dengan menggunakan objek sementara semacam - variabel, tabel, atau fungsi multi-pernyataan misalnya. Menggunakan solusi yang tampaknya berfungsi hari ini berdasarkan pengamatan adalah cara yang bagus untuk mengalami perilaku tak terduga di masa depan, biasanya dalam bentuk peringatan paging pada jam 3 pagi pada hari Minggu pagi.

Paul White 9
sumber
"Tidak ada 'penyelesaian' dalam pertanyaan yang aman;" juga. Ketika saya mencoba menerapkan salah satunya pada permintaan kerja saya yang sebenarnya, itu tidak membantu sama sekali.
JM Hicks