Perubahan estimasi pada predikat yang berisi SUBSTRING () di SQL Server 2016?

13

Apakah ada dokumentasi atau penelitian tentang perubahan dalam SQL Server 2016 untuk bagaimana kardinalitas diperkirakan untuk predikat yang berisi SUBSTRING () atau fungsi string lainnya?

Alasan saya bertanya adalah karena saya melihat kueri yang kinerjanya menurun dalam mode kompatibilitas 130 dan alasannya terkait perubahan dalam estimasi jumlah baris yang cocok dengan klausa WHERE yang berisi panggilan ke SUBSTRING (). Saya memperbaiki masalah dengan penulisan ulang kueri, tetapi saya bertanya-tanya apakah ada yang mengetahui dokumentasi tentang perubahan di area ini di SQL Server 2016.

Kode demo di bawah. Perkiraan sangat dekat dalam kasus uji ini, tetapi akurasi bervariasi tergantung pada data.

Dalam kasus pengujian, di level compat 120, SQL Server tampaknya menggunakan histogram untuk estimasi, sedangkan di level compat 130 SQL Server tampaknya mengasumsikan 10% tetap dari tabel cocok.

CREATE DATABASE MyStringTestDB;
GO
USE MyStringTestDB;
GO
DROP TABLE IF EXISTS dbo.StringTest;
CREATE TABLE dbo.StringTest ( [TheString] varchar(15) );
GO
INSERT INTO dbo.StringTest
VALUES
( 'Y5_CLV' );
INSERT INTO dbo.StringTest
VALUES
( 'Y5_EG3' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_NE' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_PQT' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_T2V' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_TT4' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_ZKK' );
INSERT INTO dbo.StringTest
VALUES
( 'ZZ_LW6' );
INSERT INTO dbo.StringTest
VALUES
( 'ZZ_QO3' );
INSERT INTO dbo.StringTest
VALUES
( 'ZZ_TZ7' );
INSERT INTO dbo.StringTest
VALUES
( 'ZZ_UZZ' );

CREATE CLUSTERED INDEX IX_Clustered ON dbo.StringTest (TheString);

/* 
Uses fixed % for estimate; 1.1 rows estimated in this case.
    Plan for computation:
        CSelCalcFixedFilter (0.1) <----
            Selectivity: 0.1
*/
ALTER DATABASE MyStringTestDB SET compatibility_level = 130;
GO
SELECT * 
FROM dbo.StringTest 
WHERE SUBSTRING(TheString, 1, CHARINDEX('_',TheString) - 1) = 'ZZ'
OPTION (QUERYTRACEON 2363, QUERYTRACEON 3604);

/* 
Uses histogram to get estimate of 1
 CSelCalcPointPredsFreqBased <----
      Distinct value calculation:
          CDVCPlanLeaf
              0 Multi-Column Stats, 1 Single-Column Stats, 0 Guesses
      Individual selectivity calculations:
          (none)
    Loaded histogram for column QCOL: [DBA].[dbo].[StringTest].TheString from stats with id 1
*/
ALTER DATABASE MyStringTestDB SET compatibility_level = 120;
GO
SELECT * 
FROM dbo.StringTest 
WHERE SUBSTRING(TheString, 1, CHARINDEX('_',TheString) - 1) = 'ZZ'
OPTION (QUERYTRACEON 2363, QUERYTRACEON 3604);

/*
-- Simpler rewrite; works fine in both compat levels and gets better estimate.
SELECT * 
FROM dbo.StringTest 
WHERE TheString LIKE 'ZZ[_]%'
OPTION (QUERYTRACEON 2363, QUERYTRACEON 3604);
*/
James L.
sumber
1
Tidak yakin tentang pertanyaan tertentu, tetapi jika Y5_EG3string hanya kode dan huruf besar, maka Anda selalu dapat mencoba menentukan susunan biner - Latin1_General_100_BIN2- yang seharusnya meningkatkan kecepatan pada operasi penyaringan. Cukup tambahkan COLLATE Latin1_General_100_BIN2ke CREATE TABLEpernyataan, tepat setelah varchar(15). Saya akan penasaran untuk melihat apakah itu juga mempengaruhi pembuatan / estimasi rencana.
Solomon Rutzky

Jawaban:

8

Saya tidak mengetahui adanya dokumentasi. Saya memang melihat ini dan melakukan beberapa pengamatan namun itu terlalu lama untuk komentar.

Estimasi 10% tidak selalu merupakan degradasi. Ambil contoh berikut.

TRUNCATE TABLE dbo.StringTest

INSERT INTO dbo.StringTest
SELECT TOP (1000000) 'ZZ_' + LEFT(NEWID(), 12)
FROM   master..spt_values v1,
       master..spt_values v2;

dan WHEREklausa dalam pertanyaan Anda.

WHERE SUBSTRING(TheString, 1, CHARINDEX('_',TheString) - 1) = 'ZZ'

Tabel berisi sejuta baris. Semuanya sesuai dengan predikat. Di bawah level compat 130, tebakan 10% menghasilkan perkiraan 100.000. Di bawah 120 baris yang diperkirakan adalah 1.03913.

Perilaku 120 menggunakan histogram tetapi hanya untuk mendapatkan jumlah baris yang berbeda. Vektor kepadatan dalam kasus saya menunjukkan 1.039131E-06 dan ini dikalikan dengan kardinalitas tabel untuk mendapatkan perkiraan jumlah baris. Semua nilai sebenarnya berbeda tetapi semua sesuai dengan predikat.

Menelusuri query_optimizer_estimate_cardinalityacara yang diperluas menunjukkan bahwa di bawah 130 ada dua <StatsCollection Name="CStCollFilter"peristiwa yang berbeda . Yang pertama memperkirakan 100.000. Yang kedua memuat histogram dan menggunakan CSelCalcPointPredsFreqBased / DistinctCountCalculator untuk mendapatkan perkiraan 1,04. Hasil kedua ini tampaknya tidak digunakan.

Perilaku yang Anda amati tidak secara konsisten diterapkan di 130. Saya menambahkan ORDER BY TheStringberharap ini menjadi kemenangan yang jelas bagi penaksir 130 karena 120 berjuang dengan pemberian memori untuk satu baris tetapi perubahan kecil ini cukup untuk membawa baris yang diperkirakan ke 1.03913 dalam 130 kasus juga.

Menambahkan OPTION (QUERYRULEOFF SelectToFilter)mengembalikan estimasi yang masuk ke dalam sortir menjadi 100.000 tetapi hibah memori tidak meningkat dan perkiraan yang keluar semacam itu masih berdasarkan tabel nilai yang berbeda.

masukkan deskripsi gambar di sini

Demikian pula mengubah ambang biaya untuk paralelisme sehingga permintaan mendapatkan rencana paralel cukup dalam kasus 130 untuk kembali ke perkiraan yang lebih rendah. Menambahkan QUERYTRACEON 8757juga menyebabkan perkiraan yang lebih rendah. Sepertinya perkiraan 10% hanya dipertahankan untuk rencana sepele.

Usulan penulisan ulang dengan

WHERE TheString LIKE 'ZZ[_]%'

Memperlihatkan jauh lebih banyak perkiraan untuk keduanya. Output untuk ini adalah

  CSelCalcTrieBased

      Column: QCOL: [MyStringTestDB].[dbo].[StringTest].TheString

Menunjukkan bahwa itu digunakan mencoba . Info lebih lanjut tentang ini ada di bagian statistik ringkasan string tepat di atas sini .

Namun tidak sama dengan permintaan awal Anda. Sebagai contoh pertama _sekarang diasumsikan selalu menjadi karakter ketiga daripada ditemukan secara dinamis.

Jika asumsi ini dimasukkan ke dalam kueri asli Anda

 WHERE SUBSTRING(TheString, 1, 3) = 'ZZ_'

Metode estimasi berubah menjadi CSelCalcHistogramComparison(INTERVAL)dan baris estimasi menjadi akurat.

Itu dapat mengkonversi itu menjadi rentang

WHERE TheString >=  'ZZ_' AND TheString < ???

dan gunakan histogram untuk memperkirakan jumlah baris dengan nilai dalam rentang itu.

Namun ini hanya berlaku untuk estimasi kardinalitas. LIKElebih disukai karena dapat menggunakan rentang pencarian saat runtime. SUBSTRING(TheString, 1, 3)atau LEFT(TheString, 3)tidak bisa.

Martin Smith
sumber