Mengapa kueri agregat secara signifikan lebih cepat dengan klausa GROUP BY daripada tanpa klausa?

12

Saya hanya ingin tahu mengapa permintaan agregat berjalan jauh lebih cepat dengan GROUP BYklausa daripada tanpa satu.

Misalnya, kueri ini membutuhkan waktu hampir 10 detik untuk dijalankan

SELECT MIN(CreatedDate)
FROM MyTable
WHERE SomeIndexedValue = 1

Sementara yang ini membutuhkan waktu kurang dari satu detik

SELECT MIN(CreatedDate)
FROM MyTable
WHERE SomeIndexedValue = 1
GROUP BY CreatedDate

Hanya ada satu CreatedDatedalam kasus ini, jadi kueri yang dikelompokkan mengembalikan hasil yang sama dengan yang tidak dikelompokkan.

Saya perhatikan rencana eksekusi untuk dua kueri berbeda - Kueri kedua menggunakan Paralelisme sedangkan kueri pertama tidak.

Query1 Rencana Eksekusi Rencana Eksekusi Query2

Apakah normal untuk server SQL untuk mengevaluasi permintaan agregat berbeda jika tidak memiliki klausa GROUP BY? Dan apakah ada sesuatu yang bisa saya lakukan untuk meningkatkan kinerja permintaan 1 tanpa menggunakan GROUP BYklausa?

Edit

Saya baru belajar saya dapat menggunakan OPTION(querytraceon 8649)untuk mengatur biaya paralelisme ke 0, yang membuat permintaan menggunakan paralelisme dan mengurangi runtime menjadi 2 detik, meskipun saya tidak tahu apakah ada kerugian untuk menggunakan petunjuk permintaan ini.

SELECT MIN(CreatedDate)
FROM MyTable
WHERE SomeIndexedValue = 1
OPTION(querytraceon 8649)

masukkan deskripsi gambar di sini

Saya masih lebih suka runtime yang lebih pendek karena kueri dimaksudkan untuk mengisi nilai pada pilihan pengguna, jadi idealnya seketika seperti kueri yang dikelompokkan. Saat ini saya hanya membungkus pertanyaan saya, tetapi saya tahu itu bukan solusi yang ideal.

SELECT Min(CreatedDate)
FROM
(
    SELECT Min(CreatedDate) as CreatedDate
    FROM MyTable WITH (NOLOCK) 
    WHERE SomeIndexedValue = 1
    GROUP BY CreatedDate
) as T

Edit # 2

Menanggapi permintaan Martin untuk info lebih lanjut :

Keduanya CreatedDatedan SomeIndexedValuememiliki indeks non-unik dan non-cluster terpisah. SomeIndexedValuesebenarnya adalah bidang varchar (7), meskipun ia menyimpan nilai numerik yang menunjuk ke PK (int) dari tabel lain. Hubungan antara dua tabel tidak didefinisikan dalam database. Saya tidak seharusnya mengubah database sama sekali, dan hanya dapat menulis kueri yang meminta data.

MyTableberisi lebih dari 3 juta catatan, dan setiap catatan ditetapkan ke grup tempat ( SomeIndexedValue). Grup dapat berkisar dari 1 hingga 200.000 catatan

Rachel
sumber

Jawaban:

8

Sepertinya itu mungkin mengikuti indeks CreatedDatedalam urutan dari terendah ke tertinggi dan melakukan pencarian untuk mengevaluasi SomeIndexedValue = 1predikat.

Ketika menemukan baris yang cocok pertama dilakukan, tetapi mungkin melakukan lebih banyak pencarian daripada yang diharapkan sebelum menemukan baris seperti itu (mengasumsikan baris yang cocok dengan predikat didistribusikan secara acak sesuai tanggal.)

Lihat jawaban saya di sini untuk masalah serupa

Indeks ideal untuk kueri ini adalah indeks aktif SomeIndexedValue, CreatedDate. Dengan asumsi bahwa Anda tidak dapat menambahkan itu atau setidaknya membuat indeks yang ada di SomeIndexedValuesampul CreatedDatesebagai kolom yang disertakan maka Anda dapat mencoba menulis ulang kueri sebagai berikut

SELECT MIN(DATEADD(DAY, 0, CreatedDate)) AS CreatedDate
FROM MyTable
WHERE SomeIndexedValue = 1

untuk mencegahnya menggunakan rencana khusus itu.

Martin Smith
sumber
2

Bisakah kita mengendalikan MAXDOP dan memilih tabel yang diketahui, misalnya AdventureWorks.Production.TransactionHistory?

Ketika saya ulangi pengaturan Anda menggunakan

--#1
SELECT MIN(TransactionDate) 
FROM AdventureWorks.Production.TransactionHistory
WHERE TransactionID = 100001 
OPTION( MAXDOP 1) ;

--#2
SELECT MIN(TransactionDate) 
FROM AdventureWorks.Production.TransactionHistory
WHERE TransactionID = 100001 
GROUP BY TransactionDate
OPTION( MAXDOP 1) ;
GO 

biayanya identik.

Sebagai tambahan, saya berharap (mewujudkannya) indeks mencari nilai indeks Anda; jika tidak, Anda cenderung akan melihat kecocokan hash alih-alih agregat aliran. Anda dapat meningkatkan kinerja dengan indeks yang tidak berkerumun yang menyertakan nilai yang Anda agregat dan atau membuat tampilan yang diindeks yang mendefinisikan agregat Anda sebagai kolom. Maka Anda akan memukul indeks berkerumun, yang berisi agregasi Anda, dengan Id Terindeks. Di SQL Standard, Anda bisa membuat tampilan dan menggunakan petunjuk WITH (NOEXPAND).

Contoh (saya tidak menggunakan MIN, karena tidak berfungsi dalam tampilan yang diindeks):

USE AdventureWorks ;
GO

-- Covering Index with Include
CREATE INDEX IX_CoverAndInclude
ON Production.TransactionHistory(TransactionDate) 
INCLUDE (Quantity) ;
GO

-- Indexed View
CREATE VIEW dbo.SumofQtyByTransDate
    WITH SCHEMABINDING
AS
SELECT 
      TransactionDate 
    , COUNT_BIG(*) AS NumberOfTransactions
    , SUM(Quantity) AS TotalTransactions
FROM Production.TransactionHistory
GROUP BY TransactionDate ;
GO

CREATE UNIQUE CLUSTERED INDEX SumofAllChargesIndex 
    ON dbo.SumofQtyByTransDate (TransactionDate) ;  
GO


--#1
SELECT SUM(Quantity) 
FROM AdventureWorks.Production.TransactionHistory 
WITH (INDEX(0))
WHERE TransactionID = 100001 
OPTION( MAXDOP 1) ;

--#2
SELECT SUM(Quantity)  
FROM AdventureWorks.Production.TransactionHistory 
WITH (INDEX(IX_CoverAndInclude))
WHERE TransactionID = 100001 
GROUP BY TransactionDate
OPTION( MAXDOP 1) ;
GO 

--#3
SELECT SUM(Quantity)  
FROM AdventureWorks.Production.TransactionHistory
WHERE TransactionID = 100001 
GROUP BY TransactionDate
OPTION( MAXDOP 1) ;
GO
ooutwire
sumber
MAXDOPmenetapkan tingkat paralelisme maksimum, yang membatasi jumlah prosesor yang dapat digunakan kueri. Ini pada dasarnya akan membuat kueri ke-2 berjalan lambat seperti yang ke-1, karena ia menghilangkan kemampuannya untuk menggunakan paralelisme, yang bukan yang saya inginkan.
Rachel
@Rachel saya setuju; tetapi kami tidak dapat membandingkan apa pun kecuali kami menetapkan beberapa aturan dasar. Saya tidak dapat dengan mudah membandingkan proses paralel yang berjalan pada 64 core dengan satu thread yang berjalan pada satu core. Pada akhirnya, saya berharap semua mesin kami memiliki setidaknya satu CPU logis = -)
ooutwire
0

Menurut pendapat saya alasan untuk masalah ini adalah bahwa pengoptimal server sql tidak mencari rencana TERBAIK melainkan mencari rencana yang baik, seperti terbukti dari fakta bahwa setelah memaksa paralelisme kueri dieksekusi lebih cepat, sesuatu yang dimiliki pengoptimal memiliki tidak dilakukan sendiri.

Saya juga telah melihat banyak situasi di mana penulisan ulang kueri dalam format yang berbeda adalah perbedaan antara paralelisasi (misalnya meskipun sebagian besar artikel tentang SQL merekomendasikan parameterisasi saya telah menemukan hal itu menyebabkan kadang-kadang tidak dapat menyejajarkan bahkan ketika parameter mengendus sama dengan non - diparalelkan, atau menggabungkan dua pertanyaan dengan UNION ALL terkadang dapat menghilangkan paralelisasi).

Dengan demikian solusi yang tepat mungkin dengan mencoba berbagai cara penulisan kueri, seperti mencoba tabel temp, variabel tabel, cte, tabel turunan, parameterisasi, dan sebagainya, dan juga bermain dengan indeks, tampilan indeks, atau indeks yang difilter dalam memesan untuk mendapatkan rencana terbaik.

halo yoel
sumber