Bug kinerja indeks datetime SQL Server 2008

11

Kami menggunakan SQL Server 2008 R2, dan memiliki tabel yang sangat besar (100M + baris) dengan indeks id primer, dan datetimekolom dengan indeks tidak tercakup. Kami melihat beberapa perilaku klien / server yang sangat tidak biasa berdasarkan penggunaan order byklausa khusus pada kolom datetime indeks .

Saya membaca posting berikut: /programming/1716798/sql-server-2008-ordering-by-datetime-is-too-slow tetapi ada lebih banyak hal yang terjadi dengan klien / server daripada apa yang ada mulai dijelaskan di sini.

Jika kami menjalankan kueri berikut (diedit untuk melindungi beberapa konten):

select * 
from [big table] 
where serial_number = [some number] 
order by test_date desc

Permintaan habis waktu setiap waktu. Dalam SQL Server Profiler kueri yang dieksekusi terlihat seperti ini ke server:

exec sp_cursorprepexec @p1 output,@p2 output,NULL,N'select * .....

Sekarang jika Anda memodifikasi kueri, katakan ini:

declare @temp int;
select * from [big table] 
where serial_number = [some number] 
order by test_date desc

SQL Server Profiler memperlihatkan kueri yang dieksekusi seperti ini ke server, dan langsung berfungsi:

exec sp_prepexec @p1 output, NULL, N'declare @temp int;select * from .....

Faktanya, Anda bahkan dapat memberikan komentar kosong ('-;') daripada pernyataan pernyataan yang tidak digunakan dan mendapatkan hasil yang sama. Jadi awalnya kami menunjuk ke sp pra-prosesor sebagai akar penyebab masalah ini, tetapi jika Anda melakukan ini:

select * 
from [big table] 
where serial_number = [some number] 
order by Cast(test_date as smalldatetime) desc

Ini juga bekerja secara instan (Anda dapat menampilkannya sebagai datetimetipe lain ), mengembalikan hasilnya dalam milidetik. Dan profiler menunjukkan permintaan ke server sebagai:

exec sp_cursorprepexec @p1 output, @p2 output, NULL, N'select * from .....

Sehingga agak mengecualikan sp_cursorprepexecprosedur dari penyebab penuh masalah. Tambahkan ke fakta bahwa sp_cursorprepexecini juga disebut ketika tidak ada 'pesanan oleh' digunakan dan hasilnya juga dikembalikan secara instan.

Kami telah sedikit menelusuri Google untuk masalah ini, dan saya melihat masalah serupa diposting dari orang lain, tetapi tidak ada yang memecahnya ke tingkat ini.

Jadi, sudahkah orang lain menyaksikan perilaku ini? Adakah yang punya solusi lebih baik daripada meletakkan SQL yang tidak berarti di depan pernyataan pilih untuk mengubah perilaku? Karena SQL Server harus menjalankan perintah setelah data dikumpulkan, sepertinya ini adalah bug di server yang telah bertahan lama. Kami telah menemukan perilaku ini konsisten di banyak tabel besar kami, dan dapat direproduksi.

Suntingan:

Saya juga harus menambahkan memasukkan forceseekjuga membuat masalah hilang.

Saya harus menambahkan untuk membantu para pencari, kesalahan batas waktu ODBC dilemparkan adalah: [Microsoft] [ODBC SQL Server Driver] Operasi dibatalkan

Ditambahkan 10/12/2012: Masih memburu akar penyebab, (bersama dengan membangun sampel untuk diberikan kepada Microsoft, saya akan mengirim silang hasil apa pun di sini setelah saya kirimkan). Saya telah menggali file jejak ODBC antara kueri yang berfungsi (dengan komentar / pernyataan pernyataan tambahan) dan kueri yang tidak berfungsi. Perbedaan jejak mendasar diposting di bawah ini. Ini terjadi pada panggilan ke panggilan SQLExtendedFetch setelah semua diskusi SQLBindCol selesai. Panggilan gagal dengan kode kembali -1, dan utas induk kemudian memasuki SQLCancel. Karena kami dapat menghasilkan ini dengan driver Native Client dan Legacy ODBC, saya masih menunjuk ke beberapa masalah kompatibilitas di sisi server.

(clip)
MSSQLODBCTester 1664-1718   EXIT  SQLBindCol  with return code 0 (SQL_SUCCESS)
        HSTMT               0x001EEA10
        UWORD                       16 
        SWORD                        1 <SQL_C_CHAR>
        PTR                0x03259030
        SQLLEN                    51
        SQLLEN *            0x0326B820 (0)

MSSQLODBCTester 1664-1718   ENTER SQLExtendedFetch 
        HSTMT               0x001EEA10
        UWORD                        1 <SQL_FETCH_NEXT>
        SQLLEN                     1
        SQLULEN *           0x032677C4
        UWORD *             0x032679B0

MSSQLODBCTester 1664-1fd0   ENTER SQLCancel 
        HSTMT               0x001EEA10

MSSQLODBCTester 1664-1718   EXIT  SQLExtendedFetch  with return code -1 (SQL_ERROR)
        HSTMT               0x001EEA10
        UWORD                        1 <SQL_FETCH_NEXT>
        SQLLEN                     1
        SQLULEN *           0x032677C4
        UWORD *             0x032679B0

        DIAG [S1008] [Microsoft][ODBC SQL Server Driver]Operation canceled (0) 

MSSQLODBCTester 1664-1fd0   EXIT  SQLCancel  with return code 0 (SQL_SUCCESS)
        HSTMT               0x001EEA10

MSSQLODBCTester 1664-1718   ENTER SQLErrorW 
        HENV                0x001E7238
        HDBC                0x001E7B30
        HSTMT               0x001EEA10
        WCHAR *             0x08BFFC5C
        SDWORD *            0x08BFFF08
        WCHAR *             0x08BFF85C 
        SWORD                      511 
        SWORD *             0x08BFFEE6

MSSQLODBCTester 1664-1718   EXIT  SQLErrorW  with return code 0 (SQL_SUCCESS)
        HENV                0x001E7238
        HDBC                0x001E7B30
        HSTMT               0x001EEA10
        WCHAR *             0x08BFFC5C [       5] "S1008"
        SDWORD *            0x08BFFF08 (0)
        WCHAR *             0x08BFF85C [      53] "[Microsoft][ODBC SQL Server Driver]Operation canceled"
        SWORD                      511 
        SWORD *             0x08BFFEE6 (53)

MSSQLODBCTester 1664-1718   ENTER SQLErrorW 
        HENV                0x001E7238
        HDBC                0x001E7B30
        HSTMT               0x001EEA10
        WCHAR *             0x08BFFC5C
        SDWORD *            0x08BFFF08
        WCHAR *             0x08BFF85C 
        SWORD                      511 
        SWORD *             0x08BFFEE6

MSSQLODBCTester 1664-1718   EXIT  SQLErrorW  with return code 100 (SQL_NO_DATA_FOUND)
        HENV                0x001E7238
        HDBC                0x001E7B30
        HSTMT               0x001EEA10
        WCHAR *             0x08BFFC5C
        SDWORD *            0x08BFFF08
        WCHAR *             0x08BFF85C 
        SWORD                      511 
        SWORD *             0x08BFFEE6
(clip)

Menambahkan kasing Microsoft Connect 10/12/2012:

https://connect.microsoft.com/SQLServer/feedback/details/767196/order-by-datetime-in-odbc-fails-for-clean-sql-statements#details

Saya juga harus mencatat bahwa kami memang mencari rencana kueri untuk kueri yang berfungsi dan yang tidak berfungsi. Keduanya digunakan kembali secara tepat berdasarkan jumlah eksekusi. Menyiram paket yang di-cache dan menjalankan kembali tidak mengubah keberhasilan kueri.

DBtheDBA
sumber
Apa yang terjadi jika Anda mencoba select id, test_date from [big table] where serial_number = ..... order by test_date- Saya hanya ingin tahu apakah SELECT *ini berdampak negatif pada kinerja Anda. Jika Anda memiliki indeks nonclustered test_datedan indeks berkerumun id(dengan asumsi itu namanya), kueri ini harus dicakup oleh indeks nonclustered dan karenanya harus kembali cukup cepat
marc_s
Maaf, bagus. Saya seharusnya menyertakan bahwa kami mencoba memodifikasi ruang kolom yang dipilih (menghapus '*', dll) dengan banyak kombinasi. Perilaku yang dijelaskan di atas bertahan melalui perubahan itu.
DBtheDBA
Saya telah menautkan akun saya sekarang ke situs itu. Jika seorang moderator ingin memindahkan posting ke situs itu, saya baik-baik saja. Salah satu pengembang saya menunjukkan situs itu kepada saya setelah saya diposting di sini.
DBtheDBA
Tumpukan klien mana yang digunakan di sini? Tanpa seluruh teks jejak, itu sepertinya masalah. Coba bungkus panggilan asli ke dalam sp_executesqldan lihat apa yang terjadi.
Jon Seigel
1
Seperti apa rencana eksekusi yang lambat itu? Parameter mengendus?
Martin Smith

Jawaban:

6

Tidak ada misteri, Anda mendapatkan rencana yang baik (er) atau (benar) buruk pada dasarnya acak karena tidak ada pilihan pemotongan yang jelas untuk digunakan indeks. Meskipun menarik untuk klausa ORDER BY dan karenanya menghindari pengurutan, Anda indeks non-clustered pada kolom datetime adalah pilihan yang sangat buruk untuk permintaan ini. Indeks yang jauh lebih baik untuk kueri ini adalah indeks aktif (serial_number, test_date). Bahkan lebih baik, ini akan menjadi kandidat yang sangat baik untuk kunci indeks berkerumun .

Sebagai aturan waktu seri harus dikelompokkan oleh kolom waktu, karena mayoritas permintaan tertarik pada rentang waktu tertentu. Jika data juga secara inheren dipartisi pada kolom dengan selektivitas rendah, seperti halnya dengan serial_number Anda, maka kolom ini harus ditambahkan sebagai yang paling kiri dalam definisi kunci yang dikelompokkan.

Remus Rusanu
sumber
Saya agak bingung di sini. Mengapa rencana itu didasarkan pada the orderklausa? Tidakkah rencana harus membatasi diri pada wherepersyaratan karena pemesanan hanya akan terjadi setelah baris diambil? Mengapa server mencoba dan mengurutkan catatan sebelum seluruh hasil ditetapkan?
DBtheDBA
5
Ini juga tidak menjelaskan mengapa menambahkan komentar di awal kueri memengaruhi durasi proses.
cfradenburg
Juga, tabel kami hampir selalu ditanyai dengan nomor seri, bukan test_date. Kami memiliki indeks yang tidak berkerumun di keduanya, dan hanya berkerumun di kolom id dalam tabel. Ini adalah penyimpanan data operasional, dan menambahkan indeks berkerumun di kolom lain hanya akan mendorong pemisahan halaman dan kinerja yang lebih buruk.
DBtheDBA
1
@DBtheDBA: jika Anda ingin membuat klaim untuk 'bug' Anda perlu melakukan penyelidikan dan pengungkapan yang tepat. The tepat skema meja Anda dan statistik diekspor, ikuti Bagaimana menghasilkan skrip dari metadata database yang diperlukan untuk membuat sebuah database statistik-satunya di SQL Server 2005 dan SQL Server 2008 , khususnya semua penting Statistik Script : Statistik Script dan histogram . Tambahkan ini ke info pos bersama dengan langkah-langkah yang mereproduksi masalah.
Remus Rusanu
1
Kami membaca itu sebelumnya selama pencarian kami, dan saya mengerti apa yang Anda katakan, tetapi ada kesalahan mendasar dalam sesuatu yang dilakukan server di sini. Kami telah membangun kembali tabel, dan indeks, dan telah memperbanyaknya di tabel baru. Opsi kompilasi ulang tidak memperbaiki masalah, yang merupakan petunjuk besar ada sesuatu yang salah. Saya tidak ragu bahwa menempatkan indeks berkerumun pada segala hal berpotensi dapat memperbaiki masalah ini, tetapi ini bukan solusi untuk penyebab utama, ini merupakan solusi, dan yang mahal di atas meja besar.
DBtheDBA
0

Dokumentasikan detail cara mereproduksi bug dan kirimkan di connect.microsoft.com. Saya memeriksa dan tidak bisa melihat apa pun di luar sana yang akan terkait dengan ini.

cfradenburg
sumber
Saya akan meminta DBA saya untuk mengetikkan skrip besok untuk menciptakan lingkungan untuk direproduksi. Saya tidak berpikir itu sulit. Saya akan mempostingnya di sini juga jika seseorang tertarik untuk mencobanya sendiri.
DBtheDBA
Posting item terhubung juga ketika dibuka. Dengan begitu jika ada orang lain yang memiliki masalah ini, mereka langsung diarahkan ke sana. Dan siapa pun yang menonton pertanyaan ini mungkin ingin memilih item tersebut agar Microsoft lebih cenderung memperhatikannya.
cfradenburg
0

Hipotesis saya adalah bahwa Anda menjalankan pertentangan dengan cache rencana kueri. (Remus mungkin mengatakan hal yang sama seperti aku, tetapi dengan cara yang berbeda.)

Berikut adalah satu ton detail tentang bagaimana SQL merencanakan caching .

Meliputi detail: Seseorang menjalankan kueri itu sebelumnya, untuk [nomor tertentu] tertentu. SQL melihat nilai yang diberikan, indeks dan statistik untuk tabel / kolom yang relevan, dll. Dan membangun rencana yang bekerja dengan baik untuk [sejumlah angka] tertentu. Kemudian rencana itu di-cache, dijalankan, dan hasilnya dikembalikan ke si penelepon.

Kemudian, orang lain menjalankan kueri yang sama, dengan nilai berbeda [beberapa nomor]. Nilai khusus ini menghasilkan jumlah baris hasil yang sangat berbeda dan mesin harus membuat rencana yang berbeda untuk instance kueri ini. Tetapi tidak bekerja seperti itu. Alih-alih, SQL mengambil kueri dan (kurang lebih) melakukan pencarian case-cache cache yang sensitif, mencari versi kueri yang sudah ada sebelumnya. Ketika menemukan satu dari sebelumnya, ia hanya menggunakan rencana itu.

Idenya adalah menghemat waktu yang diperlukan untuk memutuskan rencana dan membangunnya. Lubang dalam ide adalah ketika kueri yang sama dijalankan dengan nilai-nilai yang menghasilkan hasil yang sangat berbeda. Mereka seharusnya memiliki rencana yang berbeda, tetapi mereka tidak. Siapa pun yang menjalankan kueri pertama-tama membantu mengatur perilaku untuk semua orang yang menjalankannya setelah itu.

Contoh cepat: pilih * dari [orang] di mana nama belakang = 'SMITH' - nama belakang yang sangat populer di AS GO pilih * dari [orang] di mana nama belakang = 'BONAPARTE' - BUKAN nama belakang yang populer di AS

Ketika kueri untuk BONAPARTE dijalankan, paket yang dibuat untuk SMITH akan digunakan kembali. Jika SMITH menyebabkan pemindaian tabel (yang mungkin bagus , jika baris dalam tabel adalah 99% SMITH), maka BONAPARTE juga akan mendapatkan pemindaian tabel. Jika BONAPARTE dijalankan sebelum SMITH, sebuah rencana menggunakan indeks mungkin dibangun dan digunakan, dan kemudian digunakan lagi untuk SMITH (yang mungkin lebih baik dengan pemindaian tabel). Orang-orang mungkin tidak memperhatikan bahwa kinerja untuk SMITH buruk karena mereka mengharapkan kinerja yang buruk karena seluruh tabel harus dibaca dan membaca indeks dan melompat ke meja tidak langsung diperhatikan.

Sehubungan dengan perubahan Anda-yang-harus-mengubah-apa pun, saya curiga bahwa SQL hanya melihat itu sebagai permintaan yang sama sekali berbeda dan membangun rencana baru, khusus untuk nilai Anda [beberapa nomor].

Untuk menguji ini, buat perubahan yang tidak masuk akal pada kueri, seperti menambahkan beberapa spasi antara FOR dan nama tabel, atau beri komentar di bagian akhir. Apakah ini cepat? Jika demikian, itu karena permintaan itu sedikit berbeda dari apa yang ada di cache, jadi SQL melakukan apa yang dilakukan untuk permintaan "baru".

Sebagai solusi, saya akan melihat tiga hal. Pertama, pastikan statistik Anda mutakhir. Ini benar-benar harus menjadi hal pertama yang Anda lakukan ketika kueri tampaknya bertindak aneh atau acak. DBA Anda seharusnya melakukan ini, tetapi hal-hal terjadi. Cara yang biasa untuk memastikan statistik terkini adalah dengan mengindeks ulang tabel Anda, yang tidak selalu merupakan hal yang ringan untuk dilakukan, tetapi ada juga opsi untuk hanya memperbarui statistik.

Hal kedua yang harus dipikirkan adalah menambahkan indeks di sepanjang garis saran Remus. Dengan indeks yang lebih baik / berbeda, satu nilai versus yang lain mungkin lebih stabil dan tidak begitu bervariasi.

Jika itu tidak membantu, hal ketiga untuk dicoba adalah memaksakan rencana baru setiap kali Anda menjalankan pernyataan, menggunakan kata kunci RECOMPILE:

pilih * dari [tabel besar] di mana serial_number = [beberapa nomor] pesan dengan test_date desc OPTION (RECOMPILE)

Ada artikel yang menggambarkan situasi serupa di sini . Terus terang, saya hanya melihat RECOMPILE diterapkan pada prosedur tersimpan sebelumnya, tetapi tampaknya bekerja dengan pernyataan SELECT "biasa". Kimberly Tripp tidak pernah membuat saya salah.

Anda mungkin juga melihat fitur yang disebut " panduan paket ", tetapi lebih rumit dan mungkin berlebihan.

selat darin
sumber
Untuk mencakup beberapa masalah ini: 1. Statistik telah diperbarui, sedang diperbarui. 2. Kami telah mencoba pengindeksan dalam beberapa cara (mencakup indeks, dll) tetapi masalahnya tampaknya lebih terkait dengan order bypenggunaan terhadap indeks waktu khusus. 3. Baru saja mencoba ide Anda dengan opsi RECOMPILE, itu masih gagal, yang sedikit mengejutkan saya, saya berharap itu akan berhasil, walaupun saya tidak tahu apakah itu solusi untuk produksi.
DBtheDBA