Mengoptimalkan bergabung di meja besar

10

Saya mencoba untuk membujuk beberapa kinerja lagi dari permintaan yang mengakses tabel dengan ~ 250 juta catatan. Dari pembacaan saya tentang rencana pelaksanaan aktual (tidak diperkirakan), hambatan pertama adalah kueri yang terlihat seperti ini:

select
    b.stuff,
    a.added,
    a.value
from
    dbo.hugetable a
    inner join
    #smalltable b on a.fk = b.pk
where
    a.added between @start and @end;

Lihat lebih jauh ke bawah untuk definisi tabel & indeks yang terlibat.

Rencana eksekusi menunjukkan bahwa loop bersarang sedang digunakan di #smalltable, dan bahwa pemindaian indeks melalui hugetable dieksekusi 480 kali (untuk setiap baris di #smalltable). Ini kelihatannya terbalik bagi saya, jadi saya telah mencoba untuk memaksa gabungan bergabung untuk digunakan sebagai gantinya:

select
    b.stuff,
    a.added,
    a.value
from
    dbo.hugetable a with(index = ix_hugetable)
    inner merge join
    #smalltable b with(index(1)) on a.fk = b.pk
where
    a.added between @start and @end;

Indeks yang dimaksud (lihat definisi lengkap di bawah) mencakup kolom fk (predikat gabungan), ditambahkan (digunakan dalam klausa where) & id (tidak berguna) dalam urutan menaik, dan termasuk nilai .

Namun, ketika saya melakukan ini, kueri meledak dari 2 1/2 menit menjadi lebih dari 9. Saya berharap bahwa petunjuk akan memaksa bergabung lebih efisien yang hanya melakukan satu melewati setiap tabel, tetapi jelas tidak.

Bimbingan apa pun diterima. Informasi tambahan disediakan jika diperlukan.

Pembaruan (2011/06/02)

Setelah mengatur ulang pengindeksan di atas meja, saya telah membuat terobosan kinerja yang signifikan, namun saya telah mengalami hambatan baru ketika meringkas data dalam tabel besar. Hasilnya adalah ringkasan berdasarkan bulan, yang saat ini terlihat seperti berikut:

select
    b.stuff,
    datediff(month, 0, a.added),
    count(a.value),
    sum(case when a.value > 0 else 1 end) -- this triples the running time!
from
    dbo.hugetable a
    inner join
    #smalltable b on a.fk = b.pk
group by
    b.stuff,
    datediff(month, 0, a.added);

Saat ini, hugetable memiliki indeks berkerumun pk_hugetable (added, fk)(kunci utama), dan indeks non-berkerumun menuju sebaliknya ix_hugetable (fk, added).

Tanpa kolom ke-4 di atas, pengoptimal menggunakan gabungan loop bersarang seperti sebelumnya, menggunakan #smalltable sebagai input luar, dan indeks non-cluster mencari sebagai loop dalam (mengeksekusi 480 kali lagi). Yang mengkhawatirkan saya adalah perbedaan antara baris yang diperkirakan (12.958,4) dan baris aktual (74.668.468). Biaya relatif dari upaya ini adalah 45%. Namun waktu berjalan kurang dari satu menit.

Dengan kolom ke-4, waktu berjalan meningkat menjadi 4 menit. Itu mencari pada indeks berkerumun kali ini (2 eksekusi) untuk biaya relatif yang sama (45%), agregat melalui pertandingan hash (30%), kemudian melakukan hash bergabung di #smalltable (0%).

Saya tidak yakin dengan tindakan selanjutnya. Kekhawatiran saya adalah bahwa baik pencarian rentang tanggal maupun predikat gabungan tidak dijamin atau bahkan semua yang mungkin secara drastis mengurangi set hasil. Rentang tanggal dalam kebanyakan kasus hanya akan memangkas mungkin 10-15% dari catatan, dan gabungan dalam pada fk dapat menyaring mungkin 20-30%.


Seperti yang diminta oleh Will A, hasil dari sp_spaceused:

name      | rows      | reserved    | data        | index_size  | unused
hugetable | 261774373 | 93552920 KB | 18373816 KB | 75167432 KB | 11672 KB

#smalltable didefinisikan sebagai:

create table #endpoints (
    pk uniqueidentifier primary key clustered,
    stuff varchar(6) null
);

Sedangkan dbo.hugetable didefinisikan sebagai:

create table dbo.hugetable (
    id uniqueidentifier not null,
    fk uniqueidentifier not null,
    added datetime not null,
    value decimal(13, 3) not null,

    constraint pk_hugetable primary key clustered (
        fk asc,
        added asc,
        id asc
    )
    with (
        pad_index = off, statistics_norecompute = off,
        ignore_dup_key = off, allow_row_locks = on,
        allow_page_locks = on
    )
    on [primary]
)
on [primary];

Dengan indeks berikut ditentukan:

create nonclustered index ix_hugetable on dbo.hugetable (
    fk asc, added asc, id asc
) include(value) with (
    pad_index = off, statistics_norecompute = off,
    sort_in_tempdb = off, ignore_dup_key = off,
    drop_existing = off, online = off,
    allow_row_locks = on, allow_page_locks = on
)
on [primary];

Bidang id adalah redundan, sebuah artefak dari DBA sebelumnya yang bersikeras bahwa semua tabel di mana saja harus memiliki GUID, tanpa pengecualian.

Joe Smith Cepat
sumber
Bisakah Anda memasukkan hasil sp_spaceused 'dbo.hugetable', tolong?
Will A
Selesai, ditambahkan tepat di atas awal definisi tabel.
Cepat Joe Smith
Pastilah itu. Ukurannya yang konyol adalah alasan saya melihat ini.
Cepat Joe Smith

Jawaban:

5

ix_hugetablePenampilan Anda tidak berguna karena:

  • itu adalah indeks berkerumun (PK)
  • INCLUDE tidak membuat perbedaan karena indeks berkerumun TERMASUK semua kolom non-kunci (nilai non-kunci pada daun terendah = INCLUDEd = apa itu indeks berkerumun)

Selain itu: - ditambahkan atau fk harus menjadi yang pertama - ID lebih dulu = tidak banyak digunakan

Coba ubah kunci yang dikelompokkan ke (added, fk, id)dan lepas ix_hugetable. Anda sudah mencoba (fk, added, id). Jika tidak ada yang lain, Anda akan menghemat banyak ruang disk dan pemeliharaan indeks

Pilihan lain mungkin untuk mencoba petunjuk FORCE ORDER dengan urutan tabel dengan cara boh dan tidak ada petunjuk GABUNG / INDEKS. Saya mencoba untuk tidak menggunakan petunjuk GABUNG / INDEKS secara pribadi karena Anda menghapus opsi untuk pengoptimal. Bertahun-tahun yang lalu saya diberitahu (seminar dengan SQL Guru) bahwa petunjuk FORCE ORDER dapat membantu ketika Anda memiliki meja besar GABUNG meja kecil: YMMV 7 tahun kemudian ...

Oh, dan beri tahu kami di mana DBA tinggal sehingga kami dapat mengatur beberapa penyesuaian perkusi

Edit, setelah pembaruan 02 Juni

Kolom ke-4 bukan bagian dari indeks non-clustered sehingga menggunakan indeks clustered.

Coba ubah indeks NC ke TERMASUK kolom nilai sehingga tidak perlu mengakses kolom nilai untuk indeks berkerumun

create nonclustered index ix_hugetable on dbo.hugetable (
    fk asc, added asc
) include(value)

Catatan: Jika nilainya tidak nullable maka sama dengan COUNT(*)semantik. Tetapi untuk SUM itu membutuhkan nilai aktual , bukan keberadaan .

Sebagai contoh, jika Anda mengubah COUNT(value)ke COUNT(DISTINCT value) tanpa mengubah indeks itu harus istirahat query lagi karena memiliki memproses nilai sebagai nilai, bukan sebagai eksistensi.

Permintaan membutuhkan 3 kolom: ditambahkan, fk, nilai. 2 yang pertama difilter / digabung begitu juga kolom kunci. nilai hanya digunakan sehingga bisa dimasukkan. Penggunaan klasik dari indeks penutup.

gbn
sumber
Hah, ada di kepala saya bahwa indeks clustered dan non-clustered memiliki fk & ditambahkan dalam urutan yang berbeda. Aku tidak percaya aku tidak menyadarinya, hampir seperti aku tidak percaya itu diatur dengan cara ini sejak awal. Saya akan mengubah indeks berkerumun besok, kemudian pergi ke jalan untuk minum kopi saat itu membangun kembali.
Cepat Joe Smith
Saya telah mengubah pengindeksan dan memiliki bash dengan FORCE ORDER dalam upaya untuk mengurangi jumlah pencarian di meja besar tetapi tidak berhasil. Pertanyaan saya telah diperbarui.
Cepat Joe Smith
@Cepat Joe Smith: memperbarui jawaban saya
gbn
Ya, saya mencobanya tidak lama kemudian. Karena pembangunan kembali indeks memakan waktu begitu lama, saya lupa dan awalnya berpikir bahwa saya mempercepatnya melakukan sesuatu yang sama sekali tidak berhubungan.
Cepat Joe Smith
2

Tentukan indeks hugetablehanya pada addedkolom.

DB akan menggunakan indeks multi-bagian (multi-kolom) hanya sejauh kanan dari daftar kolom karena memiliki nilai yang dihitung dari kiri. Kueri Anda tidak menentukan fkdi mana klausa kueri pertama, sehingga mengabaikan indeks.

Orang Bohemian
sumber
Rencana eksekusi menunjukkan bahwa indeks (ix_hugetable) yang sedang menempuh jalur. Atau apakah Anda mengatakan bahwa indeks ini tidak sesuai untuk kueri?
Cepat Joe Smith
Indeks tidak sesuai. Siapa yang tahu bagaimana "menggunakan indeks". Pengalaman memberi tahu saya ini masalah Anda. Coba dan beritahu kami bagaimana hasilnya.
Bohemian
@Cepat Joe Smith - apakah Anda mencoba saran @ Bohemian? Di mana hasilnya?
Lieven Keersmaekers
2
Saya tidak setuju: klausa ON diproses secara logis terlebih dahulu dan secara efektif WHERE dalam praktiknya sehingga OP harus mencoba kedua kolom terlebih dahulu. Tidak ada pengindeksan pada fk sama sekali = scan indeks berkerumun atau pencarian kunci untuk mendapatkan nilai fk untuk GABUNG. Bisakah Anda menambahkan beberapa referensi ke perilaku yang telah Anda uraikan juga? Khusus untuk SQL Server mengingat Anda memiliki sedikit riwayat sebelumnya yang menjawab RDBMS ini. Sebenarnya, -1 dalam retrospeksi ketika saya mengetik komentar ini
gbn
2

Rencana eksekusi menunjukkan bahwa loop bersarang sedang digunakan di #smalltable, dan bahwa pemindaian indeks melalui hugetable dieksekusi 480 kali (untuk setiap baris di #smalltable).

Ini adalah urutan yang saya harapkan akan digunakan pengoptimal kueri, dengan asumsi bahwa sebuah loop bergabung dalam pilihan yang tepat. Alternatifnya adalah untuk mengulang sebanyak 250 juta kali dan melakukan pencarian ke tabel #temp setiap kali - yang bisa memakan waktu berjam-jam / hari.

Indeks yang Anda paksakan untuk digunakan dalam gabungan MERGE cukup banyak baris 250M * 'ukuran setiap baris' - tidak kecil, setidaknya beberapa GB. Dilihat dari sp_spaceusedoutput 'sepasang GB' mungkin cukup meremehkan - penggabungan MERGE mengharuskan Anda menjelajah melalui indeks yang akan menjadi sangat intensif I / O.

Will A
sumber
Pemahaman saya adalah bahwa ada 3 jenis algoritme gabung, dan gabungan gabung memiliki kinerja terbaik saat kedua input dipesan oleh predikat gabung. Benar atau salah, ini adalah hasil yang saya coba dapatkan.
Cepat Joe Smith
2
Tapi ada yang lebih dari ini. Jika #smalltable memiliki banyak baris, gabungan gabung mungkin tepat. Jika, seperti namanya, itu memiliki sejumlah kecil baris maka loop bergabung bisa menjadi pilihan yang tepat. Bayangkan #smalltable memiliki satu atau dua baris, dan cocok dengan beberapa baris dari tabel lainnya - akan sulit untuk membenarkan penggabungan bergabung di sini.
Will A
Saya pikir ada lebih dari itu; Aku hanya tidak tahu apa itu. Pengoptimalan basis data tidak tepat untuk saya, karena Anda mungkin sudah menebaknya.
Cepat Joe Smith
@Cepat Joe Smith - terima kasih untuk sp_spaceused. 75GB indeks dan 18GB data - apakah ix_hugetable bukan satu-satunya indeks di atas meja?
Will A
1
+1 Will. Perencana saat ini melakukan hal yang benar. Masalahnya terletak pada pencarian disk acak karena cara tabel Anda dikelompokkan.
Denis de Bernardy
1

Indeks Anda salah. Lihat indeks dos dan larangan .

Pada dasarnya, satu-satunya indeks berguna Anda adalah pada kunci utama tabel kecil itu. Dengan demikian, satu-satunya rencana yang masuk akal adalah dengan memindai tabel kecil dan menumpuk kekacauan dengan tabel besar.

Coba tambahkan indeks berkerumun di hugetable(added, fk). Ini harus membuat perencana mencari baris yang berlaku dari tabel besar, dan loop sarang atau bergabung bergabung dengan mereka dengan tabel kecil.

Denis de Bernardy
sumber
Terima kasih atas tautannya. Saya akan mencoba ini ketika saya mulai bekerja besok.
Cepat Joe Smith