Bagaimana perkiraan baris dapat ditingkatkan untuk mengurangi peluang tumpahan ke tempdb

11

Saya perhatikan bahwa ketika ada tumpahan ke acara tempdb (menyebabkan kueri lambat) yang sering kali perkiraan baris jauh dari gabungan tertentu. Saya telah melihat peristiwa tumpahan terjadi dengan menggabungkan dan hash bergabung dan mereka sering meningkatkan runtime 3x menjadi 10x. Pertanyaan ini menyangkut bagaimana meningkatkan perkiraan baris dengan asumsi bahwa hal itu akan mengurangi kemungkinan peristiwa tumpahan.

Jumlah baris sebenarnya 40k.

Untuk kueri ini, paket menampilkan taksiran baris buruk (11,3 baris):

select Value
  from Oav.ValueArray
 where ObjectId = (select convert(bigint, Value) NodeId
                     from Oav.ValueArray
                    where PropertyId = 3331  
                      and ObjectId = 3540233
                      and Sequence = 2)
   and PropertyId = 2840
option (recompile);

Untuk kueri ini, paket menampilkan estimasi baris yang bagus (baris 56rb):

declare @a bigint = (select convert(bigint, Value) NodeId
                       from Oav.ValueArray
                      where PropertyId = 3331
                        and ObjectId = 3540233
                        and Sequence = 2);

select Value
  from Oav.ValueArray
 where ObjectId = @a               
   and PropertyId = 2840
option (recompile);

Dapatkah statistik atau petunjuk ditambahkan untuk meningkatkan perkiraan baris untuk kasus pertama? Saya mencoba menambahkan statistik dengan nilai filter tertentu (properti = 2840) tetapi tidak bisa mendapatkan kombinasi yang benar atau mungkin diabaikan karena ObjectId tidak diketahui pada waktu kompilasi dan mungkin memilih rata-rata di atas semua ObjectIds.

Apakah ada mode di mana ia akan melakukan penyelidikan terlebih dahulu dan kemudian menggunakannya untuk menentukan estimasi baris atau haruskah terbang secara membabi buta?

Properti khusus ini memiliki banyak nilai (40k) pada beberapa objek dan nol pada sebagian besar. Saya akan senang dengan petunjuk di mana jumlah baris maksimum yang diharapkan untuk suatu join dapat ditentukan. Ini adalah masalah yang umumnya menghantui karena beberapa parameter dapat ditentukan secara dinamis sebagai bagian dari gabungan atau akan lebih baik ditempatkan dalam tampilan (tidak ada dukungan untuk variabel).

Apakah ada parameter yang dapat disesuaikan untuk meminimalkan kemungkinan tumpahan ke tempdb (mis. Memori min per kueri)? Rencana yang kuat tidak berpengaruh pada estimasi.

Edit 2013.11.06 : Tanggapan terhadap komentar dan informasi tambahan:

Berikut adalah gambar rencana kueri. Peringatannya adalah tentang kardinalitas / mencari predikat dengan orang yang dipertobatkan ():

masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini

Per komentar @Aaron Bertrand, saya mencoba mengganti convert () sebagai tes:

create table Oav.SeekObject (
       LookupId bigint not null primary key,
       ObjectId bigint not null
);

insert into Oav.SeekObject (
   LookupId, ObjectId
) VALUES (
   1, 3540233
) 

select Value
  from Oav.ValueArray
 where ObjectId = (select ObjectId 
                     from Oav.SeekObject 
                    where LookupId = 1)
   and PropertyId = 2840
option (recompile);

masukkan deskripsi gambar di sini

Sebagai tempat tujuan yang aneh namun berhasil, juga memungkinkannya untuk menyingkat pencarian:

select Value
  from Oav.ValueArray
 where ObjectId = (select ObjectId 
                     from Oav.ValueArray
                    where PropertyId = 2840
                      and ObjectId = 3540233
                      and Sequence = 2)
   and PropertyId = 2840
option (recompile);

masukkan deskripsi gambar di sini

Kedua daftar ini pencarian kunci yang tepat tetapi hanya yang pertama daftar "Output" dari ObjectId. Saya kira itu menunjukkan yang kedua memang korsleting?

Dapatkah seseorang memverifikasi apakah penyelidikan baris tunggal pernah dilakukan untuk membantu dengan perkiraan baris? Tampaknya salah untuk membatasi pengoptimalan hanya perkiraan histogram ketika pencarian PK baris tunggal dapat sangat meningkatkan akurasi pencarian ke dalam histogram (terutama jika ada potensi tumpahan atau riwayat). Ketika ada 10 sub-gabungan ini dalam kueri nyata, idealnya mereka akan terjadi secara paralel.

Catatan tambahan, karena sql_variant menyimpan tipe dasarnya (SQL_VARIANT_PROPERTY = BaseType) di dalam isian itu sendiri, saya akan mengharapkan konversi () hampir tanpa biaya asalkan konversi "langsung" (mis. Bukan string ke desimal tetapi bukan int ke int atau mungkin int ke bigint). Karena itu tidak diketahui pada waktu kompilasi tetapi dapat diketahui oleh pengguna, mungkin fungsi "AssumeType (type, ...)" untuk sql_variants akan memungkinkan mereka diperlakukan lebih transparan.

crokusek
sumber
1
Dugaan pertama adalah bahwa konversi ke bigint membuang perkiraan Anda (rencana kueri akan memiliki peringatan tentang itu di SQL Server 2012) tetapi di sisi lain, sub-kueri Anda tidak akan pernah bisa mengembalikan apa pun selain dari 0 atau 1 baris untuk kueri yang berhasil. Akan menarik untuk melihat paket permintaan yang Anda miliki. Mungkin sebagai tautan ke versi XML.
Mikael Eriksson
2
Apa yang Anda dapatkan dengan memiliki inline subquery? Saya sarankan menariknya secara terpisah lebih jelas secara keseluruhan, dan karena itu mengarah pada perkiraan yang lebih baik, mengapa tidak menggunakan metode itu saja?
Aaron Bertrand
2
Apa versi SQL Server? Bisakah Anda memberikan tabel & indeks DDL dan gumpalan statistik (tunggal dan multi-kolom) untuk tabel sehingga kami dapat melihat detail masalahnya? Memisahkan penggunaan kueri declare @a bigint = seperti yang Anda lakukan tampaknya merupakan solusi alami bagi saya, mengapa itu tidak dapat diterima?
Paul White 9
2
Saya kira desain Anda adalah desain EAV (sangat sederhana) yang memaksa Anda untuk menggunakan CONVERT()kolom dan kemudian bergabung dengan mereka. Ini tentu tidak efisien dalam banyak kasus. Dalam yang khusus ini, hanya satu nilai yang akan dikonversi jadi itu mungkin bukan masalah tetapi indeks apa yang Anda miliki di atas meja? Desain EAV biasanya berkinerja baik, hanya dengan pengindeksan yang tepat (yang berarti banyak indeks dalam tabel yang biasanya sempit).
ypercubeᵀᴹ
@ Paul White, sejauh berpisah ... tidak apa-apa sebagai solusi untuk kasus ini. Tetapi untuk yang lebih umum / kompleks saya kebanyakan tidak ingin menyerah paralelisasi dan mudah dibaca. Katakanlah saya memiliki 10 di antaranya sebagai sub kueri (beberapa lebih kompleks) dalam kueri tetapi hanya 5 yang perlu "matang" sebelum kueri lainnya dapat dimulai - ingin menghindari keharusan mencari tahu 5 yang mana.
crokusek

Jawaban:

7

Saya tidak akan berkomentar tentang tumpahan, tempdb, atau petunjuk karena permintaan tampaknya cukup sederhana untuk memerlukan banyak pertimbangan. Saya pikir pengoptimal SQL-Server akan melakukan tugasnya dengan cukup baik, jika ada indeks yang cocok untuk kueri.

Dan pemisahan Anda menjadi dua pertanyaan bagus karena menunjukkan indeks apa yang akan berguna. Bagian pertama:

(select convert(bigint, Value) NodeId
 from Oav.ValueArray
 where PropertyId = 3331  
   and ObjectId = 3540233
   and Sequence = 2)

membutuhkan indeks pada (PropertyId, ObjectId, Sequence)termasuk Value. Saya akan membuatnya UNIQUEaman. Kueri akan memunculkan kesalahan selama runtime jika lebih dari satu baris dikembalikan, jadi ada baiknya memastikan terlebih dahulu bahwa ini tidak akan terjadi, dengan indeks unik:

CREATE UNIQUE INDEX
    PropertyId_ObjectId_Sequence_UQ
  ON Oav.ValueArray
    (PropertyId, ObjectId, Sequence) INCLUDE (Value) ;

Bagian kedua dari permintaan:

select Value
  from Oav.ValueArray
 where ObjectId = @a               
   and PropertyId = 2840

membutuhkan indeks untuk (PropertyId, ObjectId)menyertakan Value:

CREATE INDEX
    PropertyId_ObjectId_IX
  ON Oav.ValueArray
    (PropertyId, ObjectId) INCLUDE (Value) ;

Jika efisiensi tidak ditingkatkan atau indeks ini tidak digunakan atau masih ada perbedaan dalam perkiraan baris yang muncul, maka akan perlu untuk melihat lebih jauh ke dalam permintaan ini.

Dalam hal ini, konversi (diperlukan dari desain EAV dan penyimpanan tipe data yang berbeda di kolom yang sama) adalah kemungkinan penyebab dan solusi pemecahan Anda (seperti @AAron Bertrand dan @Paul White komentar) permintaan menjadi dua bagian tampak alami dan cara untuk pergi. Desain ulang sehingga memiliki tipe data yang berbeda di kolomnya masing-masing mungkin berbeda.

ypercubeᵀᴹ
sumber
Tabel memiliki indeks yang meliputi - saya seharusnya menyatakan itu dalam pertanyaan. Contohnya adalah benar-benar sub bergabung memberi makan permintaan yang lebih besar yang mengapa ada semua keributan tentang tumpahan tempdb.
crokusek
5

Sebagai jawaban parsial untuk pertanyaan eksplisit tentang peningkatan statistik ...

Perhatikan bahwa estimasi baris bahkan untuk kasus yang dipisah secara terpisah masih mati sebesar 10X (4k vs yang diharapkan 40k).

Histogram statistik kemungkinan menyebar terlalu tipis untuk properti itu karena tabel baris panjang (vertikal), 3,5M, dan properti khusus itu sangat jarang.

Buat statistik tambahan (agak berlebihan dengan statistik IX) untuk properti jarang:

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840

Yang asli:

masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini

Dengan konversi () dihapus (layak):

masukkan deskripsi gambar di sini

Dengan konversi () dihapus (ciruit pendek):

masukkan deskripsi gambar di sini

Masih mati ~ 2X kemungkinan karena> 99,9% dari objek tidak memiliki Properti 2840 yang didefinisikan pada mereka sama sekali. Bahkan hanya untuk kasus uji ini properti ada hanya pada 1 dari 200k Objek berbeda dari tabel baris 3.5M. Sungguh menakjubkan, itu bisa sedekat itu. Menyesuaikan filter menjadi ObjectIds lebih sedikit,

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 and ObjectId >= 3540000 and ObjectId < 3541000

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 and ObjectId = 3540233;

Hmm, tidak ada perubahan ... Didukung bahwa ditambahkan "dengan pemindaian penuh" ke akhir statistik (mungkin mengapa dua sebelumnya tidak bekerja) dan ya:

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 with full scan;

masukkan deskripsi gambar di sini

Yay. Jadi dalam tabel yang sangat vertikal dengan IX yang luas, menambahkan statistik yang difilter tambahan tampaknya merupakan peningkatan besar (terutama untuk kombinasi kunci yang jarang tetapi sangat bervariasi).

crokusek
sumber
Tautan ke beberapa masalah bermasalah dengan statistik multi-kolom.
crokusek