Mengapa LEN () berfungsi sangat meremehkan kardinalitas di SQL Server 2014?

26

Saya memiliki tabel dengan kolom string dan predikat yang memeriksa baris dengan panjang tertentu. Di SQL Server 2014, saya melihat perkiraan 1 baris terlepas dari panjang yang saya periksa. Ini menghasilkan rencana yang sangat buruk karena sebenarnya ada ribuan atau bahkan jutaan baris dan SQL Server memilih untuk meletakkan tabel ini di sisi luar dari loop bersarang.

Apakah ada penjelasan untuk perkiraan kardinalitas 1.0003 untuk SQL Server 2014 sementara SQL Server 2012 memperkirakan 31.622 baris? Apakah ada solusi yang baik?

Ini adalah reproduksi singkat dari masalah ini:

-- Create a table with 1MM rows of dummy data
CREATE TABLE #customers (cust_nbr VARCHAR(10) NOT NULL)
GO

INSERT INTO #customers WITH (TABLOCK) (cust_nbr)
    SELECT TOP 1000000 
        CONVERT(VARCHAR(10),
        ROW_NUMBER() OVER (ORDER BY (SELECT NULL))) AS cust_nbr
    FROM master..spt_values v1
    CROSS JOIN master..spt_values v2
GO

-- Looking for string of a certain length.
-- While both CEs yield fairly poor estimates, the 2012 CE is much
-- more conservative (higher estimate) and therefore much more likely
-- to yield an okay plan rather than a drastically understimated loop join.
-- 2012: 31,622 rows estimated, 900K rows actual
-- 2014: 1 row estimated, 900K rows actual
SELECT COUNT(*)
FROM #customers
WHERE LEN(cust_nbr) = 6
OPTION (QUERYTRACEON 9481) -- Optionally, use 2012 CE
GO

Berikut ini adalah skrip yang lebih lengkap yang menunjukkan tes tambahan

Saya juga telah membaca whitepaper pada SQL Server 2014 Cardinality Estimator , tetapi tidak menemukan apa pun di sana yang menjelaskan situasi.

Geoff Patterson
sumber

Jawaban:

20

Untuk warisan CE, saya melihat perkiraan untuk 3,16228% dari baris - dan itu adalah "angka ajaib" heuristik yang digunakan untuk kolom = predikat literal (ada heuristik lain berdasarkan konstruksi predikat - tetapi LENmelilit kolom untuk hasil legacy CE cocok dengan kerangka kerja tebakan ini). Anda dapat melihat contoh-contoh ini pada posting tentang Selektivitas Tebak tanpa adanya Statistik oleh Joe Sack, dan Estimasi Perbandingan Konstan-Konstan oleh Ian Jose.

-- Legacy CE: 31622.8 rows
SELECT  COUNT(*)
FROM    #customers
WHERE   LEN(cust_nbr) = 6
OPTION  ( QUERYTRACEON 9481); -- Legacy CE
GO

Sekarang untuk perilaku CE baru, sepertinya ini sekarang terlihat oleh pengoptimal (yang berarti kita dapat menggunakan statistik). Saya melakukan latihan melihat output kalkulator di bawah ini, dan Anda dapat melihat statistik pembuatan otomatis yang terkait sebagai pointer:

-- New CE: 1.00007 rows
SELECT  COUNT(*)
FROM    #customers
WHERE   LEN(cust_nbr) = 6
OPTION  ( QUERYTRACEON 2312 ); -- New CE
GO

-- View New CE behavior with 2363 (for supported option use XEvents)
SELECT  COUNT(*)
FROM    #customers
WHERE   LEN(cust_nbr) = 6
OPTION  (QUERYTRACEON 2312, QUERYTRACEON 2363, QUERYTRACEON 3604, RECOMPILE); -- New CE
GO

/*
Loaded histogram for column QCOL:
[tempdb].[dbo].[#customers].cust_nbr from stats with id 2
Using ambient cardinality 1e+006 to combine distinct counts:
  999927

Combined distinct count: 999927
Selectivity: 1.00007e-006
Stats collection generated:
  CStCollFilter(ID=2, CARD=1.00007)
      CStCollBaseTable(ID=1, CARD=1e+006 TBL: #customers)

End selectivity computation
*/

EXEC tempdb..sp_helpstats '#customers';


--Check out AVG_RANGE_ROWS values (for example - plenty of ~ 1)
DBCC SHOW_STATISTICS('tempdb..#customers', '_WA_Sys_00000001_B0368087');
--That's my Stats name yours is subject to change

Sayangnya logika bergantung pada perkiraan jumlah nilai yang berbeda, yang tidak disesuaikan dengan efek LENfungsi.

Kemungkinan solusinya

Anda bisa mendapatkan estimasi berbasis trie di bawah kedua model CE dengan menulis ulang LENsebagai LIKE:

SELECT COUNT_BIG(*)
FROM #customers AS C
WHERE C.cust_nbr LIKE REPLICATE('_', 6);

SEPERTI rencana


Informasi tentang Bendera Jejak digunakan:

  • 2363: menunjukkan banyak informasi, termasuk statistik yang dimuat.
  • 3604: mencetak output dari perintah DBCC ke tab pesan.
Zane
sumber
13

Apakah ada penjelasan untuk perkiraan kardinalitas 1.0003 untuk SQL 2014 sementara SQL 2012 memperkirakan 31.622 baris?

Saya pikir jawaban Zane cukup baik untuk bagian ini.

Apakah ada solusi yang baik?

Anda bisa mencoba membuat Kolom Terhitung yang Tidak Bertahan untuk LEN(cust_nbr)dan (secara opsional) membuat Indeks yang Tidak Berkelompok pada Kolom yang Dikomputasi itu. Itu seharusnya membuat Anda mendapatkan statistik yang akurat.

Saya melakukan beberapa pengujian dan inilah yang saya temukan:

  • Statistik dibuat secara otomatis pada Kolom Dihitung yang Tidak Bertahan, ketika tidak ada indeks yang ditentukan.
  • Menambahkan Indeks Non-Clustered pada Kolom yang Dikomputasi tidak hanya tidak membantu, itu sebenarnya sedikit melukai kinerja. CPU sedikit lebih tinggi dan waktu yang berlalu. Perkiraan biaya yang sedikit lebih tinggi (berapa pun nilainya).
  • Menjadikan Kolom yang Dihitung sebagai PERSISTED(tanpa Indeks) lebih baik daripada dua variasi lainnya. Baris yang Diperkirakan lebih akurat. CPU dan waktu yang berlalu lebih baik (seperti yang diharapkan karena tidak perlu menghitung apa pun per-baris).
  • Saya tidak dapat membuat Indeks yang Difilter atau Statistik yang Difilter pada Kolom yang Dihitung (karena itu dihitung), meskipun itu adalah PERSISTED:-(
Solomon Rutzky
sumber
1
Terima kasih atas perbandingan menyeluruh antara bertahan dan tidak. Adalah baik untuk mengetahui bahwa meskipun kolom yang dikomputasi tetap memiliki kelebihannya, non-bertahan dapat menjadi kemenangan yang sangat cepat dengan overhead yang sangat kecil dalam beberapa kasus di mana statistik pada ekspresi bermanfaat.
Geoff Patterson