Mengoptimalkan rencana dengan pembaca XML

34

Mengeksekusi kueri dari sini untuk menarik acara kebuntuan dari sesi peristiwa diperpanjang standar

SELECT CAST (
    REPLACE (
        REPLACE (
            XEventData.XEvent.value ('(data/value)[1]', 'varchar(max)'),
            '<victim-list>', '<deadlock><victim-list>'),
        '<process-list>', '</victim-list><process-list>')
    AS XML) AS DeadlockGraph
FROM (SELECT CAST (target_data AS XML) AS TargetData
    FROM sys.dm_xe_session_targets st
    JOIN sys.dm_xe_sessions s ON s.address = st.event_session_address
    WHERE [name] = 'system_health') AS Data
CROSS APPLY TargetData.nodes ('//RingBufferTarget/event') AS XEventData (XEvent)
    WHERE XEventData.XEvent.value('@name', 'varchar(4000)') = 'xml_deadlock_report';

membutuhkan waktu sekitar 20 menit untuk menyelesaikan mesin saya. Statistik yang dilaporkan adalah

Table 'Worktable'. Scan count 0, logical reads 68121, physical reads 0, read-ahead reads 0, 
         lob logical reads 25674576, lob physical reads 0, lob read-ahead reads 4332386.

 SQL Server Execution Times:
   CPU time = 1241269 ms,  elapsed time = 1244082 ms.

XML Perencanaan Lambat

Paralel

Jika saya menghapus WHEREklausa itu selesai dalam waktu kurang dari satu detik mengembalikan 3,782 baris.

Demikian pula jika saya menambah OPTION (MAXDOP 1)kueri asli yang mempercepat dengan statistik sekarang menunjukkan lebih sedikit lob dibaca.

Table 'Worktable'. Scan count 0, logical reads 15, physical reads 0, read-ahead reads 0,
                lob logical reads 6767, lob physical reads 0, lob read-ahead reads 6076.

 SQL Server Execution Times:
   CPU time = 639 ms,  elapsed time = 693 ms.

XML Rencana Lebih Cepat

Serial

Jadi pertanyaan saya adalah

Adakah yang bisa menjelaskan apa yang terjadi? Mengapa rencana awal begitu buruk dan apakah ada cara yang dapat diandalkan untuk menghindari masalah?

Tambahan:

Saya juga menemukan bahwa mengubah kueri untuk INNER HASH JOINmeningkatkan hal-hal sampai batas tertentu (tapi masih membutuhkan> 3 menit) karena hasil DMV sangat kecil, saya ragu bahwa tipe Bergabung itu sendiri yang bertanggung jawab dan menganggap ada sesuatu yang lain harus berubah. Statistik untuk itu

Table 'Worktable'. Scan count 0, logical reads 30294, physical reads 0, read-ahead reads 0, 
          lob logical reads 10741863, lob physical reads 0, lob read-ahead reads 4361042.

 SQL Server Execution Times:
   CPU time = 200914 ms,  elapsed time = 203614 ms.

(Dan rencanakan)

Setelah mengisi buffer cincin peristiwa diperpanjang ( DATALENGTHdari XML4.880.045 byte dan berisi 1.448 peristiwa.) Dan menguji versi pengurangan dari permintaan asli dengan dan tanpa MAXDOPpetunjuk.

SELECT COUNT(*)
FROM   (SELECT CAST (target_data AS XML) AS TargetData
        FROM   sys.dm_xe_session_targets st
               JOIN sys.dm_xe_sessions s
                 ON s.address = st.event_session_address
        WHERE  [name] = 'system_health') AS Data
       CROSS APPLY TargetData.nodes ('//RingBufferTarget/event') AS XEventData (XEvent)
WHERE  XEventData.XEvent.value('@name', 'varchar(4000)') = 'xml_deadlock_report'

SELECT*
FROM   sys.dm_db_task_space_usage
WHERE  session_id = @@SPID 

Memberi hasil berikut

+-------------------------------------+------+----------+
|                                     | Fast |   Slow   |
+-------------------------------------+------+----------+
| internal_objects_alloc_page_count   |  616 |  1761272 |
| internal_objects_dealloc_page_count |  616 |  1761272 |
| elapsed time (ms)                   |  428 |   398481 |
| lob logical reads                   | 8390 | 12784196 |
+-------------------------------------+------+----------+

Ada perbedaan yang jelas dalam alokasi tempdb dengan yang lebih cepat menunjukkan 616halaman yang dialokasikan dan tidak dialokasikan. Ini adalah jumlah halaman yang sama yang digunakan ketika XML dimasukkan ke dalam variabel juga.

Untuk rencana lambat, jumlah alokasi halaman ini menjadi jutaan. Polling dm_db_task_space_usagesementara kueri sedang berjalan menunjukkan sepertinya terus-menerus mengalokasikan dan membatalkan alokasi halaman di tempdbmana saja antara 1.800 dan 3.000 halaman dialokasikan pada satu waktu.

Martin Smith
sumber
Anda bisa memindahkan WHEREklausa ke ekspresi XQuery; logika tidak harus dihapus untuk itu untuk pergi cepat: TargetData.nodes ('RingBufferTarget[1]/event[@name = "xml_deadlock_report"]'). Yang mengatakan, saya tidak tahu XML internal cukup baik untuk menjawab pertanyaan yang Anda ajukan.
Jon Seigel
Paging @SQLPoolBoy untuk Anda Martin ... ia menyarankan untuk pergi melalui komentar di sini di mana ia memiliki saran yang lebih efisien (mereka didasarkan pada artikel sumber untuk kode di atas ).
Aaron Bertrand

Jawaban:

36

Alasan untuk perbedaan kinerja terletak pada bagaimana ekspresi skalar ditangani di mesin eksekusi. Dalam hal ini, ungkapan minat adalah:

[Expr1000] = CONVERT(xml,DM_XE_SESSION_TARGETS.[target_data],0)

Label ekspresi ini didefinisikan oleh operator Compute Scalar (simpul 11 ​​dalam rencana seri, simpul 13 dalam rencana paralel). Compute Scalar operator berbeda dari operator lain (SQL Server 2005 dan seterusnya) dalam ekspresi yang mereka tetapkan tidak perlu dievaluasi pada posisi mereka muncul dalam rencana eksekusi yang terlihat; evaluasi dapat ditunda sampai hasil perhitungan diperlukan oleh operator yang lebih baru.

Dalam kueri ini, target_datastring biasanya besar, membuat konversi dari string menjadi XMLmahal. Dalam rencana lambat, string ke XMLkonversi dilakukan setiap kali operator selanjutnya yang membutuhkan hasil Expr1000rebound.

Rebinding terjadi pada sisi dalam loop bersarang bergabung ketika parameter berkorelasi (referensi luar) berubah. Expr1000adalah referensi luar untuk sebagian besar loop bersarang bergabung dalam rencana eksekusi ini. Ekspresi direferensikan beberapa kali oleh beberapa Pembaca XML, Agregat Stream, dan oleh Filter start-up. Bergantung pada ukuran XML, berapa kali string dikonversi ke XMLdapat dengan mudah dalam jutaan.

Tumpukan panggilan di bawah ini menunjukkan contoh target_datastring yang akan dikonversi XML( ConvertStringToXMLForES- di mana ES adalah Layanan Ekspresi ):

Filter Penyalaan

Start-up Filter tumpukan panggilan

Pembaca XML (Streaming TVF secara internal)

Tumpukan panggilan Streaming TVF

Agregat Aliran

Stream tumpukan panggilan agregat

Mengubah string ke XMLsetiap kali salah satu operator ini menjelaskan perbedaan kinerja yang diamati dengan rencana loop bersarang. Ini terlepas dari apakah paralelisme digunakan atau tidak. Kebetulan pengoptimal memilih hash bergabung ketika MAXDOP 1petunjuk ditentukan. Jika MAXDOP 1, LOOP JOINditentukan, kinerja buruk seperti halnya rencana paralel default (di mana pengoptimal memilih loop bersarang).

Berapa banyak peningkatan kinerja dengan hash join tergantung pada apakah Expr1000muncul di sisi build atau probe operator. Kueri berikut menempatkan ekspresi di sisi probe:

SELECT CAST (
    REPLACE (
        REPLACE (
            XEventData.XEvent.value ('(data/value)[1]', 'varchar(max)'),
            '<victim-list>', '<deadlock><victim-list>'),
        '<process-list>', '</victim-list><process-list>')
    AS XML) AS DeadlockGraph
FROM (SELECT CAST (target_data AS XML) AS TargetData
    FROM sys.dm_xe_sessions s
    INNER HASH JOIN sys.dm_xe_session_targets st ON s.address = st.event_session_address
    WHERE [name] = 'system_health') AS Data
CROSS APPLY TargetData.nodes ('//RingBufferTarget/event') AS XEventData (XEvent)
WHERE XEventData.XEvent.value('@name', 'varchar(4000)') = 'xml_deadlock_report';

Saya telah membalik urutan tertulis gabungan dari versi yang ditunjukkan dalam pertanyaan, karena petunjuk gabungan (di INNER HASH JOINatas) juga memaksa urutan untuk seluruh kueri, sama seperti jika FORCE ORDERtelah ditentukan. Pembalikan diperlukan untuk memastikan Expr1000muncul di sisi probe. Bagian yang menarik dari rencana eksekusi adalah:

petunjuk 1

Dengan ekspresi yang ditentukan di sisi probe, nilainya di-cache:

Cache Hash

Evaluasi Expr1000masih ditunda sampai operator pertama membutuhkan nilai (filter start-up dalam jejak stack di atas) tetapi nilai yang dihitung di-cache ( CValHashCachedSwitch) dan digunakan kembali untuk panggilan selanjutnya oleh Pembaca XML dan Agregat Stream. Jejak tumpukan di bawah ini menunjukkan contoh nilai cache yang digunakan kembali oleh Pembaca XML.

Penggunaan ulang cache

Ketika perintah gabungan dipaksa sedemikian rupa sehingga definisi Expr1000terjadi pada sisi build hash bergabung, situasinya berbeda:

SELECT CAST (
    REPLACE (
        REPLACE (
            XEventData.XEvent.value ('(data/value)[1]', 'varchar(max)'),
            '<victim-list>', '<deadlock><victim-list>'),
        '<process-list>', '</victim-list><process-list>')
    AS XML) AS DeadlockGraph
FROM (SELECT CAST (target_data AS XML) AS TargetData
    FROM sys.dm_xe_session_targets st 
    INNER HASH JOIN sys.dm_xe_sessions s ON s.address = st.event_session_address
    WHERE [name] = 'system_health') AS Data
CROSS APPLY TargetData.nodes ('//RingBufferTarget/event') AS XEventData (XEvent)
WHERE XEventData.XEvent.value('@name', 'varchar(4000)') = 'xml_deadlock_report'

Hash 2

Gabung hash membaca input build sepenuhnya untuk membuat tabel hash sebelum mulai mencari kecocokan. Akibatnya, kita harus menyimpan semua nilai, bukan hanya satu per utas yang sedang dikerjakan dari sisi penyelidikan paket. Karenanya, hash join menggunakan tempdbtabel kerja untuk menyimpan XMLdata, dan setiap akses ke hasil Expr1000oleh operator yang kemudian membutuhkan perjalanan yang mahal ke tempdb:

Akses lambat

Berikut ini menunjukkan rincian lebih lanjut dari jalur akses lambat:

Detail lambat

Jika gabungan bergabung dipaksa, baris input diurutkan (operasi pemblokiran, seperti input build ke hash bergabung) menghasilkan pengaturan yang sama di mana akses lambat melalui tempdbmeja kerja yang dioptimalkan diurutkan diperlukan karena ukuran data.

Paket yang memanipulasi item data besar bisa menjadi masalah karena segala macam alasan yang tidak terlihat dari rencana eksekusi. Menggunakan hash join (dengan ekspresi pada input yang benar) bukanlah solusi yang baik. Itu bergantung pada perilaku internal tidak berdokumen tanpa jaminan itu akan bekerja dengan cara yang sama minggu depan, atau pada permintaan yang sedikit berbeda.

Pesannya adalah XMLmanipulasi dapat menjadi hal rumit untuk dioptimalkan hari ini. Menuliskan XMLke variabel atau tabel sementara sebelum memotong-motong adalah solusi yang jauh lebih solid daripada apa pun yang ditunjukkan di atas. Salah satu cara untuk melakukan ini adalah:

DECLARE @data xml =
        CONVERT
        (
            xml,
            (
            SELECT TOP (1)
                dxst.target_data
            FROM sys.dm_xe_sessions AS dxs 
            JOIN sys.dm_xe_session_targets AS dxst ON
                dxst.event_session_address = dxs.[address]
            WHERE 
                dxs.name = N'system_health'
                AND dxst.target_name = N'ring_buffer'
            )
        )

SELECT XEventData.XEvent.value('(data/value)[1]', 'varchar(max)')
FROM @data.nodes ('./RingBufferTarget/event[@name eq "xml_deadlock_report"]') AS XEventData (XEvent)
WHERE XEventData.XEvent.value('@name', 'varchar(4000)') = 'xml_deadlock_report';

Akhirnya, saya hanya ingin menambahkan grafik Martin yang sangat bagus dari komentar di bawah ini:

Grafik Martin

Paul White mengatakan GoFundMonica
sumber
Penjelasan yang bagus, terima kasih. Saya telah membaca artikel Anda tentang menghitung skalar juga, tetapi tidak menempatkan dua dan dua di sini.
Martin Smith
3
Saya pasti telah mengacaukan sesuatu dengan upaya saya untuk membuat profil kemarin (mungkin mengacak jejak lambat dan cepat!). Saya telah memperbaikinya hari ini dan tentu saja itu hanya menunjukkan apa yang sudah Anda katakan.
Martin Smith
2
Ya tangkapan layar adalah laporan Call Tree View dari profiler Visual Studio 2012 . Saya pikir nama metode terlihat jauh lebih jelas dalam output Anda meskipun tanpa string misterius seperti @@IEAAXPEA_Kmuncul.
Martin Smith
10

Itulah kode dari artikel saya yang semula diposting di sini:

http://www.sqlservercentral.com/articles/deadlock/65658/

Jika Anda membaca komentar, Anda akan menemukan beberapa alternatif yang tidak memiliki masalah kinerja yang Anda alami, satu menggunakan modifikasi dari permintaan asli itu, dan yang lainnya menggunakan variabel untuk menahan XML sebelum memprosesnya yang berhasil lebih baik. (Lihat komentar saya di Halaman 2) XML dari DMV bisa lambat untuk diproses, seperti juga dapat mem-parsing XML dari DMF untuk target file yang sering lebih baik dicapai dengan membaca data ke tabel temp terlebih dahulu dan kemudian memprosesnya. XML dalam SQL lambat dibandingkan dengan menggunakan hal-hal seperti .NET atau SQLCLR.

Jonathan Kehayias
sumber
1
Terima kasih! Itu berhasil. Yang tanpa variabel mengambil 600ms dan 6341 membaca dan dengan variabel 303 msdan 3249 lob reads. Pada 2012 saya juga perlu menambahkan and target_name='ring_buffer'versi itu karena sepertinya ada dua target sekarang. Saya masih mencoba untuk mendapatkan gambaran mental tentang apa sebenarnya yang dilakukan dalam versi 20 menit.
Martin Smith