GROUP BY dengan MAX versus hanya MAX

8

Saya seorang programmer, berurusan dengan tabel besar yang skema berikut:

UpdateTime, PK, datetime, notnull
Name, PK, char(14), notnull
TheData, float

Ada indeks berkerumun di Name, UpdateTime

Saya bertanya-tanya apa yang harus lebih cepat:

SELECT MAX(UpdateTime)
FROM [MyTable]

atau

SELECT MAX([UpdateTime]) AS value
from
   (
    SELECT [UpdateTime]
    FROM [MyTable]
    group by [UpdateTime]
   ) as t

Sisipan ke tabel ini adalah dalam potongan 50.000 baris dengan tanggal yang sama . Jadi saya pikir pengelompokan dengan mungkin mempermudah MAXperhitungan.

Alih-alih mencoba menemukan maks 150.000 baris, pengelompokan menjadi 3 baris, dan kemudian perhitungan MAXakan lebih cepat? Apakah asumsi saya benar atau dikelompokkan oleh juga mahal?

Ofiris
sumber

Jawaban:

12

Saya membuat tabel big_table sesuai dengan skema Anda

create table big_table
(
    updatetime datetime not null,
    name char(14) not null,
    TheData float,
    primary key(Name,updatetime)
)

Saya kemudian mengisi tabel dengan 50.000 baris dengan kode ini:

DECLARE @ROWNUM as bigint = 1
WHILE(1=1)
BEGIN
    set @rownum  = @ROWNUM + 1
    insert into big_table values(getdate(),'name' + cast(@rownum as CHAR), cast(@rownum as float))
    if @ROWNUM > 50000
        BREAK;  
END

Menggunakan SSMS, saya kemudian menguji kedua kueri dan menyadari bahwa dalam kueri pertama Anda mencari MAX TheData dan di kedua, MAX dari updatetime

Saya memodifikasi permintaan pertama untuk mendapatkan MAX dari updatetime

set statistics time on -- execution time
set statistics io on -- io stats (how many pages read, temp tables)

-- query 1
SELECT MAX([UpdateTime])
FROM big_table

-- query 2
SELECT MAX([UpdateTime]) AS value
from
   (
    SELECT [UpdateTime]
    FROM big_table
    group by [UpdateTime]
   ) as t


set statistics time off
set statistics io off

Menggunakan Waktu Statistik, saya mendapatkan kembali jumlah milidetik yang diperlukan untuk mem-parsing, mengkompilasi, dan mengeksekusi setiap pernyataan

Menggunakan Statistik IO saya mendapatkan kembali informasi tentang aktivitas disk

WAKTU STATISTIK dan STATISTIK IO memberikan informasi yang berguna. Seperti tabel sementara yang digunakan (ditunjukkan oleh meja kerja). Juga berapa banyak halaman logis yang dibaca dibaca yang menunjukkan jumlah halaman database yang dibaca dari cache.

Saya kemudian mengaktifkan paket Eksekusi dengan CTRL + M (aktif menunjukkan rencana eksekusi aktual) dan kemudian mengeksekusi dengan F5.

Ini akan memberikan perbandingan kedua pertanyaan.

Ini adalah output dari Tab Pesan

- Pertanyaan 1

Tabel 'big_table'. Pindai hitungan 1, bacaan logis 543 , bacaan fisik 0, bacaan baca depan 0, bacaan logis lob 0, bacaan fisik lob 0, bacaan baca lob depan 0.

Waktu Eksekusi SQL Server: Waktu CPU = 16 ms, waktu yang berlalu = 6 ms .

- Pertanyaan 2

Tabel 'Meja Kerja '. Pindai jumlah 0, bacaan logis 0, bacaan fisik 0, bacaan baca-depan 0, bacaan logis lob 0, bacaan fisik lob 0, bac baca baca-depan 0.

Tabel 'big_table'. Pindai hitungan 1, bacaan logis 543 , bacaan fisik 0, bacaan baca depan 0, bacaan logis lob 0, bacaan fisik lob 0, bacaan baca lob depan 0.

Waktu Eksekusi SQL Server: Waktu CPU = 0 ms, waktu yang berlalu = 35 ms .

Kedua kueri menghasilkan 543 bacaan logis, tetapi kueri kedua memiliki waktu yang telah berlalu 35ms sedangkan yang pertama hanya memiliki 6ms. Anda juga akan melihat bahwa hasil kueri kedua dalam penggunaan tabel sementara di tempdb, ditunjukkan oleh kata meja kerja . Meskipun semua nilai untuk tabel kerja adalah 0, pekerjaan masih dilakukan di tempdb.

Lalu ada output dari tab rencana Eksekusi yang sebenarnya di sebelah tab Pesan

masukkan deskripsi gambar di sini

Menurut rencana eksekusi yang disediakan oleh MSSQL, permintaan kedua yang Anda berikan memiliki total biaya batch 64% sedangkan yang pertama hanya biaya 36% dari total batch, sehingga permintaan pertama membutuhkan lebih sedikit pekerjaan.

Menggunakan SSMS, Anda dapat menguji dan membandingkan kueri Anda dan mencari tahu bagaimana MSSQL mem-parsing kueri Anda dan objek apa: tabel, indeks, dan / atau statistik jika ada yang digunakan untuk memenuhi kueri tersebut.

Satu catatan tambahan yang perlu diingat ketika pengujian adalah membersihkan cache sebelum pengujian, jika memungkinkan. Ini membantu untuk memastikan bahwa perbandingan akurat dan ini penting ketika memikirkan aktivitas disk. Saya mulai dengan DBCC DROPCLEANBUFFERS dan DBCC FREEPROCCACHE untuk menghapus semua cache. Berhati-hatilah meskipun tidak menggunakan perintah ini pada server produksi yang benar-benar digunakan karena Anda akan secara efektif memaksa server untuk membaca semuanya dari disk ke dalam memori.

Ini dokumentasi yang relevan.

  1. Bersihkan cache rencana dengan DBCC FREEPROCCACHE
  2. Hapus semuanya dari kumpulan buffer dengan DBCC DROPCLEANBUFFERS

Menggunakan perintah ini mungkin tidak dimungkinkan tergantung pada bagaimana lingkungan Anda digunakan.

Diperbarui 10/28 12:46

Membuat koreksi pada gambar rencana eksekusi dan output statistik.

Craig Efrein
sumber
Terima kasih atas jawaban yang dalam, tolong perhatikan baris botak saya dalam kode, masing-masing kelompok 50.000 baris memiliki tanggal yang sama yang berbeda dari potongan lainnya. Jadi saya harus getdate()keluar dari loop
Ofiris
1
Halo @Ofiris. Jawaban yang saya berikan sebenarnya hanya untuk membantu Anda membuat perbandingan sendiri. Saya membuat data sampah acak hanya untuk menggambarkan penggunaan berbagai perintah dan alat yang dapat Anda gunakan untuk sampai pada kesimpulan Anda sendiri.
Craig Efrein
1
Tidak ada pekerjaan yang dilakukan di tempdb. Meja kerja adalah untuk mengelola partisi jika agregat hash harus tumpah ke tempdb karena memori tidak mencukupi untuk itu. Harap tekankan bahwa biaya selalu merupakan taksiran bahkan dalam rencana 'aktual'. Mereka adalah perkiraan pengoptimal, yang mungkin tidak memiliki banyak hubungan dengan kinerja aktual. Jangan gunakan% batch sebagai metrik penyetelan primer. Menghapus buffer hanya penting jika Anda ingin menguji kinerja cache dingin.
Paul White 9
1
Halo @PaulWhite. Terima kasih atas informasi tambahannya, saya dengan tulus menghargai setiap saran tentang bagaimana menjadi lebih tepat. Namun ketika Anda mengucapkan kalimat: "Jangan gunakan", bisakah itu tidak disalahartikan sebagai memberi perintah alih-alih menawarkan saran profesional? Salam Hormat.
Craig Efrein
@CraigEfrein Mungkin. Saya sedang singkat agar sesuai dengan ruang komentar yang diizinkan.
Paul White 9
6

Sisipan ke tabel ini adalah dalam potongan 50.000 baris dengan tanggal yang sama. Jadi saya pikir pengelompokan dengan mungkin mempermudah perhitungan MAX.

Penulisan ulang mungkin membantu jika SQL Server menerapkan indeks skip-scan, tetapi tidak.

Index skip-scan memungkinkan mesin basis data untuk mencari nilai indeks yang berbeda berikutnya daripada memindai semua duplikat (atau sub-kunci yang tidak relevan) di antaranya. Dalam kasus Anda, lewati-pindai akan memungkinkan mesin menemukan MAX(UpdateTime)yang pertama Name, melompat ke MAX(UpdateTime)yang kedua Name... dan seterusnya. Langkah terakhir adalah menemukan MAX(UpdateTime)kandidat dari satu nama.

Anda dapat mensimulasikan ini sampai batas tertentu menggunakan CTE rekursif, tetapi ini agak berantakan, dan tidak seefisien skip-scan bawaan:

WITH RecursiveCTE
AS
(
    -- Anchor: MAX UpdateTime for
    -- highest-sorting Name
    SELECT TOP (1)
        BT.Name,
        BT.UpdateTime
    FROM dbo.BigTable AS BT
    ORDER BY
        BT.Name DESC,
        BT.UpdateTime DESC

    UNION ALL

    -- Recursive part
    -- MAX UpdateTime for Name
    -- that sorts immediately lower
    SELECT
        SubQuery.Name,
        SubQuery.UpdateTime
    FROM 
    (
        SELECT
            BT.Name,
            BT.UpdateTime,
            rn = ROW_NUMBER() OVER (
                ORDER BY BT.Name DESC, BT.UpdateTime DESC)
        FROM RecursiveCTE AS R
        JOIN dbo.BigTable AS BT
            ON BT.Name < R.Name
    ) AS SubQuery
    WHERE
        SubQuery.rn = 1
)
-- Final MAX aggregate over
-- MAX(UpdateTime) per Name
SELECT MAX(UpdateTime) 
FROM RecursiveCTE
OPTION (MAXRECURSION 0);

Rencana CTE rekursif

Rencana itu melakukan pencarian tunggal untuk masing-masing yang berbeda Name, kemudian menemukan yang tertinggi UpdateTimedari kandidat. Performanya relatif terhadap pemindaian penuh sederhana dari tabel tergantung pada berapa banyak duplikat yang ada per Name, dan apakah halaman yang disentuh oleh singleton berusaha dalam memori atau tidak.

Solusi alternatif

Jika Anda dapat membuat indeks baru di tabel ini, pilihan yang baik untuk kueri ini adalah indeks UpdateTimesendiri:

CREATE INDEX IX__BigTable_UpdateTime 
ON dbo.BigTable (UpdateTime);

Indeks ini akan memungkinkan mesin eksekusi menemukan yang tertinggi UpdateTimedengan pencarian tunggal ke akhir indeks b-tree:

Rencana indeks baru

Paket ini hanya menggunakan beberapa IO logis (untuk menavigasi level b-tree) dan segera menyelesaikannya. Perhatikan bahwa Pemindaian Indeks dalam paket tersebut bukan pemindaian penuh indeks baru - hanya mengembalikan satu baris dari 'ujung' indeks.

Jika Anda tidak ingin membuat indeks baru yang lengkap di atas meja, Anda dapat mempertimbangkan tampilan yang diindeks berisi hanya nilai- UpdateTimenilai unik :

CREATE VIEW dbo.BigTableUpdateTimes
WITH SCHEMABINDING AS
SELECT 
    UpdateTime, 
    NumRows = COUNT_BIG(*)
FROM dbo.BigTable AS BT
GROUP BY
    UpdateTime;
GO
CREATE UNIQUE CLUSTERED INDEX cuq
ON dbo.BigTableUpdateTimes (UpdateTime);

Ini memiliki keuntungan hanya dengan membuat struktur dengan baris sebanyak-banyaknya karena ada UpdateTimenilai unik , meskipun setiap kueri yang mengubah data dalam tabel dasar akan memiliki operator tambahan yang ditambahkan ke rencana pelaksanaannya untuk mempertahankan tampilan yang diindeks. Kueri untuk menemukan nilai maksimum UpdateTimeadalah:

SELECT MAX(BTUT.UpdateTime)
FROM dbo.BigTableUpdateTimes AS BTUT
    WITH (NOEXPAND);

Rencana tampilan terindeks

Paul White 9
sumber