SQL update satement membutuhkan waktu yang sangat lama / penggunaan disk yang tinggi selama berjam-jam

8

Ya itu terdengar seperti masalah yang sangat umum, tetapi saya belum dapat mempersempitnya.

Jadi saya punya pernyataan UPDATE dalam file batch sql:

UPDATE A
SET A.X = B.X
FROM A JOIN B ON A.B_ID = B.ID

B memiliki catatan 40k, A memiliki catatan 4M dan mereka terkait 1-ke-n melalui A.B_ID, meskipun tidak ada FK di antara keduanya.

Jadi pada dasarnya saya pra-menghitung bidang untuk keperluan penambangan data. Meskipun saya mengubah nama tabel untuk pertanyaan ini, saya tidak mengubah pernyataan, itu sangat sederhana.

Ini membutuhkan waktu berjam-jam untuk berjalan, jadi saya memutuskan untuk membatalkan semuanya. DB rusak, jadi saya menghapusnya, mengembalikan cadangan yang saya lakukan sebelum menjalankan pernyataan dan memutuskan untuk lebih detail dengan kursor:

DECLARE CursorB CURSOR FOR SELECT ID FROM B ORDER BY ID DESC -- Descending order
OPEN CursorB 
DECLARE @Id INT
FETCH NEXT FROM CursorB INTO @Id

WHILE @@FETCH_STATUS = 0
BEGIN
    DECLARE @Msg VARCHAR(50) = 'Updating A for B_ID=' + CONVERT(VARCHAR(10), @Id)
    RAISERROR(@Msg, 10, 1) WITH NOWAIT

    UPDATE A
    SET A.X = B.X
    FROM A JOIN B ON A.B_ID = B.ID
    WHERE B.ID = @Id

    FETCH NEXT FROM CursorB INTO @Id
END

Sekarang saya bisa melihatnya berjalan dengan pesan dengan id turun. Yang terjadi adalah dibutuhkan sekitar 5 menit untuk beralih dari id = 40k ke id = 13

Dan kemudian pada id 13, untuk beberapa alasan, sepertinya hang. DB tidak memiliki koneksi selain SSMS, tetapi sebenarnya tidak digantung:

  • hard drive berjalan terus-menerus sehingga pasti melakukan sesuatu (saya memeriksa di Process Explorer bahwa itu memang proses sqlserver.exe menggunakannya)
  • Saya menjalankan sp_who2, menemukan SPID (70) dari sesi SUSPENDED kemudian menjalankan skrip berikut:

    pilih * dari sys.dm_exec_requests r gabung sys.dm_os_tasks t pada r.session_id = t.session_id di mana r.session_id = 70

Ini memberi saya wait_type, yang merupakan PAGEIOLATCH_SH sebagian besar waktu tetapi sebenarnya kadang-kadang berubah menjadi WRITE_COMPLETION, yang saya kira terjadi ketika sedang membilas log

  • file log, yang 1.6GB ketika saya mengembalikan DB (dan ketika sampai ke id 13), sekarang 3.5GB

Informasi lain yang mungkin bermanfaat:

  • jumlah catatan dalam tabel A untuk B_ID 13 tidak besar (14)
  • Rekan saya tidak memiliki masalah yang sama pada mesinnya, dengan salinan DB ini (dari beberapa bulan yang lalu) dengan struktur yang sama.
  • tabel A sejauh ini adalah tabel terbesar di DB
  • Ini memiliki beberapa indeks, dan beberapa tampilan yang diindeks menggunakannya.
  • Tidak ada pengguna lain di DB, ini lokal dan tidak ada aplikasi yang menggunakannya.
  • Ukuran file LDF tidak terbatas.
  • Model pemulihan SIMPLE, tingkat kompatibilitas 100
  • Procmon tidak memberi saya banyak informasi: sqlserver.exe banyak membaca dan menulis dari MDF dan file LDF.

Saya masih menunggu sampai selesai (sudah 1 jam 30) tapi saya berharap mungkin seseorang akan memberi saya beberapa tindakan lain saya bisa mencoba untuk memecahkan masalah ini.

Diedit: menambahkan ekstrak dari procmon log

15:24:02.0506105    sqlservr.exe    1760    ReadFile    C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\TA.mdf  SUCCESS Offset: 5,498,732,544, Length: 8,192, I/O Flags: Non-cached, Priority: Normal
15:24:02.0874427    sqlservr.exe    1760    WriteFile   C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\TA.mdf  SUCCESS Offset: 6,225,805,312, Length: 16,384, I/O Flags: Non-cached, Write Through, Priority: Normal
15:24:02.0884897    sqlservr.exe    1760    WriteFile   C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\TA_1.LDF    SUCCESS Offset: 4,589,289,472, Length: 8,388,608, I/O Flags: Non-cached, Write Through, Priority: Normal

Dari menggunakan DBCC PAGE tampaknya membaca dan menulis ke bidang yang terlihat seperti tabel A (atau salah satu indeksnya), tetapi untuk B_ID berbeda yang 13. Membangun kembali indeks mungkin?

Diedit 2: rencana eksekusi

Jadi saya membatalkan permintaan (benar-benar menghapus DB dan file-nya lalu mengembalikannya), dan memeriksa rencana eksekusi untuk:

UPDATE A
SET A.X = B.X
FROM A JOIN B ON A.B_ID = B.ID
WHERE B.ID = 13

Rencana pelaksanaan (diperkirakan) sama dengan B.ID apa pun, dan terlihat cukup jelas. Klausa WHERE menggunakan pencarian indeks pada indeks B non-cluster, GABUNG menggunakan indeks pencarian cluster pada kedua PK tabel. Indeks berkerumun mencari pada A menggunakan paralelisme (x7) dan mewakili 90% dari waktu CPU.

Lebih penting lagi, sebenarnya mengeksekusi kueri dengan ID 13 segera.

Diedit 3: fragmentasi indeks

Struktur indeks adalah sebagai berikut:

B memiliki satu PK berkerumun (bukan bidang ID), dan satu indeks unik yang tidak berkerumun, bidang pertama adalah B.ID - indeks kedua ini tampaknya selalu digunakan.

A memiliki satu PK berkerumun (bidang tidak terkait).

Ada juga 7 pandangan tentang A (semua termasuk bidang AXE), masing-masing dengan PK berkerumun sendiri, dan indeks lainnya yang juga mencakup bidang AXE

Pandangan disaring (dengan bidang yang tidak ada dalam persamaan ini), jadi saya ragu ada cara UPDATE A akan menggunakan pandangan itu sendiri. Tetapi mereka memiliki indeks termasuk AX, jadi mengubah AX berarti menulis 7 tampilan dan 7 indeks yang mereka miliki yang menyertakan bidang.

Meskipun UPDATE diharapkan lebih lambat untuk ini, tidak ada alasan mengapa ID tertentu akan jauh lebih lama daripada yang lain.

Saya memeriksa fragmentasi untuk semua indeks, semua berada di <0,1%, kecuali indeks sekunder dari pandangan , semua antara 25% dan 50%. Faktor pengisian untuk semua indeks tampaknya ok, antara 90% dan 95%.

Saya mengatur ulang semua indeks sekunder, dan memutar ulang skrip saya.

Itu masih digantung, tetapi pada titik yang berbeda:

...
(0 row(s) affected)

        Updating A for B_ID=14

(4 row(s) affected)

Padahal sebelumnya, log pesan tampak seperti ini:

...
(0 row(s) affected)

        Updating A for B_ID=14

(4 row(s) affected)

        Updating A for B_ID=13

Ini aneh, karena itu berarti itu bahkan tidak digantung pada titik yang sama di WHILEloop. Sisanya terlihat sama: baris UPDATE yang sama menunggu di sp_who2, tipe tunggu PAGEIOLATCH_EX yang sama dan penggunaan HD berat yang sama dari sqlserver.exe.

Langkah selanjutnya adalah menghapus semua indeks dan tampilan dan saya pikir ulang.

Diedit 4: menghapus lalu membangun kembali indeks

Jadi, saya menghapus semua tampilan terindeks yang saya miliki di atas meja (7 dari mereka, 2 indeks per tampilan termasuk yang berkerumun). Saya menjalankan skrip awal (tanpa kursor), dan itu sebenarnya berjalan dalam 5 menit.

Jadi masalah saya berasal dari keberadaan indeks ini.

Saya membuat ulang indeks saya setelah menjalankan pembaruan, dan butuh 16 menit.

Sekarang saya mengerti indeks membutuhkan waktu untuk membangun kembali, dan saya sebenarnya baik-baik saja dengan tugas lengkap memakan waktu 20 menit.

Apa yang saya masih tidak mengerti adalah, mengapa ketika saya menjalankan pembaruan tanpa menghapus indeks terlebih dahulu, dibutuhkan beberapa jam, tetapi ketika saya menghapusnya terlebih dahulu kemudian membuatnya kembali, dibutuhkan 20 menit. Tidakkah seharusnya memakan waktu yang hampir bersamaan?

GFK
sumber
1
Ada yang ada di log galat SQL Server? Juga dari procmon apa offset dalam file yang sedang ditulisnya? Anda dapat membagi dengan 8.192 untuk mendapatkan halaman dan kemudian gunakan DBCC PAGEuntuk melihat apa yang sedang ditulis.
Martin Smith
3.5GB sepertinya jumlah maksimum RAM yang dapat ditangani oleh windows 32bits .. hazard?
tschmit007
@ MartinSmith Sama sekali tidak ada sejak saya dipulihkan di SSMS SQL Server Logs dan tidak ada yang baik di log peristiwa Windows
GFK
Seperti apa indeks Anda pada tabel A (kolom apa, dll)? Apakah mereka terfragmentasi?
Stuart Ainsworth
@ tschmit007 Edisi Dev SQL 2008 R2 x64 pada Win Server 2008 R2 x64. Ini adalah VM yang menjalankan dirinya pada Hyper-V (host juga 2008 R2 x64); VM memiliki memori fisik 4.2GB yang digunakan dari 5GB, dan 4.6GB melakukan maksimal 10GB; host memiliki memori fisik 7.2GB yang digunakan dari 8GB, dan 7.8 komit dari 16GB maks. Kedua mesin lebih lambat karena penggunaan HD tetapi tidak tersumbat.
GFK

Jawaban:

0
  1. Tetap dengan perintah UPDATE. CURSOR akan lebih lambat untuk apa yang Anda coba lakukan.
  2. Jatuhkan / nonaktifkan semua indeks, termasuk yang untuk tampilan yang diindeks. Jika Anda memiliki kunci asing pada AXE, jatuhkan.
  3. Buat indeks yang hanya akan berisi A.B_ID, dan satu lagi untuk B.ID.
  4. Meskipun Anda menggunakan model Pemulihan Sederhana, transaksi terakhir akan selalu ada dalam log transaksi sebelum dipindahkan ke disk. Itu sebabnya Anda perlu menumbuhkan log transaksi Anda dan mengaturnya untuk tumbuh dengan jumlah yang lebih besar (mis. 100 MB).
  5. Juga, atur pertumbuhan file data ke jumlah yang lebih besar.
  6. Pastikan Anda memiliki ruang disk yang cukup untuk pertumbuhan lebih lanjut dari file log dan data.
  7. Ketika pembaruan selesai buat ulang / aktifkan indeks yang Anda jatuhkan / nonaktifkan pada langkah 2.
  8. Jika Anda tidak membutuhkannya lagi, letakkan indeks yang dibuat pada langkah 3.

Sunting: Karena saya tidak dapat mengomentari kiriman asli Anda, saya akan menjawab di sini pertanyaan Anda dari Sunting 4. Anda memiliki 7 indeks pada Indeks AX adalah B-tree , dan setiap pembaruan ke bidang itu menyebabkan B-tree menyeimbangkan kembali. Lebih cepat membangun kembali indeks tersebut dari awal daripada menyeimbangkannya setiap kali.

bojan
sumber
Untuk poin 1 lihat jawaban saya untuk ik_zelf. Kursor ada di sana untuk alasan investigasi dan tidak memiliki banyak dampak. Saya akan menerapkan sisa saran Anda, saya pikir itu yang harus saya lakukan. Jika berhasil, saya masih akan dibiarkan tanpa penjelasan tentang apa yang terjadi sekarang ...
GFK
Anda dapat memposting DDL untuk tabel Anda (termasuk semua indeks, batasan, dll.). Mungkin ada sesuatu yang memperlambat kinerja Anda dan Anda melewatkannya.
bojan
1
Drop indexes / Update / Rebuild index berfungsi, dan meskipun saya lebih suka tidak harus melakukan sesuatu yang drastis, saya tidak melihat bahwa saya punya pilihan. Terima kasih!
GFK
0

Satu hal yang perlu dilihat adalah sumber daya sistem (Memori, Disk, CPU) selama proses ini. Saya mencoba memasukkan 7 juta baris individu ke dalam satu tabel dalam satu pekerjaan besar dan server saya menggantung dengan cara yang mirip dengan milik Anda.

Ternyata saya tidak memiliki cukup memori pada server saya untuk menjalankan pekerjaan penyisipan massal ini. Dalam situasi seperti ini SQL suka memegang memori dan tidak membiarkannya pergi .... bahkan setelah kata perintah insert mungkin atau mungkin belum selesai. Semakin banyak perintah yang diproses dalam pekerjaan besar, semakin banyak memori yang dimakan. Sebuah reboot cepat membebaskan memori tersebut.

Apa yang akan saya lakukan adalah memulai proses ini dari awal dengan Task Manager Anda berjalan. Jika penggunaan memori mendapatkan lebih dari 75% kemungkinan sistem / proses Anda membekukan skyrockets secara astronomis.

Jika memori / sumber daya Anda memang terbatas seperti yang disebutkan di atas, maka pilihan Anda adalah untuk memotong proses menjadi lebih kecil (dengan reboot sesekali jika penggunaan memori tinggi) alih-alih satu pekerjaan besar atau upgrade ke server 64 bit dengan banyak memori.

Techie Joe
sumber
0

Skenario pembaruan selalu lebih cepat daripada menggunakan prosedur.

Karena Anda memperbarui kolom X dari semua baris di tabel A, pastikan Anda menjatuhkan indeks pada yang pertama. Pastikan juga tidak ada hal-hal seperti pemicu dan kendala aktif di kolom itu.

Memperbarui indeks adalah bisnis yang mahal, seperti memvalidasi kendala dan mengeksekusi pemicu tingkat baris yang melakukan pencarian pada data lain.

ik_zelf
sumber
Saya pikir bukan itu intinya. Saya menyadari bahwa pembaruan catatan yang diindeks membutuhkan waktu, dan saya tahu bahwa, secara keseluruhan, sebagian dari waktu yang diperlukan adalah karena hal ini. Tapi saya mengharapkan ini, dan saya baik-baik saja dengan itu: seperti yang saya katakan, memperbarui 99% dari baris membutuhkan 5 menit (bahkan menggunakan kursor), tetapi untuk beberapa alasan, satu baris (dan tidak selalu sama) membutuhkan 5 jam. Yang membuat saya khawatir adalah perilaku khusus ini.
GFK
kunci tidak masalah yang Anda katakan .... bagaimana dengan pemanfaatan sistem file, mencapai 90% atau lebih tinggi?
ik_zelf
tidak, ini 31GB gratis dari 120GB, jadi saya pikir tidak apa
GFK
apa yang terjadi jika Anda mencoba menyalin tabel seperti membuat tabel a_copy sebagai pilih * dari a;
ik_zelf