Apakah mungkin untuk meningkatkan kinerja permintaan pada tabel sempit dengan jutaan baris?

14

Saya memiliki permintaan yang saat ini sedang menyelesaikan rata-rata 2500 ms. Meja saya sangat sempit, tetapi ada 44 juta baris. Opsi apa yang saya miliki untuk meningkatkan kinerja, atau apakah ini sebagus yang didapat?

The Query

SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31'; 

Meja

CREATE TABLE [dbo].[Heartbeats](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [DeviceID] [int] NOT NULL,
    [IsPUp] [bit] NOT NULL,
    [IsWebUp] [bit] NOT NULL,
    [IsPingUp] [bit] NOT NULL,
    [DateEntered] [datetime] NOT NULL,
 CONSTRAINT [PK_Heartbeats] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

Indeks

CREATE NONCLUSTERED INDEX [CommonQueryIndex] ON [dbo].[Heartbeats] 
(
    [DateEntered] ASC,
    [DeviceID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]

Apakah menambahkan indeks tambahan akan membantu? Jika demikian, akan seperti apa mereka? Kinerja saat ini dapat diterima, karena kueri hanya berjalan sesekali, tetapi saya bertanya-tanya sebagai latihan pembelajaran, adakah yang bisa saya lakukan untuk membuatnya lebih cepat?

MEMPERBARUI

Ketika saya mengubah kueri untuk menggunakan petunjuk indeks gaya, kueri dijalankan dalam 50ms:

SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats] WITH(INDEX(CommonQueryIndex))
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31' 

Menambahkan klausa DeviceID selektif dengan benar juga mencapai kisaran 50ms:

SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31' AND DeviceID = 4;

Jika saya menambahkan ORDER BY [DateEntered], [DeviceID]ke permintaan asli, saya berada di kisaran 50ms:

SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31' 
ORDER BY [DateEntered], [DeviceID];

Ini semua menggunakan indeks yang saya harapkan (CommonQueryIndex) jadi, saya kira pertanyaan saya sekarang, apakah ada cara untuk memaksa indeks ini digunakan pada pertanyaan seperti ini? Atau apakah ukuran meja saya terlalu banyak membuang pengoptimal dan saya harus menggunakan ORDER BYatau memberi petunjuk?

Nate
sumber
Saya kira Anda dapat menambahkan satu lagi indeks non-cluster pada "DateEntered" yang akan meningkatkan kinerja ke tingkat yang lebih tinggi
Praveen
@Praveen Apakah pada dasarnya akan sama dengan indeks saya yang ada? Apakah saya perlu melakukan sesuatu yang istimewa karena akan ada dua indeks pada bidang yang sama?
Nate
@Nate, karena tabel ini disebut detak jantung dan ada 44 juta catatan yang terlibat, saya asumsikan Anda memiliki banyak sisipan di tabel ini? Dengan pengindeksan, Anda hanya dapat menambahkan indeks penutup untuk mempercepat. Tetapi seperti yang Anda sebutkan, Anda hanya menggunakan kueri ini sesekali. Saya akan sangat menyarankan jika Anda melakukan sisipan berat. Ini pada dasarnya menggandakan beban insert Anda. Apakah Anda menjalankan edisi perusahaan?
Edward Dortland
Saya perhatikan bahwa Anda memiliki deviceID di indeks NC Anda. Apakah mungkin untuk memasukkannya dalam klausa where Anda? Dan apakah itu akan menurunkan hasil yang ditetapkan di bawah ambang batas? <35k catatan (tanpa klausa 1000 teratas).
Edward Dortland
1
pertanyaan terakhir, Apakah Anda selalu memasukkan urutan tanggal? Atau dapatkah ini rusak karena perangkat dapat memasukkan async dari satu sama lain. Anda mungkin mencoba mengubah indeks berkerumun ke kolom DateEntered. Halaman cuti indeks Clustered Anda sekarang adalah 445 halaman. Itu akan berlipat ganda, jika Anda beralih dari int ke datetime. Tetapi dalam kasus ini, itu mungkin tidak terlalu buruk.
Edward Dortland

Jawaban:

13

Mengapa pengoptimal tidak sesuai dengan indeks pertama Anda:

CREATE NONCLUSTERED INDEX [CommonQueryIndex] ON [dbo].[Heartbeats] 
(
    [DateEntered] ASC,
    [DeviceID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]

Apakah masalah selektivitas Kolom [DateEntered].

Anda memberi tahu kami bahwa meja Anda memiliki 44 juta baris. ukuran baris adalah:

4 byte, untuk ID, 4 byte untuk Device ID, 8 byte untuk tanggal, dan 1 byte untuk kolom 4 bit. itu 17 byte + 7 byte overhead untuk (tag, bitmap Null, var col offset ,, jumlah col) total 24 Bytes per baris.

Itu akan menerjemahkan secara kasar ke 140 ribu halaman. Untuk menyimpan 44 juta baris itu.

Sekarang pengoptimal dapat melakukan dua hal:

  1. Itu bisa memindai tabel (scan indeks berkerumun)
  2. Atau bisa juga menggunakan indeks Anda. Untuk setiap baris dalam indeks Anda, maka perlu melakukan pencarian bookmark di indeks berkerumun.

Sekarang pada titik tertentu itu menjadi lebih mahal untuk melakukan semua pencarian tunggal dalam indeks berkerumun untuk setiap entri indeks yang ditemukan dalam indeks nonkluster Anda. Ambang batas untuk itu umumnya jumlah total pencarian harus melebihi 25% total 33% dari total jumlah halaman tabel.

Jadi dalam hal ini: 140k / 25% = 35000 baris 140k / 33% = 46666 baris.

(@RBarryYoung, 35k adalah 0,08% dari total baris dan 46666 adalah 0,10%, jadi saya pikir di situlah kebingungan itu)

Jadi, jika klausa Anda di mana akan menghasilkan baris antara 35.000 dan 46666 (ini di bawah klausa teratas!) Sangat mungkin bahwa non-cluster Anda tidak akan digunakan dan bahwa pemindaian indeks berkerumun akan digunakan.

Hanya dua cara untuk mengubahnya adalah:

  1. Jadikan klausa Anda lebih selektif. (jika memungkinkan)
  2. Jatuhkan * dan pilih hanya beberapa kolom sehingga Anda dapat menggunakan indeks penutup.

sekarang yakin Anda dapat membuat indeks penutup bahkan ketika Anda menggunakan pilih *. Tapi itu hanya menciptakan overhead besar untuk memasukkan / memperbarui / menghapus Anda. Kami harus tahu lebih banyak tentang beban kerja Anda (baca vs tulis) untuk memastikan apakah itu solusi terbaik.

Mengubah dari datetime ke smalldatetime adalah pengurangan 16% dalam ukuran pada indeks berkerumun dan pengurangan 24% dalam ukuran pada indeks non-clustered Anda.

Edward Dortland
sumber
ambang pemindaian biasanya jauh lebih rendah dari itu (10% atau bahkan lebih rendah), namun karena rentangnya satu hari dari lebih dari setahun yang lalu, seharusnya tidak mencapai ambang itu. Dan Scan Indeks Clustered tidak diberikan, karena indeks penutup ditambahkan. Karena indeks itu membuat klausa WHERE bisa SARG, maka harus dipilih.
RBarryYoung
@RBarryYoung Saya mencoba menjelaskan mengapa indeks non-cluster pada [EnteredDate], [DeviceID] tidak digunakan di tempat pertama. Mengenai Ambang Batas, saya pikir kami berdua setuju, saya hanya berbicara dari perspektif halaman. Saya akan mengubah jawaban saya untuk membuatnya lebih jelas.
Edward Dortland
Mengubah jawaban untuk memperjelas apa yang saya jawab. Saya tidak bisa menjelaskan mengapa indeks penutup yang disarankan @RBarryYoung tidak digunakan. Saya mengujinya pada sejuta baris di sini, dan optimisasinya menggunakan indeks penutup.
Edward Dortland
Terima kasih atas respons yang sangat komprehensif, sangat masuk akal. Sehubungan dengan beban kerja, tabel memiliki 150-300 sisipan per periode 5 menit dan beberapa bacaan per hari untuk tujuan pelaporan.
Nate
Head overhead untuk indeks penutup tidak terlalu signifikan mengingat bahwa ini adalah tabel sempit dan "penutup" hanyalah tambahan untuk indeks yang sudah ada sebelumnya yang sudah termasuk sebagian besar baris.
RBarryYoung
8

Apakah ada alasan tertentu bahwa PK Anda mengelompok? Banyak orang melakukan ini karena default seperti itu, atau mereka berpikir bahwa PK harus dikelompokkan. Tidak juga. Indeks berkerumun biasanya terbaik untuk kueri rentang (seperti ini) atau pada kunci asing tabel anak.

Efek dari indeks pengelompokan adalah bahwa pengelompokan semua data bersama-sama karena data disimpan pada node daun dari pohon cluster b. Jadi, dengan asumsi bahwa Anda tidak meminta 'terlalu luas' dari suatu rentang, pengoptimal akan tahu dengan pasti bagian b pohon mana yang berisi data dan itu tidak harus menemukan pengidentifikasi baris dan kemudian melompat ke tempat data adalah (seperti halnya ketika berurusan dengan indeks NC). Apa yang 'terlalu luas' dari suatu rentang? Contoh konyol akan meminta data 11 bulan dari tabel yang hanya memiliki catatan satu tahun. Menarik data satu hari seharusnya tidak menjadi masalah, dengan asumsi bahwa statistik Anda mutakhir. (Meskipun, pengoptimal mungkin mendapat masalah jika Anda mencari data kemarin dan Anda belum memperbarui statistik selama tiga hari.)

Karena Anda menjalankan kueri "SELECT *", mesin harus mengembalikan semua kolom dalam tabel (bahkan jika seseorang menambahkan yang baru yang tidak dibutuhkan aplikasi Anda pada saat itu) sehingga indeks yang meliputi atau indeks dengan kolom yang disertakan tidak akan banyak membantu, jika sama sekali. (Jika Anda memasukkan setiap kolom dari tabel dalam indeks, Anda melakukan sesuatu yang salah.) Pengoptimal mungkin akan mengabaikan indeks NC tersebut.

Jadi, apa yang harus dilakukan?

Saran saya adalah untuk menjatuhkan indeks NC, mengubah PK berkerumun menjadi nonclustered dan membuat indeks berkerumun di [DateEntered]. Simpler lebih baik, sampai terbukti sebaliknya.

selat darin
sumber
Dengan asumsi baris dimasukkan dalam urutan yang meningkat, ini adalah jawaban yang paling sederhana - tetapi memasukkan dalam urutan non-linear akan menyebabkan fragmentasi.
Kirk Broadhurst
Menambahkan data ke struktur b-tree apa pun akan menyebabkan kehilangan keseimbangan. Bahkan jika Anda menambahkan baris dalam urutan cluster, indeks akan kehilangan keseimbangan. Mengindeks ulang tabel menghapus fragmentasi, dan setiap DBA akan memberi tahu Anda bahwa tabel perlu diindeks ulang setelah data "cukup" ditambahkan ke tabel. (Definisi "cukup" mungkin diperdebatkan, atau "kapan" mungkin menjadi diskusi.) Saya tidak melihat apa pun dalam pertanyaan yang mengatakan pengindeksan ulang tidak dapat dilakukan karena alasan tertentu.
Selat darin
4

Selama Anda memiliki "*" di sana, maka satu-satunya hal yang dapat saya bayangkan yang akan membuat banyak perbedaan adalah mengubah definisi indeks Anda menjadi ini:

CREATE NONCLUSTERED INDEX [CommonQueryIndex] ON [dbo].[Heartbeats] 
(
    [DateEntered] ASC,
    [DeviceID] ASC
)INCLUDE (ID, IsWebUp, IsPingUp, IsPUp)
 WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]

Seperti yang saya catat di komentar, harus menggunakan indeks itu, tetapi jika tidak, Anda dapat membujuknya dengan ORDER BY atau petunjuk indeks.

RBarryYoung
sumber
Saya baru saja mencoba ini dan saya masih di tempat yang hampir sama, 2500ms menunggu respon server dan 10ms waktu proses klien.
Nate
Posting rencana permintaan.
RBarryYoung
Sepertinya menggunakan Indeks Clustered. (Biaya SELECT: 0% <- Biaya Top: 20% <- Indeks Clustered Scan Biaya PK_Heartbeats: 80%)
Nate
Ya, itu tidak benar, kadang-kadang membuang statistik / pengoptimal. Tambahkan petunjuk untuk memaksanya menggunakan indeks baru.
RBarryYoung
@ Max Vernon: Mungkin, tapi itu seharusnya ditandai pada rencana permintaan.
RBarryYoung
3

Saya akan melihat ini sedikit berbeda.

  • Ya, saya tahu itu utas lama tapi saya tertarik.

Saya akan membuang kolom datetime - ubah ke int. Miliki tabel pencarian atau lakukan konversi untuk kencan Anda.

Dump indeks berkerumun - biarkan sebagai tumpukan dan buat indeks non-berkerumun pada kolom INT baru yang mewakili tanggal. yaitu hari ini adalah 20121015. Perintah itu penting. Bergantung pada seberapa sering Anda memuat tabel, lihat membuat indeks itu dalam urutan DESC. Biaya pemeliharaan akan lebih tinggi dan Anda ingin memperkenalkan faktor pengisian atau partisi. Partisi juga akan membantu mengurangi waktu tayang Anda.

Terakhir, jika Anda bisa menggunakan SQL 2012, coba gunakan SEQUENCE - itu akan mengungguli identitas () untuk menyisipkan.

Jeremy Lowell
sumber
Solusi menarik. Meskipun tidak jelas dari pertanyaan saya, porsi waktu dari DateTime sangat penting. Umumnya saya meminta berdasarkan tanggal, untuk meninjau waktu tertentu selama periode itu. Bagaimana Anda menyesuaikan solusi ini dengan memperhitungkan itu?
Nate
Dalam hal ini, pertahankan kolom datetime, tambahkan kolom int untuk tanggal (karena rentang Anda didasarkan pada elemen tanggal dan bukan elemen waktu). Anda juga dapat mempertimbangkan menggunakan tipe data WAKTU dan kemudian, secara efektif membagi waktu terpisah dari tanggal. Dengan cara itu, jejak data Anda lebih kecil dan Anda masih memiliki elemen Waktu pada kolom.
Jeremy Lowell
1
Saya tidak yakin mengapa saya melewatkan ini sebelumnya tetapi menggunakan kompresi baris pada indeks berkerumun dan indeks juga. Saya baru saja melakukan tes cepat dengan meja Anda dan inilah yang saya temukan: Saya membuat satu set data (5,8 juta baris) pada tabel yang ditentukan di atas. Saya mengompres (baris) indeks yang berkerumun dan tidak tercakup. pembacaan logis, berdasarkan permintaan persis Anda, menurun dari 2.074 menjadi 1.433. Itu adalah penurunan yang signifikan dan saya yakin itu saja akan membantu Anda - dan itu risiko yang sangat rendah.
Jeremy Lowell