it-swarm-id.com

Mengoptimalkan bergabung di meja besar

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 dalam #smalltable). Ini kelihatannya terbalik bagi saya, jadi saya sudah mencoba 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 di bawah untuk definisi lengkap) 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 clustered pk_hugetable (added, fk) (kunci utama), dan indeks non-clustered 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 bagian dalam bergabung fk dapat menyaring mungkin 20-30%.


Seperti yang diminta oleh Will A, hasil 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
);

Sementara 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];

Kolom id berlebihan, sebuah artefak dari DBA sebelumnya yang bersikeras bahwa semua tabel di mana saja di mana-mana harus memiliki GUID, tidak ada pengecualian .

10
Quick Joe Smith

ix_hugetable Anda terlihat sangat tidak berguna karena:

  • it is 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 adalah yang pertama = tidak banyak digunakan

Coba ubah kunci yang dikelompokkan menjadi (added, fk, id) Dan drop 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 dapat dibatalkan maka sama dengan COUNT(*) semantik. Tetapi untuk SUM dibutuhkan nilai aktual, bukan keberadaan.

Sebagai contoh, jika Anda mengubah COUNT(value) menjadi COUNT(DISTINCT value)tanpa mengubah indeks itu harus memecah kueri lagi karena harus memproses nilai sebagai nilai, tidak sebagai keberadaan.

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.

5
gbn

Tetapkan indeks pada hugetable hanya pada kolom added.

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 fk dalam klausa di mana kueri pertama, sehingga mengabaikan indeks.

2
Bohemian

Rencana eksekusi menunjukkan bahwa loop bersarang sedang digunakan di #smalltable, dan bahwa pemindaian indeks lebih dari hugetable dieksekusi 480 kali (untuk setiap baris dalam #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 adalah cukup banyak 250M baris * 'ukuran setiap baris' - tidak kecil, paling setidaknya beberapa GB . Dilihat dari sp_spaceused output 'sepasang GB' mungkin cukup meremehkan - penggabungan MERGE mengharuskan Anda menjelajah indeks yang akan menjadi sangat intensif I/O.

2
Will A

Indeks Anda salah. Lihat indeks dos dan tidak boleh dilakukan .

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.

1