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 WHILE
loop. 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?
DBCC PAGE
untuk melihat apa yang sedang ditulis.