Oracle tidak menggunakan indeks unik untuk kunci yang panjang

16

Saya memiliki tabel dengan 250 ribu baris di basis data pengujian saya. (Ada beberapa ratus juta dalam produksi, kita dapat mengamati masalah yang sama di sana.) Tabel memiliki pengidentifikasi string nvarchar2 (50), bukan nol, dengan indeks unik di atasnya (bukan PK).

Pengidentifikasi terdiri dari bagian pertama yang memiliki 8 nilai berbeda di basis data pengujian saya (dan sekitar seribu dalam produksi), kemudian tanda @, dan akhirnya angka, 1 hingga 6 digit. Misalnya mungkin ada 50 ribu baris yang dimulai dengan 'ABCD_BGX1741F_2006_13_20110808.xml @', dan diikuti oleh 50 ribu angka yang berbeda.

Ketika saya meminta satu baris berdasarkan pengenalnya, kardinalitas diperkirakan 1, biayanya sangat rendah, itu berfungsi dengan baik. Ketika saya meminta lebih dari satu baris dengan beberapa pengidentifikasi dalam ekspresi IN atau ekspresi OR, estimasi untuk indeks benar-benar salah, sehingga pemindaian tabel penuh digunakan. Jika saya memaksakan indeks dengan petunjuk, itu sangat cepat, pemindaian tabel penuh sebenarnya dieksekusi urutan besarnya lebih lambat (dan lebih banyak lebih lambat dalam produksi). Jadi ini adalah masalah pengoptimal.

Sebagai tes, saya menduplikasi tabel (dalam skema yang sama + tablespace) dengan DDL yang sama persis dan konten yang sama persis. Saya menciptakan kembali indeks unik di tabel pertama untuk ukuran yang baik, dan membuat indeks yang sama persis di tabel klon. Saya melakukan DBMS_STATS.GATHER_SCHEMA_STATS('schemaname',estimate_percent=>100,cascade=>true);. Anda bahkan dapat melihat bahwa nama indeks berurutan. Jadi sekarang satu-satunya perbedaan antara dua tabel adalah bahwa yang pertama dimuat secara acak selama periode waktu yang lama, dengan blok yang tersebar di disk (dalam tablespace bersama dengan beberapa tabel besar lainnya), yang kedua dimuat sebagai satu batch INSERT-SELECT. Selain itu, saya tidak bisa membayangkan perbedaan apa pun. (Tabel asli telah menyusut sejak penghapusan besar terakhir, dan tidak ada satu penghapusan pun setelah itu.)

Berikut adalah rencana permintaan untuk orang sakit dan tabel klon (String di bawah kuas hitam sama di seluruh gambar, dan juga di bawah kuas abu-abu.):

rencana permintaan

(Dalam contoh ini, ada 1867 baris yang dimulai dengan pengenal yang disikat hitam. Kueri 2-baris menghasilkan kardinalitas 1867 * 2, kueri 3-baris menghasilkan kardinalitas 1867 * 3, dll. Tidak dapat secara kebetulan, Oracle tampaknya tidak peduli dengan akhir dari pengidentifikasi.)

Apa yang bisa menyebabkan perilaku ini? Jelas akan sangat mahal untuk membuat ulang tabel dalam produksi.

USER_TABLES: http://i.stack.imgur.com/nDWze.jpg USER_INDEXES: http://i.stack.imgur.com/DG9um.jpg Saya hanya mengubah nama skema dan tablespace. Anda dapat melihat bahwa tabel dan nama indeks sama seperti pada tangkapan layar rencana kueri.

fejesjoco
sumber

Jawaban:

7

(Ini menjawab pertanyaan lain tentang mengapa histogram berbeda.)

Histogram dibuat secara default berdasarkan kemiringan kolom dan apakah kolom itu digunakan dalam predikat yang relevan. Menyalin DDL dan datanya tidak cukup, informasi beban kerja juga penting.

Menurut Panduan Penyesuaian Kinerja :

Saat Anda menjatuhkan tabel, informasi beban kerja yang digunakan oleh fitur pengumpulan histogram otomatis dan riwayat statistik tersimpan yang digunakan oleh RESTORE _ * _ prosedur STATS hilang. Tanpa data ini, fitur-fitur ini tidak berfungsi dengan baik.

Misalnya, berikut adalah tabel dengan data yang miring tetapi tidak ada histogram:

drop table test1;
create table test1(a date);
insert into test1 select date '2000-01-01'+level from dual connect by level <= 10;
insert into test1 select date '2000-01-01' from dual connect by level <= 1000;
begin
    dbms_stats.gather_table_stats(user, 'TEST1');
end;
/
select histogram from user_tab_columns where table_name = 'TEST1';

HISTOGRAM
---------
NONE

Menjalankan hal yang sama, tetapi dengan kueri sebelum statistik dikumpulkan, akan menghasilkan histogram.

drop table test1;
create table test1(a date);
insert into test1 select date '2000-01-01'+level from dual connect by level <= 10;
insert into test1 select date '2000-01-01' from dual connect by level <= 1000;
select count(*) from test1 where a = sysdate; --Only new line
begin
    dbms_stats.gather_table_stats(user, 'TEST1');
end;
/
select histogram from user_tab_columns where table_name = 'TEST1';

HISTOGRAM
---------
FREQUENCY
Jon Heller
sumber
2
Contoh yang sangat sederhana. Apakah Anda tahu mengapa CBO menggunakan histogram untuk perkiraan kardinalitas pada pemindaian unik daripada hanya dengan asumsi 1?
Jack Douglas
Terima kasih! Saya membuat repro lengkap dengan jenis data dan pertanyaan di blog saya: joco.name/2014/01/05/…
fejesjoco
@ Jack, kupikir itu kemalasan. Insinyur Oracle pasti mengira bahwa statistik indeks unik akan memiliki jumlah nilai berbeda yang sama dengan baris, sehingga asumsi kardinalitas 1 tidak bawaan, tetapi hanya digunakan dari statistik, seperti dalam kasus lain. Juga, sebagai kasus umum, histogram mengalahkan statistik sederhana. Kasing saya sepertinya sangat istimewa karena tombol yang panjang saja, tetapi saya yakin ini bekerja dengan sangat baik.
fejesjoco
@fejesjoco Saya pikir penjelasan JL lebih mungkin, karena histogram juga akan mengungguli statistik umum dalam hal pencarian tunggal (tanpa in), bukan? Saya pikir CBO membuat asumsi kardinalitas 1, tetapi hanya dalam kasus yang sangat sederhana. Saya berasumsi Anda bisa mengatasi semuanya dengan menggunakan besar, UNION ALLtetapi mungkin ada alasan lain untuk tidak melakukan itu dan JL menyebutkan solusi lain yang mungkin dalam posting blog terkait.
Jack Douglas
1
Satu misteri kecil lainnya yang perlu dipertimbangkan - bagaimana histogram ini dapat dibuat pada awalnya? Oracle tampaknya hanya menganggap sebuah kolom miring jika memiliki duplikat, yang jelas kolom unik Anda tidak bisa miliki. Apakah seseorang dengan sengaja membuat histogram ini (tidak mungkin), atau seseorang mengumpulkan statistik dengan yang tidak direkomendasikan method_opt=>'for all indexed columns'?
Jon Heller
8

Saya menemukan solusinya! Sangat indah dan saya benar-benar belajar BANYAK tentang Oracle.

Dalam satu kata: histogram.

Saya mulai membaca banyak tentang cara kerja Oracle CBO dan saya menemukan histogram. Saya tidak sepenuhnya mengerti jadi saya melihat tabel USER_HISTOGRAMS, dan voilá. Ada beberapa baris untuk meja sakit, dan praktis tidak ada untuk tabel kloning. Untuk tabel sakit, ada satu baris untuk masing-masing dari 8 bagian pengenal-awal yang berbeda. Dan ini kuncinya: mereka terpotong di 32 karakter, sebelum tanda @. Seperti yang saya katakan, bagian pertama kunci sangat berulang, mereka menjadi berbeda setelah tanda @.

Tampaknya histogram bisa lebih kuat daripada fakta sederhana bahwa indeks unik selalu memiliki kardinalitas 0 atau 1 untuk nilai yang diberikan. Ketika saya mencari 2+ baris, Oracle melihat histogram, ia berpikir bahwa mungkin ada puluhan ribu nilai untuk bagian pengenal awal, dan itu melemparkan CBO di luar jalur.

Saya menghapus histogram untuk kolom itu di tabel lama dan masalahnya hilang!

Lebih banyak membaca: https://blogs.oracle.com/optimizer/entry/how_do_i_drop_an_existing_histogram_on_a_column_and_stop_the_auto_stats_gathering_job_from_creating

fejesjoco
sumber
2
Saya menyebutkan itu di ruang obrolan kami :) chat.stackexchange.com/transcript/message/12987649#12987649
Philᵀᴹ
Saya tidak melihat itu :). Jadi satu-satunya hal yang aneh adalah mengapa ada histogram di tabel pertama dan tidak di klon, saya pikir collect_schema_stats memperbarui semuanya, ternyata tidak.
fejesjoco
6

Saya mengirim email ke Jonathan Lewis tentang ini dan mendapat balasan yang sangat membantu:

Keanehan dalam perhitungan adalah konsekuensi dari batasan histogram berbasis karakter, lihat khususnya:

http://jonathanlewis.wordpress.com/2010/10/13/frequency-histogram-5/ http://jonathanlewis.wordpress.com/2010/10/19/frequency-histograms-6/

Melihat contoh, kueri adalah untuk daftar IN, bukan untuk satu baris, jadi tebakan awal saya adalah bahwa pengoptimal telah menggunakan strategi generik untuk menghitung selektivitas multi-baris daripada memiliki potongan kasus khusus kode untuk suatu Daftar IN pada kunci utama. Saya kira itu tidak akan terlalu sulit bagi mereka untuk mengenali kasus ini, tetapi pengembang mungkin tidak menganggapnya sepadan dengan usaha.

Saya sangat merekomendasikan membaca posting blog yang dia tautkan, mereka menjelaskan secara rinci batasan histogram yang Anda jalankan, misalnya:

Kesimpulan : Jika Anda memiliki string yang cukup panjang, dan serupa, di kolom yang merupakan kandidat yang baik untuk histogram frekuensi (misalnya kolom status yang sangat deskriptif) maka Anda memiliki masalah jika nilai yang sangat langka terlihat identik dengan yang sangat populer nilai hingga 32 karakter pertama. Anda mungkin menemukan bahwa satu-satunya solusi adalah mengubah daftar nilai hukum (meskipun berbagai strategi yang melibatkan kolom virtual atau indeks berbasis fungsi dapat mem-bypass masalah).

Jack Douglas
sumber
Sayangnya histogram tampaknya menjadi fitur yang sedikit diketahui, saya kira itu karena terlalu dalam untuk pengembang SQL dan sebagian besar waktu mereka hanya bekerja, tetapi ada baiknya mengetahui ada banyak sumber daya tentang hal itu, saya hanya tidak mencari di tempat yang tepat :). Sangat buruk bahwa Oracle memotong 32 byte dan membuat keputusan bencana berdasarkan itu. Untungnya, saya tidak perlu mengutak-atik, menjatuhkan histogram adalah solusi sempurna. Nilai kuncinya unik, saya selalu mencari 20 nilai sekaligus, hanya berfungsi dengan indeks saja, dan bersifat deterministik. Tapi saya tidak akan menggunakan tombol panjang lain kali, itu sudah pasti.
fejesjoco
Histogram cukup terkenal di antara para DBA;) Saya suka fakta bahwa Anda tampaknya tertarik untuk mempelajari hal-hal yang lebih dalam dan benar-benar berpikir Anda harus membaca buku JL itu sangat sangat bagus. CBO umumnya bekerja dengan baik: akan selalu ada kasus-kasus tepi yang perlu diselidiki tetapi perlu diingat bahwa bahkan tanpa terputus, estimasi selalu hanya perkiraan.
Jack Douglas
1
Jika Anda menjalankan pekerjaan statistik biasa (seperti yang dijalankan Oracle secara default pada instalasi yang bersih), Anda mungkin menemukan histogram muncul kembali, Anda mungkin perlu melihat cara mencegah hal itu (seperti LOCK_TABLE_STATS mungkin)
Jack Douglas
Saya menyebutkan posting blog dalam jawaban saya, ada instruksi tentang cara mencegah histogram untuk kolom.
fejesjoco
1
@ Jack Douglas, terima kasih telah melibatkan J. Lewis dan melaporkan kembali!
Dimitre Radoulov