Mengapa mengubah perintah kolom gabungan yang dideklarasikan memperkenalkan semacam?

40

Saya memiliki dua tabel dengan kolom kunci yang dinamai identik, diketik, dan diindeks. Salah satunya memiliki indeks berkerumun unik , yang lain memiliki non-unik .

Pengaturan tes

Skrip penyiapan, termasuk beberapa statistik realistis:

DROP TABLE IF EXISTS #left;
DROP TABLE IF EXISTS #right;

CREATE TABLE #left (
    a       char(4) NOT NULL,
    b       char(2) NOT NULL,
    c       varchar(13) NOT NULL,
    d       bit NOT NULL,
    e       char(4) NOT NULL,
    f       char(25) NULL,
    g       char(25) NOT NULL,
    h       char(25) NULL
    --- and a few other columns
);

CREATE UNIQUE CLUSTERED INDEX IX ON #left (a, b, c, d, e, f, g, h)

UPDATE STATISTICS #left WITH ROWCOUNT=63800000, PAGECOUNT=186000;

CREATE TABLE #right (
    a       char(4) NOT NULL,
    b       char(2) NOT NULL,
    c       varchar(13) NOT NULL,
    d       bit NOT NULL,
    e       char(4) NOT NULL,
    f       char(25) NULL,
    g       char(25) NOT NULL,
    h       char(25) NULL
    --- and a few other columns
);

CREATE CLUSTERED INDEX IX ON #right (a, b, c, d, e, f, g, h)

UPDATE STATISTICS #right WITH ROWCOUNT=55700000, PAGECOUNT=128000;

Repro itu

Ketika saya bergabung dengan dua tabel ini pada kunci pengelompokan mereka, saya berharap satu-ke-banyak Gabung bergabung, seperti:

SELECT *
FROM #left AS l
LEFT JOIN #right AS r ON
    l.a=r.a AND
    l.b=r.b AND
    l.c=r.c AND
    l.d=r.d AND
    l.e=r.e AND
    l.f=r.f AND
    l.g=r.g AND
    l.h=r.h
WHERE l.a='2018';

Ini adalah paket permintaan yang saya inginkan:

Ini yang aku inginkan.

(Jangankan peringatan, itu ada hubungannya dengan statistik palsu.)

Namun, jika saya mengubah urutan kolom di dalam gabungan, seperti:

SELECT *
FROM #left AS l
LEFT JOIN #right AS r ON
    l.c=r.c AND     -- used to be third
    l.a=r.a AND     -- used to be first
    l.b=r.b AND     -- used to be second
    l.d=r.d AND
    l.e=r.e AND
    l.f=r.f AND
    l.g=r.g AND
    l.h=r.h
WHERE l.a='2018';

... ini terjadi:

Paket kueri setelah mengubah urutan kolom yang dinyatakan dalam gabungan.

Operator Sortir tampaknya memesan stream sesuai dengan urutan gabungan yang dinyatakan, yaitu c, a, b, d, e, f, g, h, yang menambahkan operasi pemblokiran ke rencana kueri saya.

Hal-hal yang telah saya lihat

  • Saya sudah mencoba mengubah kolom menjadi NOT NULL, hasil yang sama.
  • Tabel asli dibuat dengan ANSI_PADDING OFF, tetapi membuatnya dengan ANSI_PADDING ONtidak memengaruhi rencana ini.
  • Saya mencoba INNER JOINbukan LEFT JOIN, tidak ada perubahan.
  • Saya menemukannya di Perusahaan SP2 2014, membuat repro pada Pengembang 2017 (CU saat ini).
  • Menghapus klausa WHERE pada kolom indeks utama memang menghasilkan rencana yang baik, tetapi itu mempengaruhi hasil .. :)

Akhirnya, kita sampai pada pertanyaan

  • Apakah ini disengaja?
  • Dapatkah saya menghilangkan semacam itu tanpa mengubah kueri (yang merupakan kode vendor, jadi saya lebih suka tidak ...). Saya bisa mengubah tabel dan indeks.
Daniel Hutmacher
sumber

Jawaban:

28

Apakah ini disengaja?

Itu memang dari desain, ya. Sumber publik terbaik untuk pernyataan ini sayangnya hilang ketika Microsoft pensiun dari situs umpan balik Connect, melenyapkan banyak komentar yang berguna dari pengembang di tim SQL Server.

Bagaimanapun, desain pengoptimal saat ini tidak secara aktif mencari untuk menghindari jenis yang tidak perlu per se . Ini paling sering ditemui dengan fungsi windowing dan sejenisnya, tetapi juga dapat dilihat dengan operator lain yang sensitif terhadap pemesanan, dan khususnya untuk pemesanan yang diawetkan antara operator.

Namun demikian, pengoptimal cukup baik (dalam banyak kasus) menghindari penyortiran yang tidak perlu, tetapi hasil ini biasanya terjadi karena alasan selain secara agresif mencoba kombinasi pemesanan yang berbeda. Dalam hal itu, ini bukan masalah 'ruang pencarian' seperti halnya interaksi kompleks antara fitur-fitur pengoptimal ortogonal yang telah terbukti meningkatkan kualitas rencana umum dengan biaya yang dapat diterima.

Misalnya, penyortiran sering kali dapat dihindari hanya dengan mencocokkan persyaratan pemesanan (misalnya tingkat atas ORDER BY) dengan indeks yang ada. Sepele dalam kasus Anda itu bisa berarti menambahkan ORDER BY l.a, l.b, l.c, l.d, l.e, l.f, l.g, l.h;tetapi ini adalah penyederhanaan yang berlebihan (dan tidak dapat diterima karena Anda tidak ingin mengubah kueri).

Secara umum, setiap grup memo dapat dikaitkan dengan properti yang diperlukan atau diinginkan, yang dapat mencakup pemesanan input. Ketika tidak ada alasan yang jelas untuk menegakkan pesanan tertentu (misalnya untuk memuaskan ORDER BY, atau untuk memastikan hasil yang benar dari operator fisik yang peka terhadap pesanan), ada unsur 'keberuntungan' yang terlibat. Saya menulis lebih banyak tentang hal-hal spesifik yang berkaitan dengan menggabungkan bergabung (dalam mode gabungan atau bergabung) dalam Menghindari Macam dengan Penggabungan Gabung . Banyak yang melampaui area permukaan yang didukung produk, jadi perlakukan itu sebagai informasi, dan dapat berubah.

Dalam kasus khusus Anda, ya, Anda dapat menyesuaikan pengindeksan seperti jadarnel27 menyarankan untuk menghindari jenis; meskipun ada sedikit alasan untuk benar-benar lebih suka bergabung bergabung di sini. Anda juga bisa mengisyaratkan pilihan antara hash atau loop fisik bergabung dengan OPTION(HASH JOIN, LOOP JOIN)menggunakan Panduan Rencana tanpa mengubah kueri, tergantung pada pengetahuan Anda tentang data, dan trade-off antara kinerja terbaik, terburuk, dan kasus rata-rata.

Akhirnya, sebagai keingintahuan, perhatikan bahwa jenis dapat dihindari dengan sederhana ORDER BY l.b, dengan biaya menggabungkan banyak-ke-banyak berpotensi kurang efisien bergabung bsendirian, dengan residu yang kompleks. Saya menyebutkan ini sebagian besar sebagai ilustrasi interaksi antara fitur-fitur optimizer yang saya sebutkan sebelumnya, dan cara persyaratan tingkat atas dapat disebarkan.

Paul White mengatakan GoFundMonica
sumber
19

Dapatkah saya menghilangkan semacam itu tanpa mengubah kueri (yang merupakan kode vendor, jadi saya lebih suka tidak ...). Saya bisa mengubah tabel dan indeks.

Jika Anda dapat mengubah indeks, lalu mengubah urutan indeks #rightuntuk mencocokkan dengan urutan filter dalam gabungan menghapus jenis (untuk saya):

CREATE CLUSTERED INDEX IX ON #right (c, a, b, d, e, f, g, h)

Anehnya (bagi saya, setidaknya), ini tidak menghasilkan permintaan yang berakhir dengan pengurutan.

Apakah ini disengaja?

Melihat output dari beberapa flag trace yang aneh , ada perbedaan yang menarik dalam struktur Memo terakhir:

tangkapan layar dari struktur memo akhir untuk setiap permintaan

Seperti yang Anda lihat di "Grup Root" di bagian atas, kedua kueri memiliki opsi untuk menggunakan Gabung Gabung sebagai operasi fisik utama untuk menjalankan kueri ini.

Permintaan yang bagus

Gabung tanpa pengurutan didorong oleh grup 29 opsi 1 dan grup 31 opsi 1 (masing-masing merupakan pemindaian rentang pada indeks yang terlibat). Itu difilter oleh grup 27 (tidak ditampilkan), yang merupakan rangkaian operasi perbandingan logis yang menyaring gabungan.

Kueri salah

Yang dengan pengurutan didorong oleh opsi (baru) 3 yang dimiliki masing-masing dari kedua kelompok (29 dan 31). Opsi 3 melakukan pengurutan fisik pada hasil pemindaian rentang yang disebutkan sebelumnya (opsi 1 dari masing-masing kelompok).

Mengapa?

Untuk beberapa alasan, opsi untuk menggunakan 29.1 dan 31.1 secara langsung sebagai sumber untuk gabungan gabung bahkan tidak tersedia untuk pengoptimal dalam kueri kedua. Kalau tidak, saya pikir itu akan terdaftar di bawah grup root di antara opsi lain. Jika itu tersedia sama sekali, maka pasti akan memilih orang-orang di atas operasi semacam besar-besaran lebih mahal.

Saya hanya dapat menyimpulkan bahwa:

  • ini adalah bug (atau kemungkinan besar suatu batasan) dalam algoritma pencarian pengoptimal
    • mengubah indeks dan bergabung menjadi hanya memiliki 5 kunci menghapus semacam itu untuk permintaan kedua (6, 7, dan 8 kunci semua memiliki semacam itu).
    • Ini menyiratkan bahwa ruang pencarian dengan 8 tombol sangat besar sehingga pengoptimal tidak punya waktu untuk mengidentifikasi solusi non-sortir sebagai opsi yang layak sebelum diakhiri lebih awal dengan alasan "rencana yang cukup baik ditemukan"
    • Tampaknya memang sedikit membingungkan bagi saya bahwa urutan kondisi gabungan memengaruhi proses pencarian pengoptimal sebanyak ini, tetapi sebenarnya itu sedikit berlebihan.
  • penyortiran diperlukan untuk memastikan kebenaran dalam hasil
    • yang ini sepertinya tidak mungkin, karena kueri dapat berjalan tanpa pengurutan ketika ada lebih sedikit kunci, atau kunci ditentukan dalam urutan yang berbeda

Semoga seseorang dapat datang dan menjelaskan mengapa hal itu diperlukan, tetapi saya pikir perbedaan dalam bangunan Memo cukup menarik untuk dikirim sebagai jawaban.

Josh Darnell
sumber
1
Saya percaya komentar Anda tentang ruang pencarian sebenarnya terjadi di sini. untuk menggunakan hanya indeks, pengoptimal harus memverifikasi mereka cukup untuk kondisi, melewati 5 kunci ada terlalu banyak kemungkinan untuk memeriksa sebelum harus mundur. Saya ingin tahu, jika semua kombinasi urutan kueri dihitung, berapa banyak pengoptimal yang akan berhasil vs jatuh kembali
Mr.Mindor
Dan ya ketidakkonsistenan memang terlihat sedikit bermasalah, tetapi mungkin sepenuhnya tergantung pada algoritma yang digunakan untuk memverifikasi indeks sudah cukup. Jika semua kombinasi diuji, Anda mungkin dapat melihat pola dalam hasil dan menentukan algoritma apa yang digunakan. Saya berani bertaruh itu ditulis untuk melakukan secara optimal untuk kasus penggunaan yang lebih khas. Sebuah alternatif mungkin ada yang akan dapat menemukan solusi 8 kunci secara andal dalam batas waktu, tetapi lebih lambat dari solusi saat ini ketika ada kurang dari katakanlah 3-4 kunci.
Mr.Mindor