Kepadatan yang aneh menghasilkan statistik sampel

8

NC-index mendapat distribusi statistik yang sama sekali berbeda ketika diestimasi dengan sampling vs fullscan; sampel memiliki vektor kerapatan yang aneh. Ini menghasilkan rencana eksekusi yang buruk.


Saya memiliki tabel ~ 27 juta baris, dengan kolom FK tidak nol yang didukung oleh indeks yang tidak dikelompokkan. Tabel dikelompokkan pada kunci utama. Kedua kolom adalah varchar.

Pembaruan statistik fullscan untuk kolom FK kami memberikan vektor kepadatan tampak normal:

All density Average Length  Columns
6,181983E-08    45,99747    INSTANCEELEMENTID
3,615442E-08    95,26874    INSTANCEELEMENTID, ID

Artinya, kita diharapkan membaca sekitar 1,7 baris untuk setiap perbedaan yang INSTANCELEMENTIDkita ikuti.

Tempat sampah khas dari histogram terlihat seperti ini:

RANGE_HI_KEY    RANGE_ROWS  EQ_ROWS DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
FOOBAR          133053      10      71366               1,679318

Namun, jika kami melakukan pembaruan sampel (menggunakan nomor sampel default yaitu 230r baris untuk tabel ini) hal-hal berubah menjadi aneh:

4,773657E-06    45,99596    INSTANCEELEMENTID
3,702179E-08    95,30183    INSTANCEELEMENTID, ID

Kepadatan pada INSTANCEELEMENTIDsekarang dua urutan besarnya lebih besar. (Namun kepadatan untuk kedua kolom telah diperkirakan ke nilai yang cukup dapat diterima).

Tempat sampah khas dari histogram sekarang terlihat seperti ini;

RANGE_HI_KEY    RANGE_ROWS  EQ_ROWS     DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
FOOBAR          143870,4    766,2573    1247                115,3596
ZOTZOT          131560,7    1           969                 135,7092

yang merupakan distribusi yang sama sekali berbeda. Perhatikan bahwa INSTANCEELEMENTIDdengan jumlah tertinggi dari yang terkait IDmemiliki 12, jumlah yang paling umum adalah 1. Ini juga sangat aneh bahwa beberapa tempat sampah mendapatkan EQ_ROWS = 1, ini terjadi pada sekitar 10% dari tempat sampah.

Tidak ada gambar "sial" dari baris-baris aneh yang dapat menyebabkan hal ini.

Apakah saya membaca histogram dengan benar? Tidakkah itu terlihat seperti sampel yang entah bagaimana telah menskala EQ_ROWS, DISTINCT_RANGE_ROWS, dan AVG_RANGE_ROWS salah?

Tabelnya, sejauh yang saya tahu, tidak dikenal. Saya sudah mencoba untuk meniru sampler dengan memperkirakan sendiri nilai dengan tablesample. Menghitung hasil ini dengan cara normal memberikan hasil yang sesuai dengan versi fullscan, bukan sampler.

Selain itu, saya tidak dapat mereproduksi perilaku ini pada indeks berkerumun.


Saya mempersempit ini menjadi ini untuk mereproduksi:

CREATE TABLE F_VAL (
    id varchar(100) primary key,
    num_l_val int not null
)

set nocount on

declare @rowlimit integer = 20000000;

Tabel harus cukup besar agar dapat diamati. Aku pernah melihat ini dengan uniqueidentiferdan varchar(100)tapi tidak int.

declare @i integer = 1;

declare @r float = rand()

while @i < @rowlimit
begin
set @r = rand()
insert f_val (id,num_l_val)
values (
   cast(@i as varchar(100)) + REPLICATE('f', 40 - len(@i)),
   case when @r > 0.8 then 4 when @r > 0.5 then 3 when @r > 0.4 then 2 else 1 end
)
  set @i = @i + 1

end

create table k_val (
 id int identity primary key,
 f_val varchar(100) not null,
)

insert into k_val(f_val)
select id from F_VAL
union all select id from f_val where num_l_val - 1 = 1
union all select id from f_val where num_l_val - 2 = 1
union all select id from f_val where num_l_val - 3 = 1
order by id

create nonclustered index IX_K_VAL_F_VAL  ON K_VAL (F_VAL)

update statistics K_VAL(IX_K_VAL_F_VAL) 
dbcc show_statistics (k_val,IX_k_VAL_F_VAL)

update statistics K_VAL(IX_K_VAL_F_VAL) WITH FULLSCAN
dbcc show_statistics (k_val,IX_k_VAL_F_VAL)

Bandingkan dua statistik; satu dengan sampling sekarang mewakili total vektor kepadatan yang berbeda dan tempat histogram dimatikan. Perhatikan bahwa tabel tidak miring.

Menggunakan intdatatype tidak menyebabkan hal ini, apakah SQL Server tidak memeriksa seluruh titik data saat menggunakan varchar?

Penting untuk menyebutkan bahwa masalah tampaknya berskala, meningkatkan laju sampel membantu.

Paul White 9
sumber

Jawaban:

3

Saya telah melihat masalah kerapatan yang sama pada beberapa indeks nonclustered pada database terbesar yang saya miliki aksesnya. Pertama saya akan mulai dengan beberapa pengamatan yang telah saya lakukan tentang histogram dan perhitungan kepadatan:

  • SQL Server dapat menggunakan kunci utama di atas meja untuk menyimpulkan sesuatu tentang kepadatan kedua kolom. Ini berarti bahwa kepadatan yang mencakup kolom PK biasanya akan sangat akurat.
  • Perhitungan kepadatan untuk kolom pertama dalam statistik konsisten dengan histogram. Jika histogram tidak memodelkan data dengan baik maka kerapatan mungkin tidak aktif.
  • Untuk membuat histogram, StatManfungsi membuat kesimpulan tentang data yang hilang. Perilaku dapat berubah tergantung pada tipe data kolom.

Untuk satu cara untuk melihat masalah, anggaplah Anda sampel 100 baris dari tabel 10.000 baris dan Anda mendapatkan 100 nilai yang berbeda. Satu tebakan pada apa sisa data dalam tabel adalah bahwa ada 10.000 nilai unik. Dugaan lain adalah bahwa ada 100 nilai yang berbeda tetapi masing-masing nilai diulang 100 kali. Tebakan kedua mungkin tampak tidak masuk akal bagi Anda, yang akan saya setujui. Namun, bagaimana Anda menyeimbangkan kedua pendekatan ketika data sampel kembali tidak terdistribusi secara merata? Ada beberapa set algoritma yang dikembangkan untuk ini oleh Microsoft yang terkandung dalam StatManfungsi. Algoritme mungkin tidak berfungsi untuk semua gangguan data dan semua level sampel.

Mari kita lihat contoh yang relatif sederhana. Saya akan menggunakan VARCHARkolom seperti di meja Anda untuk melihat beberapa perilaku yang sama. Namun, saya hanya akan menambahkan satu nilai miring ke tabel. Saya sedang menguji terhadap SQL Server 2016 SP1. Mulai dengan 100rb baris dengan 100rb nilai unik untuk FKkolom:

DROP TABLE IF EXISTS X_STATS_SMALL;

CREATE TABLE X_STATS_SMALL (
ID VARCHAR(10) NOT NULL, 
FK VARCHAR(10) NOT NULL,
PADDING VARCHAR(900) NOT NULL,
PRIMARY KEY (ID)
);
-- insert 100k rows
INSERT INTO X_STATS_SMALL WITH (TABLOCK)
SELECT N, N, REPLICATE('Z', 900)
FROM dbo.GetNums(100000);

CREATE INDEX IX_X_STATS_SMALL ON X_STATS_SMALL (FK);

-- get sampled stats
UPDATE STATISTICS X_STATS_SMALL IX_X_STATS_SMALL;

Berikut ini beberapa contoh dari statistik:

╔═════════════╦════════════════╦═════════╗
 All density  Average Length  Columns 
╠═════════════╬════════════════╬═════════╣
 1.00001E-05  4.888205        FK      
 1.00001E-05  9.77641         FK, ID  
╚═════════════╩════════════════╩═════════╝

╔══════════════╦════════════╦═════════╦═════════════════════╦════════════════╗
 RANGE_HI_KEY  RANGE_ROWS  EQ_ROWS  DISTINCT_RANGE_ROWS  AVG_RANGE_ROWS 
╠══════════════╬════════════╬═════════╬═════════════════════╬════════════════╣
 1005          0           1        0                    1              
 10648         665.0898    1        664                  1.002173       
 10968         431.6008    1        432                  1              
 11182         290.0924    1        290                  1              
 1207          445.7517    1        446                  1              
 ...           ...         ...      ...                  ...            
 99989         318.3941    1        318                  1              
╚══════════════╩════════════╩═════════╩═════════════════════╩════════════════╝

Untuk data yang terdistribusi secara merata dengan satu nilai unik per baris, kami mendapatkan kerapatan yang akurat, bahkan dengan VARCHARkolom histogram dan ukuran sampel 14294 baris.

Sekarang mari kita tambahkan nilai miring dan perbarui statistik lagi:

-- add 70k rows with a FK value of '35000'
INSERT INTO X_STATS_SMALL WITH (TABLOCK)
SELECT N + 100000 , '35000',  REPLICATE('Z', 900)
FROM dbo.GetNums(70000);

UPDATE STATISTICS X_STATS_SMALL IX_X_STATS_SMALL;

Dengan ukuran sampel 17010 baris, kepadatan kolom pertama lebih kecil dari seharusnya:

╔══════════════╦════════════════╦═════════╗
 All density   Average Length  Columns 
╠══════════════╬════════════════╬═════════╣
 6.811061E-05  4.935802        FK      
 5.882353E-06  10.28007        FK, ID  
╚══════════════╩════════════════╩═════════╝

╔══════════════╦════════════╦══════════╦═════════════════════╦════════════════╗
 RANGE_HI_KEY  RANGE_ROWS  EQ_ROWS   DISTINCT_RANGE_ROWS  AVG_RANGE_ROWS 
╠══════════════╬════════════╬══════════╬═════════════════════╬════════════════╣
 10039         0           1         0                    1              
 10978         956.9945    1         138                  6.954391       
 11472         621.0283    1         89                   6.941863       
 1179          315.6046    1         46                   6.907561       
 11909         91.62713    1         14                   6.74198        
 ...           ...         ...       ...                  ...            
 35000         376.6893    69195.05  54                   6.918834       
 ...           ...         ...       ...                  ...            
 99966         325.7854    1         47                   6.909731       
╚══════════════╩════════════╩══════════╩═════════════════════╩════════════════╝

Mengejutkan bahwa AVG_RANGE_ROWSseragam ini cukup seragam untuk semua langkah di sekitar 6,9, bahkan untuk ember kunci yang sampelnya tidak dapat menemukan nilai duplikat. Saya tidak tahu mengapa ini terjadi. Penjelasan yang paling mungkin adalah bahwa algoritma yang digunakan untuk menebak halaman yang hilang tidak bekerja dengan baik dengan distribusi data dan ukuran sampel ini.

Seperti yang dinyatakan sebelumnya, adalah mungkin untuk menghitung kepadatan untuk kolom FK menggunakan histogram. Jumlah DISTINCT_RANGE_ROWSnilai untuk semua langkah adalah 14497. Ada 179 langkah histogram sehingga kepadatannya harus sekitar 1 / (179 + 14497) = 0,00006813845 yang cukup dekat dengan nilai yang dilaporkan.

Pengujian dengan tabel yang lebih besar dapat menunjukkan bagaimana masalah bisa menjadi lebih buruk karena tabel semakin besar. Kali ini kita akan mulai dengan 1 baris M:

DROP TABLE IF EXISTS X_STATS_LARGE;

CREATE TABLE X_STATS_LARGE (
ID VARCHAR(10) NOT NULL,
FK VARCHAR(10) NOT NULL,
PADDING VARCHAR(900) NOT NULL,
PRIMARY KEY (ID));

INSERT INTO X_STATS_LARGE WITH (TABLOCK)
SELECT N, N, REPLICATE('Z', 900)
FROM dbo.Getnums(1000000);

CREATE INDEX IX_X_STATS_LARGE ON X_STATS_LARGE (FK);

-- get sampled stats
UPDATE STATISTICS X_STATS_LARGE IX_X_STATS_LARGE;

Objek statistik belum menarik. Densitas untuk FKadalah 1.025289E-06 yang mendekati tepat (1.0E-06).

Sekarang mari kita tambahkan nilai miring dan perbarui statistik lagi:

INSERT INTO X_STATS_LARGE WITH (TABLOCK)
SELECT N + 1000000 , '350000',  REPLICATE('Z', 900)
FROM dbo.Getnums(700000);

UPDATE STATISTICS X_STATS_LARGE IX_X_STATS_LARGE;

Dengan ukuran sampel 45627 baris, kepadatan kolom pertama lebih buruk daripada sebelumnya:

╔══════════════╦════════════════╦═════════╗
 All density   Average Length  Columns 
╠══════════════╬════════════════╬═════════╣
 2.60051E-05   5.93563         FK      
 5.932542E-07  12.28485        FK, ID  
╚══════════════╩════════════════╩═════════╝

╔══════════════╦════════════╦═════════╦═════════════════════╦════════════════╗
 RANGE_HI_KEY  RANGE_ROWS  EQ_ROWS  DISTINCT_RANGE_ROWS  AVG_RANGE_ROWS 
╠══════════════╬════════════╬═════════╬═════════════════════╬════════════════╣
 100023        0           1        0                    1              
 107142        8008.354    1        306                  26.17787       
 110529        4361.357    1        168                  26.02392       
 114558        3722.193    1        143                  26.01217       
 116696        2556.658    1        98                   25.97568       
 ...           ...         ...      ...                  ...            
 350000        5000.522    700435   192                  26.03268       
 ...           ...         ...      ...                  ...            
 999956        2406.266    1        93                   25.96841       
╚══════════════╩════════════╩═════════╩═════════════════════╩════════════════╝

AVG_RANGE_ROWShingga 26. Menariknya, jika saya mengubah ukuran sampel ke 170100 baris (10X tabel lainnya) maka nilai rata-rata untuk AVG_RANGE_ROWSlagi tepat di sekitar 6,9. Ketika tabel Anda bertambah besar, SQL Server akan memilih ukuran sampel yang lebih kecil yang berarti perlu membuat perkiraan tentang persentase halaman yang lebih besar dalam tabel. Ini dapat membesar-besarkan masalah statistik untuk jenis data tertentu yang condong.

Kesimpulannya, penting untuk diingat bahwa SQL Server tidak menghitung kepadatan seperti ini:

SELECT COUNT(DISTINCT FK) * 1700000. / COUNT(*) -- 1071198.9 distinct values for one run
FROM X_STATS_LARGE TABLESAMPLE (45627 ROWS);

Yang untuk beberapa distribusi data akan sangat akurat. Sebaliknya ia menggunakan algoritma tidak berdokumen . Dalam pertanyaan Anda, Anda mengatakan bahwa data Anda tidak condong, tetapi INSTANCEELEMENTIDnilai dengan jumlah tertinggi ID terkait memiliki 12 dan angka paling umum adalah 1. Untuk keperluan algoritma yang digunakan oleh Statmanyang dapat miring.

Pada saat itu tidak ada yang dapat Anda lakukan kecuali mengumpulkan statistik dengan laju sampel yang lebih tinggi. Salah satu strategi umum adalah untuk mengumpulkan statistik dengan FULLSCANdan NORECOMPUTE. Anda dapat menyegarkan statistik dengan pekerjaan pada interval apa pun yang masuk akal untuk tingkat perubahan data Anda. Dalam pengalaman saya, FULLSCANpembaruan tidak seburuk yang dipikirkan kebanyakan orang, terutama terhadap indeks. SQL Server hanya dapat memindai seluruh indeks, bukan seluruh tabel (seperti yang akan dilakukan untuk tabel rowstore terhadap kolom yang tidak diindeks). Selain itu, di SQL Serer 2014 hanya FULLSCANpembaruan statistik yang dilakukan secara paralel, sehingga FULLSCANpembaruan dapat selesai lebih cepat daripada beberapa pembaruan sampel.

Joe Obbish
sumber
Terima kasih atas jawabannya, Joe! Ini terlihat seperti celah bug atau fitur; ingat perilaku ini tidak terjadi ketika Anda menggunakan nilai berbasis INT. Pada INT sistem bekerja jauh lebih baik, dan Anda mendapatkan estimasi distribusi statistik yang mendekati distribusi nyata jauh lebih baik. Sementara StatMan jelas melakukan beberapa smoothing / heuristik; Saya akan mengatakan itu cukup membingungkan bahwa Anda bisa mendapatkan hasil yang jauh lebih baik sendiri dengan menghitung histogram secara langsung, masih menggunakan sumber data yang sama dengan yang akan diperoleh dengantablesample
@JohanBenumEvensberget IMO bukan tidak masuk akal untuk berperilaku berbeda untuk kolom INT. Dengan INT, Anda memiliki domain yang jauh lebih terbatas untuk nilai yang hilang. Untuk string itu benar-benar bisa apa saja hingga batas panjang. Ini bisa membingungkan ketika kita tidak mendapatkan histogram yang baik tetapi itu berfungsi dengan baik sebagian besar waktu. Karena kode itu rahasia, kami tidak dapat memastikan apakah itu berfungsi seperti yang diharapkan atau tidak. Anda dapat mempertimbangkan untuk membuat posting di sini jika Anda merasa bahwa masalah ini harus diatasi oleh MS: connect.microsoft.com/SQLServer/Feedback
Joe Obbish