Kami menggunakan SQL Server 2008 R2, dan memiliki tabel yang sangat besar (100M + baris) dengan indeks id primer, dan datetime
kolom dengan indeks tidak tercakup. Kami melihat beberapa perilaku klien / server yang sangat tidak biasa berdasarkan penggunaan order by
klausa 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 datetime
tipe 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_cursorprepexec
prosedur dari penyebab penuh masalah. Tambahkan ke fakta bahwa sp_cursorprepexec
ini 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 forceseek
juga 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:
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.
sumber
select id, test_date from [big table] where serial_number = ..... order by test_date
- Saya hanya ingin tahu apakahSELECT *
ini berdampak negatif pada kinerja Anda. Jika Anda memiliki indeks nonclusteredtest_date
dan indeks berkerumunid
(dengan asumsi itu namanya), kueri ini harus dicakup oleh indeks nonclustered dan karenanya harus kembali cukup cepatsp_executesql
dan lihat apa yang terjadi.Jawaban:
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.
sumber
the order
klausa? Tidakkah rencana harus membatasi diri padawhere
persyaratan karena pemesanan hanya akan terjadi setelah baris diambil? Mengapa server mencoba dan mengurutkan catatan sebelum seluruh hasil ditetapkan?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.
sumber
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.
sumber
order by
penggunaan 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.