Apakah kita melakukan sesuatu yang salah atau kesalahan SQL Server?
Ini adalah bug hasil yang salah, yang harus Anda laporkan melalui saluran dukungan biasa. Jika Anda tidak memiliki perjanjian dukungan, mungkin membantu untuk mengetahui bahwa insiden berbayar biasanya dikembalikan jika Microsoft mengkonfirmasi perilaku tersebut sebagai bug.
Bug ini membutuhkan tiga bahan:
- Loop bersarang dengan referensi luar (berlaku)
- Spool indeks sisi malas yang mencari referensi luar
- Operator Rangkaian sisi dalam
Misalnya, kueri dalam pertanyaan menghasilkan rencana seperti berikut:
Ada banyak cara untuk menghapus salah satu elemen ini, sehingga bug tidak lagi mereproduksi.
Misalnya, seseorang dapat membuat indeks atau statistik yang berarti pengoptimal memilih untuk tidak menggunakan Malas Indeks Spool. Atau, orang dapat menggunakan petunjuk untuk memaksa hash atau menggabungkan serikat daripada menggunakan Rangkaian. Orang juga bisa menulis ulang kueri untuk mengekspresikan semantik yang sama, tetapi yang menghasilkan bentuk rencana yang berbeda di mana satu atau lebih elemen yang diperlukan hilang.
Keterangan lebih lanjut
Spool Indeks Malas dengan malas mem-cache baris hasil sisi dalam, dalam tabel kerja yang diindeks oleh nilai referensi terluar (parameter berkorelasi). Jika Spool Indeks Malas diminta untuk referensi luar yang telah dilihat sebelumnya, itu mengambil baris hasil di-cache dari tabel kerjanya ("mundur"). Jika spool diminta untuk nilai referensi luar yang belum pernah dilihat sebelumnya, ia menjalankan subtree dengan nilai referensi luar saat ini dan menyimpan hasilnya ("rebind"). Predikat pencarian di Spool Indeks Malas menunjukkan kunci untuk tabel kerjanya.
Masalah terjadi dalam bentuk rencana khusus ini ketika spool memeriksa untuk melihat apakah referensi luar baru sama dengan yang telah dilihat sebelumnya. Nested Loops Join memperbarui referensi luarnya dengan benar, dan memberi tahu operator tentang input dalamnya melalui PrepRecompute
metode antarmuka mereka . Pada awal pemeriksaan ini, operator sisi dalam membaca CParamBounds:FNeedToReload
properti untuk melihat apakah referensi luar telah berubah dari terakhir kali. Contoh jejak tumpukan ditampilkan di bawah ini:
Ketika subtree yang ditunjukkan di atas ada, khususnya di mana Concatenation digunakan, ada yang salah (mungkin masalah ByVal / ByRef / Copy) dengan binding yang CParamBounds:FNeedToReload
selalu mengembalikan false, terlepas dari apakah referensi luar benar-benar berubah atau tidak.
Ketika subtree yang sama ada, tetapi Gabung Union atau Hash Union digunakan, properti penting ini diatur dengan benar pada setiap iterasi, dan Spool Indeks Malas menggulung atau mem-rebind setiap kali sesuai kebutuhan. Agak Mengurutkan dan Stream Agregat tidak bersalah, omong-omong. Kecurigaan saya adalah bahwa Merge dan Hash Union membuat salinan dari nilai sebelumnya, sedangkan Concatenation menggunakan referensi. Sayangnya hampir tidak mungkin untuk memverifikasi ini tanpa akses ke kode sumber SQL Server.
Hasil akhirnya adalah bahwa Spool Indeks Malas dalam bentuk rencana yang bermasalah selalu berpikir telah melihat referensi luar saat ini, mundur dengan mencari ke dalam tabel kerjanya, umumnya tidak menemukan apa pun, sehingga tidak ada baris yang dikembalikan untuk referensi luar itu. Melangkah melalui eksekusi dalam debugger, spool hanya pernah menjalankannyaRewindHelper
metodenya, dan tidak pernah ReloadHelper
metodenya (reload = rebind dalam konteks ini). Ini terbukti dalam rencana eksekusi karena semua operator di bawah spool memiliki 'Jumlah Eksekusi = 1'.
Pengecualian, tentu saja, adalah untuk referensi luar pertama Spool Indeks Malas diberikan. Ini selalu mengeksekusi subtree dan cache baris hasil di tabel kerja. Semua iterasi berikutnya menghasilkan mundur, yang hanya akan menghasilkan baris (baris cache tunggal) ketika iterasi saat ini memiliki nilai yang sama untuk referensi luar seperti yang pertama kali dilakukan.
Jadi, untuk input apa pun yang ditetapkan di sisi luar Nested Loops Join, kueri akan mengembalikan sebanyak mungkin baris karena ada duplikat dari baris pertama yang diproses (ditambah satu untuk baris pertama itu sendiri tentunya).
Demo
Tabel dan data sampel:
CREATE TABLE #T1
(
pk integer IDENTITY NOT NULL,
c1 integer NOT NULL,
CONSTRAINT PK_T1
PRIMARY KEY CLUSTERED (pk)
);
GO
INSERT #T1 (c1)
VALUES
(1), (2), (3), (4), (5), (6),
(1), (2), (3), (4), (5), (6),
(1), (2), (3), (4), (5), (6);
Kueri (sepele) berikut menghasilkan hitungan yang benar dari dua untuk setiap baris (total 18) menggunakan Gabungan Gabungan:
SELECT T1.c1, C.c1
FROM #T1 AS T1
CROSS APPLY
(
SELECT COUNT_BIG(*) AS c1
FROM
(
SELECT T1.c1
UNION
SELECT NULL
) AS U
) AS C;
Jika sekarang kita menambahkan petunjuk kueri untuk memaksa Rangkaian:
SELECT T1.c1, C.c1
FROM #T1 AS T1
CROSS APPLY
(
SELECT COUNT_BIG(*) AS c1
FROM
(
SELECT T1.c1
UNION
SELECT NULL
) AS U
) AS C
OPTION (CONCAT UNION);
Rencana eksekusi memiliki bentuk yang bermasalah:
Dan hasilnya sekarang salah, hanya tiga baris:
Meskipun perilaku ini tidak dijamin, baris pertama dari Pemindaian Indeks Clustered memiliki c1
nilai 1. Ada dua baris lain dengan nilai ini, sehingga total tiga baris dihasilkan.
Sekarang pangkas tabel data dan muat dengan duplikat dari baris 'pertama':
TRUNCATE TABLE #T1;
INSERT #T1 (c1)
VALUES
(1), (2), (3), (4), (5), (6),
(1), (2), (3), (4), (5), (6),
(1), (1), (1), (1), (1), (1);
Sekarang rencana Penggabungan adalah:
Dan, seperti yang ditunjukkan, 8 baris diproduksi, semua c1 = 1
tentu saja dengan:
Saya perhatikan Anda telah membuka item Connect untuk bug ini tetapi sebenarnya itu bukan tempat untuk melaporkan masalah yang memiliki dampak produksi. Jika itu masalahnya, Anda harus menghubungi Dukungan Microsoft.
Bug hasil salah ini diperbaiki pada tahap tertentu. Tidak lagi mereproduksi untuk saya di versi SQL Server mulai 2012 dan seterusnya. Itu repro pada SQL Server 2008 R2 SP3-GDR build 10.50.6560.0 (X64).