การปรับแผนให้เหมาะสมด้วยตัวอ่าน XML


34

ดำเนินการค้นหาจากที่นี่เพื่อดึงเหตุการณ์ deadlock ออกจากเซสชันเพิ่มเติมของเหตุการณ์ที่ขยายเริ่มต้น

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';

ใช้เวลาประมาณ 20 นาทีจึงจะเสร็จสิ้นในเครื่องของฉัน สถิติที่รายงานคือ

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 ช้า

ขนาน

ถ้าฉันลบส่วนWHEREคำสั่งมันจะเสร็จสมบูรณ์ภายในเวลาน้อยกว่าหนึ่งวินาทีที่ส่งคืน 3,782 แถว

ในทำนองเดียวกันถ้าฉันเพิ่มลงOPTION (MAXDOP 1)ในคิวรีดั้งเดิมที่เร่งความเร็วให้ดีขึ้นด้วยสถิติขณะนี้แสดงว่าลูกเทนนิสอ่านน้อยลงอย่างมาก

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 ที่เร็วขึ้น

อนุกรม

ดังนั้นคำถามของฉันคือ

ใครสามารถอธิบายสิ่งที่เกิดขึ้น? เหตุใดแผนดั้งเดิมจึงเลวร้ายยิ่งและมีวิธีที่เชื่อถือได้ในการหลีกเลี่ยงปัญหา

นอกจากนี้:

ฉันยังพบว่าการเปลี่ยนแบบสอบถามเพื่อINNER HASH JOINปรับปรุงบางสิ่งบางอย่าง (แต่ยังคงใช้เวลา> 3 นาที) เนื่องจากผลลัพธ์ DMV มีขนาดเล็กมากฉันสงสัยว่าตัวเองประเภทเข้าร่วมเป็นผู้รับผิดชอบแต่ทว่าและต้องมีการเปลี่ยนแปลงอย่างอื่น สถิติสำหรับสิ่งนั้น

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.

(และวางแผน)

หลังจากเติมบัฟเฟอร์วงแหวนเหตุการณ์ที่ขยายออกมา ( DATALENGTHจากนั้นXMLคือ 4,880,045 ไบต์และมีกิจกรรม 1,448 รายการ) และทดสอบเวอร์ชันต้นฉบับที่ตัดลงของคิวรีดั้งเดิมที่มีและไม่มีMAXDOPคำใบ้

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 

ให้ผลลัพธ์ดังต่อไปนี้

+-------------------------------------+------+----------+
|                                     | 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 |
+-------------------------------------+------+----------+

มีความแตกต่างที่ชัดเจนในการจัดสรร tempdb โดยเร็วกว่าหนึ่ง616หน้าแสดงได้รับการจัดสรรและยกเลิกการจัดสรร นี่คือจำนวนหน้าเท่ากันที่ใช้เมื่อ XML ถูกใส่ลงในตัวแปรด้วย

สำหรับแผนช้าจำนวนหน้าเหล่านี้นับการจัดสรรเป็นล้าน การสำรวจdm_db_task_space_usageในขณะที่เรียกใช้แบบสอบถามแสดงว่าดูเหมือนว่าจะมีการจัดสรรและยกเลิกการจัดสรรหน้าอย่างต่อเนื่องในtempdbทุก ๆ ที่ระหว่าง 1,800 ถึง 3,000 หน้าในแต่ละครั้ง


คุณสามารถย้ายWHEREประโยคไปที่นิพจน์ XQuery; ตรรกะไม่จำเป็นต้องถูกลบออกเพื่อให้มันไปอย่างรวดเร็ว: TargetData.nodes ('RingBufferTarget[1]/event[@name = "xml_deadlock_report"]'). ที่กล่าวว่าฉันไม่ทราบว่า XML internals ดีพอที่จะตอบคำถามที่คุณโพสต์
Jon Seigel

Paging @SQLPoolBoy สำหรับคุณ Martin ... เขาแนะนำให้อ่านความคิดเห็นที่นี่ซึ่งเขามีคำแนะนำที่มีประสิทธิภาพมากขึ้น (พวกเขาอ้างอิงจากซอร์สโค้ดของโค้ดด้านบน )
Aaron Bertrand

คำตอบ:


36

สาเหตุของความแตกต่างด้านประสิทธิภาพนั้นขึ้นอยู่กับวิธีจัดการนิพจน์สเกลาร์ในเครื่องมือการดำเนินการ ในกรณีนี้การแสดงออกของความสนใจคือ:

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

เลเบลนิพจน์นี้ถูกกำหนดโดยตัวดำเนินการคำนวณ Scalar (โหนด 11 ในแผนอนุกรมโหนด 13 ในแผนขนาน) ตัวดำเนินการคำนวณ Scalar นั้นแตกต่างจากตัวดำเนินการอื่น (SQL Server 2005 เป็นต้นไป) ซึ่งนิพจน์ที่พวกเขากำหนดนั้นไม่จำเป็นต้องประเมินตำแหน่งที่ปรากฏในแผนปฏิบัติการที่มองเห็นได้ การประเมินสามารถเลื่อนออกไปได้จนกว่าผลลัพธ์ของการคำนวณจะต้องดำเนินการในภายหลัง

ในแบบสอบถามปัจจุบันtarget_dataสตริงมักมีขนาดใหญ่ทำให้การแปลงจากสตริงเป็นXMLราคาแพง ในแผนช้าสตริงเพื่อXMLการแปลงจะดำเนินการทุกครั้งที่ผู้ประกอบการในภายหลังที่ต้องใช้ผลที่ได้Expr1000คือการตอบสนอง

การ Rebinding เกิดขึ้นที่ด้านในของลูปซ้อนกันเมื่อพารามิเตอร์ที่สัมพันธ์กัน (การอ้างอิงภายนอก) เปลี่ยนแปลง Expr1000เป็นการอ้างอิงภายนอกสำหรับลูปซ้อนกันส่วนใหญ่ในแผนการดำเนินการนี้ นิพจน์นั้นถูกอ้างถึงหลายครั้งโดยผู้อ่าน XML หลายคนทั้งสตรีมรวมและโดยตัวกรองเริ่มต้นขึ้น XMLจำนวนครั้งที่สตริงถูกแปลงเป็นXMLจำนวนในล้านได้อย่างง่ายดายทั้งนี้ขึ้นอยู่กับขนาดของ

สแต็คการโทรด้านล่างแสดงตัวอย่างของtarget_dataสตริงที่ถูกแปลงเป็นXML( ConvertStringToXMLForES- โดยที่ ES คือExpression Service ):

ตัวกรองเริ่มต้นขึ้น

ตัวกรองการโทรเริ่มต้นขึ้น

XML Reader (TVF Stream ภายใน)

สแต็กการโทรสตรีมของ TVF

รวมสตรีม

สตรีมรวมการโทรสแต็ก

การแปลงสตริงเป็นXMLทุกครั้งที่ตัวดำเนินการใด ๆ ที่เชื่อมโยงใหม่อธิบายความแตกต่างของประสิทธิภาพที่สังเกตได้ด้วยแผนการลูปซ้อนกัน สิ่งนี้ไม่ว่าจะใช้การขนานหรือไม่ก็ตาม มันจะเกิดขึ้นเมื่อเครื่องมือเพิ่มประสิทธิภาพเลือกแฮชเข้าร่วมเมื่อMAXDOP 1มีการระบุคำใบ้ หากMAXDOP 1, LOOP JOINระบุไว้ประสิทธิภาพจะไม่ดีเช่นเดียวกับแผนขนานเริ่มต้น (โดยที่เครื่องมือเพิ่มประสิทธิภาพเลือกลูปซ้อนกัน)

จำนวนประสิทธิภาพที่เพิ่มขึ้นเมื่อมีการรวมแฮชจะขึ้นอยู่กับว่าExpr1000จะปรากฏขึ้นที่ด้านการสร้างหรือโพรบของตัวดำเนินการ เคียวรีต่อไปนี้หาตำแหน่งนิพจน์ทางด้านโพรบ:

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';

ฉันได้กลับคำสั่งเขียนของการรวมจากเวอร์ชันที่แสดงในคำถามเนื่องจากคำแนะนำการเข้าร่วม ( INNER HASH JOINด้านบน) บังคับให้คำสั่งทั้งหมดสำหรับการสืบค้นเช่นเดียวกับที่FORCE ORDERได้ระบุไว้ การกลับรายการจำเป็นเพื่อให้แน่ใจว่าExpr1000จะปรากฏขึ้นที่ด้านโพรบ ส่วนที่น่าสนใจของแผนการดำเนินการคือ:

คำใบ้ 1

ด้วยนิพจน์ที่กำหนดไว้ที่ด้านโพรบค่าจะถูกแคช:

แคชแฮช

การประเมินผลExpr1000จะยังคงเลื่อนไปจนกว่าผู้ดำเนินการรายแรกจะต้องการค่า (ตัวกรองเริ่มต้นในการติดตามสแต็กด้านบน) แต่ค่าที่คำนวณได้ถูกแคช ( CValHashCachedSwitch) และนำมาใช้ใหม่สำหรับการโทรในภายหลังโดย XML Readers และ Stream Aggregates การติดตามสแต็กด้านล่างแสดงตัวอย่างของค่าแคชที่นำมาใช้ซ้ำโดย XML Reader

แคชใช้ซ้ำ

เมื่อลำดับการเข้าร่วมถูกบังคับเพื่อให้คำจำกัดความของการExpr1000เกิดขึ้นในด้านการสร้างของการเข้าร่วมแฮชสถานการณ์จะแตกต่างกัน:

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'

แฮช 2

การเข้าร่วมแฮชอ่านอินพุตการสร้างของมันอย่างสมบูรณ์เพื่อสร้างตารางแฮชก่อนที่จะเริ่มต้นการตรวจสอบการจับคู่ เป็นผลให้เราต้องเก็บค่าทั้งหมดไม่เพียงหนึ่งต่อเธรดที่กำลังทำงานจากด้านการสอบสวนของแผน การเข้าร่วมแฮชจึงใช้tempdbตารางงานเพื่อจัดเก็บXMLข้อมูลและการเข้าถึงผลลัพธ์ของExpr1000ผู้ดำเนินการในภายหลังทุกครั้งต้องใช้การเดินทางที่มีค่าใช้จ่ายสูงเพื่อtempdb:

เข้าถึงช้า

ต่อไปนี้จะแสดงรายละเอียดเพิ่มเติมของเส้นทางการเข้าถึงช้า:

รายละเอียดช้า

หากการรวมการผสานถูกบังคับแถวการป้อนข้อมูลจะถูกจัดเรียง (การดำเนินการบล็อกเช่นเดียวกับการสร้างการป้อนข้อมูลเพื่อการเข้าร่วมแฮช) ทำให้เกิดการจัดเรียงที่คล้ายกันซึ่งการเข้าถึงช้าผ่านtempdbโต๊ะทำงานที่เหมาะสมที่สุด

แผนการจัดการรายการข้อมูลขนาดใหญ่อาจเป็นปัญหาได้ด้วยเหตุผลทุกประเภทที่ไม่ชัดเจนจากแผนการดำเนินการ การใช้แฮชเข้าร่วม (ด้วยนิพจน์ในอินพุตที่ถูกต้อง) ไม่ใช่วิธีแก้ปัญหาที่ดี มันขึ้นอยู่กับพฤติกรรมภายในที่ไม่มีเอกสารและไม่มีการรับประกันว่ามันจะทำงานในลักษณะเดียวกันในสัปดาห์หน้าหรือใช้แบบสอบถามที่แตกต่างกันเล็กน้อย

ข้อความคือการXMLจัดการอาจเป็นเรื่องยุ่งยากในการเพิ่มประสิทธิภาพในวันนี้ การเขียนXMLลงในตัวแปรหรือตารางชั่วคราวก่อนที่จะทำลายเอกสารเป็นวิธีแก้ปัญหาที่มั่นคงกว่าสิ่งใด ๆ วิธีหนึ่งในการทำเช่นนี้คือ:

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';

ในที่สุดฉันแค่ต้องการเพิ่มกราฟิกที่ดีมากของ Martin จากความคิดเห็นด้านล่าง:

กราฟิกของมาร์ติน


คำอธิบายที่ดีขอบคุณ ฉันได้อ่านบทความของคุณเกี่ยวกับสเกลาร์คำนวณ แต่ไม่รวมสองและสองเข้าด้วยกันที่นี่
Martin Smith

3
ฉันต้องทำให้บางสิ่งบางอย่างสับสนเมื่อฉันพยายามทำโปรไฟล์เมื่อวานนี้ (อาจทำให้ง่วงช้าและเร็วมาก!) ฉันได้ทำมันใหม่ในวันนี้และแน่นอนมันแค่แสดงสิ่งที่คุณพูดไปแล้ว
Martin Smith

2
ใช่ภาพหน้าจอเป็นโทรต้นไม้ดูรายงานจากVisual Studio 2012 Profiler ฉันคิดว่าชื่อวิธีการดูชัดเจนมากขึ้นในการส่งออกของคุณ แต่ไม่มีสายลึกลับเช่นการ@@IEAAXPEA_Kปรากฏ
Martin Smith

10

นั่นคือรหัสจากบทความของฉันโพสต์ที่นี่:

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

หากคุณอ่านความคิดเห็นคุณจะพบทางเลือกสองทางที่ไม่มีปัญหาด้านประสิทธิภาพที่คุณกำลังประสบอยู่โดยใช้การแก้ไขแบบสอบถามต้นฉบับนั้นและอีกรายการใช้ตัวแปรเพื่อเก็บ XML ไว้ก่อนที่จะประมวลผล ดีกว่า (ดูความคิดเห็นของฉันในหน้า 2) XML จาก DMV สามารถช้าในการประมวลผลเช่นเดียวกับการแยกวิเคราะห์ XML จาก DMF สำหรับเป้าหมายไฟล์ซึ่งมักจะทำได้ดีกว่าโดยการอ่านข้อมูลลงในตารางชั่วคราวก่อนจากนั้นทำการประมวลผล XML ใน SQL ช้ากว่าเมื่อเปรียบเทียบกับการใช้สิ่งต่าง ๆ เช่น. NET หรือ SQLCLR


1
ขอบคุณ! นั่นเป็นการหลอกลวง หนึ่งโดยไม่ต้องสละ 600ms ตัวแปรและ 6341 อ่านและมีตัวแปรและ303 ms 3249 lob readsในปี 2012 ฉันจำเป็นต้องเพิ่มand target_name='ring_buffer'เวอร์ชันดังกล่าวเนื่องจากดูเหมือนว่ามีเป้าหมายสองเป้าหมายในขณะนี้ ฉันยังคงพยายามที่จะรับภาพลักษณ์ของสิ่งที่กำลังทำในเวอร์ชัน 20 นาที
Martin Smith
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.