Saat ini saya sedang merancang tabel transaksi. Saya menyadari bahwa menghitung total running untuk setiap baris akan diperlukan dan ini mungkin memperlambat kinerja. Jadi saya membuat tabel dengan 1 juta baris untuk tujuan pengujian.
CREATE TABLE [dbo].[Table_1](
[seq] [int] IDENTITY(1,1) NOT NULL,
[value] [bigint] NOT NULL,
CONSTRAINT [PK_Table_1] PRIMARY KEY CLUSTERED
(
[seq] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
Dan saya mencoba untuk mendapatkan 10 baris baru dan total berjalan, tetapi butuh sekitar 10 detik.
--1st attempt
SELECT TOP 10 seq
,value
,sum(value) OVER (ORDER BY seq) total
FROM Table_1
ORDER BY seq DESC
--(10 rows affected)
--Table 'Worktable'. Scan count 1000001, logical reads 8461526, physical reads 2, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
--Table 'Table_1'. Scan count 1, logical reads 2608, physical reads 516, read-ahead reads 2617, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
--Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
--
--(1 row affected)
--
-- SQL Server Execution Times:
-- CPU time = 8483 ms, elapsed time = 9786 ms.
Saya menduga TOP
karena alasan kinerja lambat dari rencana, jadi saya mengubah permintaan seperti ini, dan butuh sekitar 1 ~ 2 detik. Tapi saya pikir ini masih lambat untuk produksi dan bertanya-tanya apakah ini dapat diperbaiki lebih lanjut.
--2nd attempt
SELECT *
,(
SELECT SUM(value)
FROM Table_1
WHERE seq <= t.seq
) total
FROM (
SELECT TOP 10 seq
,value
FROM Table_1
ORDER BY seq DESC
) t
ORDER BY seq DESC
--(10 rows affected)
--Table 'Table_1'. Scan count 11, logical reads 26083, physical reads 1, read-ahead reads 443, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
--
--(1 row affected)
--
-- SQL Server Execution Times:
-- CPU time = 1422 ms, elapsed time = 1621 ms.
Pertanyaan saya adalah:
- Mengapa kueri dari upaya 1 lebih lambat dari yang ke-2?
- Bagaimana saya dapat meningkatkan kinerja lebih lanjut? Saya juga dapat mengubah skema.
Supaya jelas, kedua kueri mengembalikan hasil yang sama seperti di bawah ini.
sql-server
database-design
t-sql
query-performance
execution-plan
pengguna2652379
sumber
sumber
value
?Jawaban:
Saya merekomendasikan pengujian dengan sedikit lebih banyak data untuk mendapatkan ide yang lebih baik tentang apa yang terjadi dan untuk melihat bagaimana kinerja berbagai pendekatan. Saya memuat 16 juta baris ke sebuah tabel dengan struktur yang sama. Anda dapat menemukan kode untuk mengisi tabel di bagian bawah jawaban ini.
Pendekatan berikut ini memakan waktu 19 detik di mesin saya:
Rencana sebenarnya di sini . Sebagian besar waktu dihabiskan untuk menghitung jumlah dan melakukan semacam itu. Yang mengkhawatirkan, rencana kueri mengerjakan hampir semua pekerjaan untuk seluruh rangkaian hasil dan memfilter ke 10 baris yang Anda minta di bagian paling akhir. Runtime kueri ini menskala dengan ukuran tabel alih-alih dengan ukuran set hasil.
Opsi ini membutuhkan 23 detik pada mesin saya:
Rencana sebenarnya di sini . Pendekatan ini menskala dengan jumlah baris yang diminta dan ukuran tabel. Hampir 160 juta baris dibaca dari tabel:
Untuk mendapatkan hasil yang benar, Anda harus menjumlahkan baris untuk seluruh tabel. Idealnya Anda akan melakukan penjumlahan ini hanya sekali. Dimungkinkan untuk melakukan ini jika Anda mengubah cara Anda mendekati masalah. Anda bisa menghitung jumlah untuk seluruh tabel lalu mengurangi total berjalan dari baris di set hasil. Itu memungkinkan Anda menemukan jumlah untuk baris ke-N. Salah satu cara untuk melakukan ini:
Rencana sebenarnya di sini . Kueri baru berjalan dalam 644 ms pada mesin saya. Tabel dipindai sekali untuk mendapatkan total lengkap kemudian baris tambahan dibaca untuk setiap baris dalam set hasil. Tidak ada penyortiran dan hampir semua waktu dihabiskan untuk menghitung jumlah pada bagian paralel dari rencana:
Jika Anda ingin agar kueri ini berjalan lebih cepat, Anda hanya perlu mengoptimalkan bagian yang menghitung jumlah keseluruhan. Permintaan di atas melakukan pemindaian indeks berkerumun. Indeks berkerumun termasuk semua kolom tetapi Anda hanya perlu
[value]
kolom. Salah satu opsi adalah membuat indeks yang tidak tercakup pada kolom itu. Pilihan lain adalah membuat indeks toko kolom yang tidak tercakup pada kolom itu. Keduanya akan meningkatkan kinerja. Jika Anda menggunakan Enterprise, pilihan yang bagus adalah membuat tampilan yang diindeks seperti berikut:Tampilan ini mengembalikan satu baris sehingga hampir tidak membutuhkan ruang. Akan ada penalti ketika melakukan DML tetapi seharusnya tidak jauh berbeda dengan pemeliharaan indeks. Dengan tampilan yang diindeks dalam permainan kueri sekarang membutuhkan 0 ms:
Rencana sebenarnya di sini . Bagian terbaik tentang pendekatan ini adalah runtime tidak diubah oleh ukuran tabel. Satu-satunya hal yang penting adalah berapa banyak baris yang dikembalikan. Misalnya, jika Anda mendapatkan 10.000 baris pertama, permintaan sekarang membutuhkan 18 ms untuk dieksekusi.
Kode untuk mengisi tabel:
sumber
Perbedaan dalam dua pendekatan pertama
Paket pertama menghabiskan sekitar 7 dari 10 detik di operator Window Spool, jadi ini adalah alasan utama sangat lambat. Ini melakukan banyak I / O di tempdb untuk membuat ini. Statistik I / O dan waktu saya terlihat seperti ini:
The Rencana kedua adalah mampu menghindari spool, dan dengan demikian meja kerja seluruhnya. Ini hanya mengambil 10 baris teratas dari indeks berkerumun, dan kemudian apakah loop bersarang bergabung dengan agregasi (jumlah) yang keluar dari pemindaian indeks berkerumun terpisah. Sisi dalam masih berakhir dengan membaca seluruh tabel, tetapi meja ini sangat padat, jadi ini cukup efisien dengan jutaan baris.
Meningkatkan kinerja
Kolom toko
Jika Anda benar-benar menginginkan pendekatan "pelaporan online", toko kolom kemungkinan adalah pilihan terbaik Anda.
Maka pertanyaan ini sangat cepat:
Berikut ini statistik dari mesin saya:
Anda mungkin tidak akan mengalahkan itu (kecuali jika Anda benar - benar pintar - bagus, Joe). Columnstore sangat pandai memindai dan mengagregasi sejumlah besar data.
Menggunakan opsi fungsi jendela
ROW
daripadaRANGE
Anda bisa mendapatkan kinerja yang sangat mirip dengan permintaan kedua Anda dengan pendekatan ini, yang disebutkan dalam jawaban lain, dan yang saya gunakan dalam contoh kolom di atas ( rencana eksekusi ):
Ini menghasilkan lebih sedikit bacaan daripada pendekatan kedua Anda, dan tidak ada aktivitas tempdb vs pendekatan pertama Anda karena spool jendela terjadi di memori :
Sayangnya, runtime hampir sama dengan pendekatan kedua Anda.
Solusi berbasis skema: total async running
Karena Anda terbuka untuk ide-ide lain, Anda dapat mempertimbangkan memperbarui "running total" secara tidak sinkron. Anda bisa secara berkala mengambil hasil dari salah satu pertanyaan ini, dan memuatnya ke tabel "total". Jadi, Anda akan melakukan sesuatu seperti ini:
Muat setiap hari / jam / apa pun (butuh sekitar 2 detik pada mesin saya dengan baris 1mm, dan bisa dioptimalkan):
Maka permintaan pelaporan Anda sangat efisien:
Berikut adalah statistik baca:
Solusi berbasis skema: total baris dengan batasan
Solusi yang sangat menarik untuk hal ini dibahas secara terperinci dalam jawaban untuk pertanyaan ini: Menulis skema bank sederhana: Bagaimana saya harus menjaga saldo saya sinkron dengan riwayat transaksi mereka?
Pendekatan dasar adalah untuk melacak total berjalan saat ini di-baris bersama dengan jumlah total dan urutan berjalan sebelumnya. Kemudian Anda bisa menggunakan batasan untuk memvalidasi total yang berjalan selalu benar dan terbaru.
Penghargaan untuk Paul White karena memberikan contoh implementasi untuk skema dalam T&J ini:
sumber
Ketika berhadapan dengan sekelompok kecil baris yang dikembalikan, gabungan segitiga adalah pilihan yang baik. Namun, saat menggunakan fungsi jendela Anda memiliki lebih banyak opsi yang dapat meningkatkan kinerjanya. Opsi default untuk opsi jendela adalah RANGE, tetapi opsi optimal adalah ROWS. Ketahuilah bahwa perbedaannya bukan hanya dalam kinerja, tetapi juga dalam hasil saat ikatan terlibat.
Kode berikut sedikit lebih cepat daripada yang Anda sajikan.
sumber
ROWS
. Saya mencobanya tetapi saya tidak bisa mengatakan itu lebih cepat dari permintaan saya yang ke-2. Hasilnya adalahCPU time = 1438 ms, elapsed time = 1537 ms.