Memperbarui tabel secara efisien menggunakan BERGABUNG


8

Saya memiliki tabel yang memiliki perincian rumah tangga dan yang lain memiliki perincian semua orang yang terkait dengan rumah tangga tersebut. Untuk tabel rumah tangga saya memiliki kunci primer yang didefinisikan menggunakan dua kolom di dalamnya - [tempId,n]. Untuk tabel orang saya memiliki kunci utama yang didefinisikan menggunakan 3 kolomnya[tempId,n,sporder]

Dengan menggunakan pengurutan yang ditentukan oleh pengindeksan berkerumun pada kunci primer, saya telah membuat ID unik untuk setiap rumah tangga [HHID]dan setiap orang [PERID]mencatat (potongan di bawah ini untuk menghasilkan PERID]:

 ALTER TABLE dbo.persons
 ADD PERID INT IDENTITY
 CONSTRAINT [UQ dbo.persons HHID] UNIQUE;

Sekarang, langkah saya selanjutnya adalah mengasosiasikan setiap orang dengan rumah tangga yang sesuai yaitu; memetakan a [PERID]ke a [HHID]. Penyeberangan antara dua tabel didasarkan pada dua kolom [tempId,n]. Untuk ini, saya memiliki pernyataan bergabung berikut ini.

UPDATE t1
  SET t1.HHID = t2.HHID
  FROM dbo.persons AS t1
  INNER JOIN dbo.households AS t2
  ON t1.tempId = t2.tempId AND t1.n = t2.n;

Saya memiliki total 1928783 catatan rumah tangga dan 5239842 catatan orang. Waktu pelaksanaan saat ini sangat tinggi.

Sekarang, pertanyaan saya:

  1. Apakah mungkin untuk mengoptimalkan kueri ini lebih lanjut? Secara umum, apa aturan praktis untuk mengoptimalkan kueri bergabung?
  2. Apakah ada konstruk query lain yang dapat mencapai hasil yang saya inginkan dengan waktu eksekusi yang lebih baik?

Saya telah mengunggah paket eksekusi yang dihasilkan oleh SQL Server 2008 untuk seluruh skrip ke SQLPerformance.com

Jawaban:


19

Saya cukup yakin definisi tabel dekat dengan ini:

CREATE TABLE dbo.households
(
    tempId  integer NOT NULL,
    n       integer NOT NULL,
    HHID    integer IDENTITY NOT NULL,

    CONSTRAINT [UQ dbo.households HHID] 
        UNIQUE NONCLUSTERED (HHID),

    CONSTRAINT [PK dbo.households tempId, n]
    PRIMARY KEY CLUSTERED (tempId, n)
);

CREATE TABLE dbo.persons
(
    tempId  integer NOT NULL,
    sporder integer NOT NULL,
    n       integer NOT NULL,
    PERID   integer IDENTITY NOT NULL,
    HHID    integer NOT NULL,

    CONSTRAINT [UQ dbo.persons HHID]
        UNIQUE NONCLUSTERED (PERID),

    CONSTRAINT [PK dbo.persons tempId, n, sporder]
        PRIMARY KEY CLUSTERED (tempId, n, sporder)
);

Saya tidak memiliki statistik untuk tabel ini atau data Anda, tetapi yang berikut setidaknya akan mengatur kardinalitas tabel dengan benar (jumlah halaman adalah perkiraan):

UPDATE STATISTICS dbo.persons 
WITH 
    ROWCOUNT = 5239842, 
    PAGECOUNT = 100000;

UPDATE STATISTICS dbo.households 
WITH 
    ROWCOUNT = 1928783, 
    PAGECOUNT = 25000;

Analisis Rencana Kueri

Permintaan yang Anda miliki sekarang adalah:

UPDATE P
SET HHID = H.HHID
FROM dbo.households AS H
JOIN dbo.persons AS P
    ON P.tempId = H.tempId
    AND P.n = H.n;

Ini menghasilkan rencana yang agak tidak efisien:

Paket standar

Masalah utama dalam rencana ini adalah hash join dan sortir. Keduanya membutuhkan hibah memori (hash join perlu membangun tabel hash, dan sort membutuhkan ruang untuk menyimpan baris saat penyortiran berlangsung). Plan Explorer menunjukkan permintaan ini diberikan 765 MB:

Memory Grant

Ini cukup banyak memori server yang didedikasikan untuk satu permintaan! Lebih penting lagi, pemberian memori ini diperbaiki sebelum eksekusi dimulai berdasarkan jumlah baris dan perkiraan ukuran.

Jika memori ternyata tidak mencukupi pada waktu eksekusi, setidaknya beberapa data untuk hash dan / atau pengurutan akan ditulis ke disk tempdb fisik . Ini dikenal sebagai 'tumpahan' dan ini bisa menjadi operasi yang sangat lambat. Anda dapat melacak tumpahan ini (dalam SQL Server 2008) menggunakan peristiwa Profiler Peringatan Hash dan Sortir Peringatan .

Perkiraan untuk input build tabel hash sangat baik:

Input Hash

Perkiraan untuk input sortir kurang akurat:

Sortir Input

Anda harus menggunakan Profiler untuk memeriksa, tetapi saya curiga jenisnya akan tumpah ke tempdb dalam kasus ini. Mungkin juga tabel hash juga ikut tumpah, tapi itu kurang jelas.

Perhatikan bahwa memori yang dicadangkan untuk kueri ini dibagi antara tabel hash dan urutkan, karena mereka menjalankan secara bersamaan. Properti paket Fraksi Memori menunjukkan jumlah relatif dari hibah memori yang diharapkan akan digunakan oleh setiap operasi.

Mengapa Menyortir dan Hash?

Pengurutan ini diperkenalkan oleh pengoptimal kueri untuk memastikan bahwa baris tiba di operator Pembaruan Indeks Clustered dalam urutan kunci terkelompok. Ini mempromosikan akses berurutan ke tabel, yang seringkali jauh lebih efisien daripada akses acak.

Gabung hash adalah pilihan yang tidak terlalu jelas, karena inputnya berukuran sama (untuk perkiraan pertama). Hash join adalah yang terbaik di mana satu input (input yang membangun tabel hash) relatif kecil.

Dalam kasus ini, model penetapan biaya pengoptimal menentukan bahwa hash join adalah yang lebih murah dari tiga opsi (hash, merge, nested loop).

Meningkatkan Kinerja

Model biaya tidak selalu benar. Ini cenderung untuk memperkirakan biaya gabungan paralel, terutama karena jumlah utas meningkat. Kami dapat memaksa gabungan bergabung dengan petunjuk kueri:

UPDATE P
SET HHID = H.HHID
FROM dbo.households AS H
JOIN dbo.persons AS P
    ON P.tempId = H.tempId
    AND P.n = H.n
OPTION (MERGE JOIN);

Ini menghasilkan paket yang tidak membutuhkan banyak memori (karena menggabungkan gabungan tidak memerlukan tabel hash):

Rencana Penggabungan

Urutan bermasalah masih ada, karena gabungan bergabung hanya mempertahankan urutan kunci gabungannya (tempId, n) tetapi kunci yang dikelompokkan adalah (tempId, n, sporder). Anda dapat menemukan rencana bergabung gabung melakukan tidak lebih baik dari rencana bergabung hash.

Nested Loops Bergabung

Kami juga dapat mencoba loop bersarang bergabung:

UPDATE P
SET HHID = H.HHID
FROM dbo.households AS H
JOIN dbo.persons AS P
    ON P.tempId = H.tempId
    AND P.n = H.n
OPTION (LOOP JOIN);

Rencana kueri ini adalah:

Paket Serial Nested Loops

Rencana kueri ini dianggap yang terburuk oleh model penetapan biaya pengoptimal, tetapi memang memiliki beberapa fitur yang sangat diinginkan. Pertama, loop bersarang bergabung tidak memerlukan hibah memori. Kedua, ia bisa mempertahankan urutan kunci dari Personstabel sehingga pengurutan eksplisit tidak diperlukan. Anda mungkin menemukan bahwa rencana ini berkinerja relatif baik, bahkan mungkin cukup baik.

Loop Bersarang Paralel

Kelemahan besar dengan rencana loop bersarang adalah bahwa ia berjalan pada satu utas. Kemungkinan kueri ini mendapat manfaat dari paralelisme, tetapi pengoptimal memutuskan tidak ada keuntungan dalam melakukan itu di sini. Ini belum tentu benar juga. Sayangnya, tidak ada petunjuk kueri bawaan untuk mendapatkan paket paralel, tetapi ada cara tidak berdokumen:

UPDATE t1
  SET t1.HHID = t2.HHID
  FROM dbo.persons AS t1
  INNER JOIN dbo.households AS t2
  ON t1.tempId = t2.tempId AND t1.n = t2.n
OPTION (LOOP JOIN, QUERYTRACEON 8649);

Mengaktifkan jejak flag 8649 dengan QUERYTRACEONpetunjuk menghasilkan rencana ini:

Paket Loop Bersarang Paralel

Sekarang kami memiliki rencana yang menghindari penyortiran, tidak memerlukan memori tambahan untuk bergabung, dan menggunakan paralelisme secara efektif. Anda harus menemukan kueri ini berkinerja lebih baik daripada alternatifnya.

Informasi lebih lanjut tentang paralelisme dalam artikel saya Memaksa Rencana Eksekusi Paralel :


1

Melihat rencana permintaan Anda, ada kemungkinan bahwa masalah Anda yang sebenarnya mungkin bukan gabungannya saja, melainkan proses pembaruan yang sebenarnya.

Dari apa yang saya lihat, Anda cenderung memperbarui semua catatan orang dalam database Anda dan memperbarui indeks (Saya tidak bisa melihat indeks lain apa yang dimiliki sehingga saya tidak tahu apakah itu bisa menjadi faktor)

Jika ini adalah tugas satu kali, bisakah Anda menonaktifkan indeks, menjalankan pembaruan dan membangun kembali indeks?

Setelah data terisi, Anda bisa menambahkan klausa di mana ke kueri Anda untuk hanya memperbarui catatan yang perlu diperbarui

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.