Kelompokkan menurut jam melalui dataset besar

12

Menggunakan MS SQL 2008 saya memilih bidang rata-rata dari 2,5 juta catatan. Setiap catatan mewakili satu detik. MyField adalah rata-rata per jam dari catatan 1 detik itu. Tentu saja CPU server mencapai 100% dan pemilihannya terlalu lama. Saya perlu menyimpan nilai rata-rata tersebut sehingga SQL tidak harus memilih semua catatan pada setiap permintaan. Apa yang bisa dilakukan?

  SELECT DISTINCT
         CONVERT(VARCHAR, [timestamp], 1)+' '+ CAST(DATEPART(Hh,[timestamp]) as VARCHAR) AS TimeStampHour,
         MIN([timestamp]) as TimeStamp,
         AVG(MyField) As AvgField
    FROM MyData
   WHERE TimeStamp > '4/10/2011'
GROUP BY CONVERT(VARCHAR, [timestamp], 1)+' '+ CAST(DATEPART(Hh,[timestamp]) as VARCHAR)
ORDER BY TimeStamp

sumber
6
Apakah TimeStamp bagian dari indeks berkerumun? Seharusnya ...
@ antusias - mengapa? dia memaksimalkan CPU bukan disk io
Jack mengatakan coba topanswers.xyz

Jawaban:

5

Bagian dari kueri adalah memaksimalkan CPU untuk jangka waktu yang lama adalah fungsi-fungsi dalam klausa GROUP BY dan fakta bahwa pengelompokan akan selalu membutuhkan pengurutan yang tidak terindeks dalam hal ini. Sementara indeks pada bidang cap waktu akan membantu filter awal operasi ini harus dilakukan pada setiap baris yang cocok dengan filter. Mempercepat ini menggunakan rute yang lebih efisien untuk melakukan pekerjaan yang sama seperti yang disarankan oleh Alex akan membantu, tetapi Anda masih memiliki inefisiensi besar di sana karena kombinasi fungsi apa pun yang Anda gunakan perencana kueri tidak akan dapat muncul dengan sesuatu yang akan dibantu oleh indeks apa pun sehingga harus dijalankan melalui setiap baris terlebih dahulu menjalankan fungsi untuk menghitung nilai pengelompokan, hanya kemudian dapat memesan data dan menghitung agregat atas pengelompokan yang dihasilkan.

Jadi solusinya adalah entah bagaimana membuat grup proses dengan sesuatu yang dapat digunakan indeks, atau menghapus kebutuhan untuk mempertimbangkan semua baris yang cocok sekaligus.

Anda bisa mempertahankan kolom tambahan untuk setiap baris yang berisi waktu dibulatkan ke jam, dan indeks kolom ini untuk digunakan dalam permintaan tersebut. Ini menormalkan data Anda sehingga mungkin terasa "kotor" tetapi itu akan berhasil dan akan lebih bersih daripada melakukan cache semua agregat untuk penggunaan di masa mendatang (dan memperbarui cache itu saat data dasar diubah). Kolom tambahan harus dikelola oleh pemicu atau kolom yang dikomputasi tetap, daripada dikelola oleh logika di tempat lain, karena ini akan menjamin semua tempat saat ini dan masa depan yang mungkin memasukkan data atau memperbarui kolom timestamp atau baris yang ada menghasilkan data yang konsisten di baru kolom. Anda masih bisa mengeluarkan MIN (cap waktu). Apa permintaan akan menghasilkan dengan cara ini masih berjalan di semua baris (ini tidak dapat dihindari, jelas) tetapi dapat melakukannya dengan urutan indeks, mengeluarkan sebuah baris untuk setiap pengelompokan saat mencapai nilai berikutnya dalam indeks daripada harus mengingat seluruh rangkaian baris untuk operasi pengurutan yang tidak indeks sebelum pengelompokan / agregasi dapat dilakukan. Ini akan menggunakan memori jauh lebih sedikit juga, karena tidak perlu mengingat baris dari nilai pengelompokan sebelumnya untuk memproses yang sedang dilihatnya atau sisanya.

Metode itu menghilangkan kebutuhan menemukan di suatu tempat di memori untuk seluruh hasil yang ditetapkan dan melakukan pengurutan tidak terindeks untuk operasi grup dan menghapus perhitungan nilai grup dari kueri besar (memindahkan pekerjaan itu ke individu INSERT / PEMBARUAN yang menghasilkan data) dan harus memungkinkan pertanyaan seperti itu berjalan dengan dapat diterima tanpa perlu mempertahankan penyimpanan terpisah dari hasil agregat.

Metode yang tidakMenormalkan data Anda, tetapi masih membutuhkan struktur tambahan, adalah dengan menggunakan "tabel waktu", dalam hal ini yang berisi satu baris per jam untuk semua waktu yang mungkin Anda pertimbangkan. Tabel ini tidak akan mengkonsumsi ruang yang signifikan dalam DB atau ukuran yang cukup besar - untuk mencakup rentang waktu 100 tahun, tabel yang berisi satu baris dari dua tanggal (awal dan akhir jam, seperti '2011-01-01 @ 00: 00: 00.0000 ',' 2011-01-01 @ 00: 00: 59.9997 ', "9997" adalah jumlah milidetik terkecil, bidang DATETIME tidak akan membulatkan ke detik berikutnya) yang merupakan bagian dari kunci primer yang dikelompokkan akan memakan ruang ~ 14Mbyte (8 + 8 byte per baris * 24 jam / hari * 365,25 hari / tahun * 100, ditambah sedikit untuk overhead struktur pohon indeks berkerumun tetapi overhead itu tidak akan signifikan) .

SELECT CONVERT(VARCHAR, [timestamp], 1)+' '+ CAST(DATEPART(Hh,[timestamp]) as VARCHAR) AS TimeStampHour
     , MIN([timestamp]) as TimeStamp
     , AVG(MyField) As AvgField
FROM TimeRangeByHours tt
INNER JOIN MyData md ON md.TimeStamp BETWEEN tt.StartTime AND tt.EndTime
WHERE tt.StartTime > '4/10/2011'
GROUP BY tt.StartTime
ORDER BY tt.StartTime

Ini berarti bahwa perencana permintaan dapat mengatur indeks pada MyData.TimeStamp untuk digunakan. Perencana kueri harus cukup cerdas untuk bekerja sehingga dapat berjalan di meja jinak sesuai dengan indeks pada MyData.TimeStamp, lagi-lagi mengeluarkan satu baris per pengelompokan dan membuang setiap set atau baris saat menyentuh nilai pengelompokan berikutnya. Tidak menyimpan semua baris perantara di suatu tempat di RAM kemudian melakukan semacam pengindeksan pada mereka. Tentu saja metode ini mengharuskan Anda membuat tabel waktu dan memastikan rentang waktunya cukup jauh ke belakang dan ke depan, tetapi Anda dapat menggunakan tabel waktu untuk kueri terhadap banyak bidang tanggal dalam kueri yang berbeda, di mana sebagai opsi "kolom tambahan" akan membutuhkan kolom tambahan yang dihitung untuk setiap bidang tanggal yang Anda perlukan untuk memfilter / mengelompokkan dengan cara ini, dan ukuran tabel yang kecil (kecuali jika Anda memerlukannya untuk rentang 10,

Metode tabel waktu memiliki perbedaan ekstra (yang bisa sangat menguntungkan) dibandingkan dengan situasi Anda saat ini dan solusi kolom yang dihitung: metode ini dapat mengembalikan baris untuk periode di mana tidak ada data, cukup dengan mengubah INNER JOIN dalam contoh permintaan di atas menjadi LEFT OUTER.

Beberapa orang menyarankan tidak memiliki tabel waktu fisik, tetapi selalu mengembalikannya dari fungsi pengembalian tabel. Ini berarti isi dari tabel waktu tidak pernah disimpan di (atau perlu dibaca dari) disk dan jika fungsi ditulis dengan baik Anda tidak perlu khawatir tentang berapa lama tabel waktu perlu bolak-balik dalam waktu, tapi saya meragukan biaya CPU dalam menghasilkan tabel di-memori untuk beberapa baris setiap query bernilai penghematan kecil dari kerumitan menciptakan (dan mempertahankan, jika rentang waktu perlu melampaui batas versi awal Anda) tabel waktu fisik.

Catatan tambahan: Anda tidak perlu klausa DISTINCT pada kueri asli Anda. Pengelompokan akan memastikan bahwa kueri ini hanya mengembalikan satu baris per periode yang sedang dipertimbangkan sehingga DISTINCT tidak akan melakukan apa pun selain memutar CPU sedikit lebih banyak (kecuali perencana kueri memperhatikan bahwa perbedaan tersebut akan menjadi no-op dalam hal ini akan abaikan saja dan jangan gunakan waktu CPU tambahan).

David Spillett
sumber
3

Lihat pertanyaan ini ( lantai tanggal ) Juga, mengapa repot-repot mengubah segalanya menjadi string - Anda bisa melakukannya nanti (jika perlu).

  SELECT DISTINCT
         dateadd(hour,datediff(hour,0,[timestamp]),0) AS TimeStampHour,
         MIN([timestamp]) as TimeStamp,
         AVG(MyField) As AvgField
    FROM MyData
   WHERE TimeStamp > '4/10/2011'
GROUP BY dateadd(hour,datediff(hour,0,[timestamp],0);
ORDER BY TimeStamp
Hogan
sumber
1

Apakah Anda ingin membuat kueri lebih cepat atau Anda bertanya bagaimana membuat snapshot data dan menyimpannya?

Jika Anda ingin membuatnya lebih cepat, Anda pasti membutuhkan indeks pada bidang TimeStamp. Juga, saya akan menyarankan menggunakan ini untuk mengkonversi ke jam:

select convert(varchar(13), getdate(), 121)

Jika Anda perlu membuat snapshot dan menggunakannya kembali nanti gunakan insert intountuk membuat tabel baru dengan hasil dari permintaan Anda. Tabel indeks sesuai dan gunakan. Dari apa yang saya mengerti Anda akan memerlukan indeks di TimeStampHour.

Anda juga dapat mengatur pekerjaan yang mengumpulkan data harian di tabel agregat baru Anda.

Alex Aza
sumber
-1

Dengan mengonversi grup Anda dengan klausa menjadi string seperti itu, Anda pada dasarnya menjadikannya hit yang tidak tereksekusi untuk setiap baris dalam database. Inilah yang mematikan kinerja Anda. Setiap server yang layak setengah jalan akan mampu menangani agregat sederhana seperti itu pada sejuta catatan jika indeks digunakan dengan benar. Saya akan memodifikasi kueri Anda dan menempatkan indeks berkerumun di cap waktu Anda. Itu akan memecahkan masalah kinerja Anda sedangkan menghitung data setiap jam hanya menunda masalah.


sumber
1
-1 - tidak, Anda tidak "menjadikannya hit yang tidak terindeks ke setiap baris dalam basis data" - indeks apa pun pada TimeStampmasih akan digunakan untuk menyaring baris
Jack mengatakan coba topanswers.xyz
-3

Saya akan mempertimbangkan meninggalkan ide menerapkan perhitungan semacam ini menggunakan model database relasional. Terutama jika Anda memiliki banyak titik data yang Anda kumpulkan nilai setiap detik.

Jika Anda memiliki uang, Anda dapat mempertimbangkan untuk membeli sejarawan data proses khusus seperti:

  1. Keseragaman Honeywell PHD
  2. Osisoft PI
  3. Aspentech IP21
  4. dll.

Produk-produk ini dapat menyimpan sejumlah besar data deret waktu yang sangat padat (dalam format kepemilikan) sambil secara simultan memungkinkan pemrosesan cepat atas permintaan ekstraksi data. Kueri dapat menentukan banyak titik data (juga disebut tag), interval waktu yang lama (bulan / tahun), dan juga dapat melakukan berbagai macam perhitungan data ringkasan (termasuk rata-rata).

.. dan pada catatan umum: Saya selalu berusaha menghindari menggunakan DISTINCTkata kunci saat menulis SQL. Ini bukan ide yang bagus. Dalam kasus Anda, Anda harus bisa menjatuhkan DISTINCTdan mendapatkan hasil yang sama dengan menambahkan klausa MIN([timestamp])Anda GROUP BY.


sumber
1
Ini tidak terlalu akurat. Database relasional sangat baik untuk 2,5 juta catatan. Dan dia bahkan tidak melakukan join di banyak tabel. Indikasi pertama bahwa Anda perlu mendenormalisasi data Anda atau pindah ke sistem non-relasional adalah ketika Anda melakukan penggabungan yang besar dan kompleks di banyak tabel. Kumpulan data poster sebenarnya terdengar seperti penggunaan sistem basis data relasional yang bisa diterima.