ทำไม NOLOCK ทำการสแกนด้วยการกำหนดตัวแปรให้ช้าลง?


11

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

ฉันค้นพบว่า NOLOCK จริง ๆ แล้วการสแกนของฉันช้าลง

ตอนแรกฉันดีใจ แต่ตอนนี้ฉันสับสน การทดสอบของฉันไม่ถูกต้องอย่างใด? NOLOCK ไม่ควรอนุญาตให้สแกนเร็วขึ้นเล็กน้อยจริง ๆ หรือ เกิดอะไรขึ้นที่นี่

นี่คือสคริปต์ของฉัน:

USE TestDB
GO

--Create a five-million row table
DROP TABLE IF EXISTS dbo.JustAnotherTable
GO

CREATE TABLE dbo.JustAnotherTable (
ID INT IDENTITY PRIMARY KEY,
notID CHAR(5) NOT NULL )

INSERT dbo.JustAnotherTable
SELECT TOP 5000000 'datas'
FROM sys.all_objects a1
CROSS JOIN sys.all_objects a2
CROSS JOIN sys.all_objects a3

/********************************************/
-----Testing. Run each multiple times--------
/********************************************/
--How fast is a plain select? (I get about 587ms)
DECLARE @trash CHAR(5), @dt DATETIME = SYSDATETIME()

SELECT @trash = notID  --trash variable prevents any slowdown from returning data to SSMS
FROM dbo.JustAnotherTable
ORDER BY ID
OPTION (MAXDOP 1)

SELECT DATEDIFF(MILLISECOND,@dt,SYSDATETIME())

----------------------------------------------
--Now how fast is it with NOLOCK? About 640ms for me
DECLARE @trash CHAR(5), @dt DATETIME = SYSDATETIME()

SELECT @trash = notID
FROM dbo.JustAnotherTable (NOLOCK)
ORDER BY ID --would be an allocation order scan without this, breaking the comparison
OPTION (MAXDOP 1)

SELECT DATEDIFF(MILLISECOND,@dt,SYSDATETIME())

สิ่งที่ฉันพยายามที่ไม่ทำงาน:

  • ทำงานบนเซิร์ฟเวอร์ที่แตกต่างกัน (ผลลัพธ์เดียวกันเซิร์ฟเวอร์คือ 2016-SP1 และ 2016-SP2 ทั้งสองเงียบ)
  • ทำงานบน dbfiddle.ukในรุ่นต่าง ๆ (มีเสียงดัง แต่อาจเป็นผลลัพธ์เดียวกัน)
  • SET ISOLATION LEVEL แทนคำใบ้ (ผลลัพธ์เดียวกัน)
  • การปิดการเพิ่มล็อคบนโต๊ะ (ผลลัพธ์เดียวกัน)
  • การตรวจสอบเวลาดำเนินการจริงของการสแกนในแผนแบบสอบถามจริง (ผลลัพธ์เดียวกัน)
  • คำแนะนำการคอมไพล์ใหม่ (ผลลัพธ์เดียวกัน)
  • อ่านเฉพาะกลุ่มไฟล์ (ผลลัพธ์เดียวกัน)

การสำรวจที่มีแนวโน้มมากที่สุดนั้นมาจากการลบตัวแปรถังขยะและใช้แบบสอบถามที่ไม่มีผลลัพธ์ ในขั้นต้นนี่แสดงให้เห็นว่า NOLOCK เร็วขึ้นเล็กน้อย แต่เมื่อฉันแสดงตัวอย่างให้เจ้านายของฉัน NOLOCK กลับมาช้าลง

อะไรเกี่ยวกับ NOLOCK ที่ทำให้การสแกนช้าลงด้วยการกำหนดตัวแปร?


จะต้องมีผู้ที่สามารถเข้าถึงซอร์สโค้ดและผู้สร้างโปรไฟล์เพื่อให้คำตอบที่ชัดเจน แต่ NOLOCK ต้องทำงานเพิ่มเติมบางอย่างเพื่อให้แน่ใจว่ามันจะไม่เข้าสู่วงวนไม่สิ้นสุดในที่ที่มีข้อมูลกลายพันธุ์ และอาจมีการเพิ่มประสิทธิภาพที่ถูกปิดใช้งาน (aka ไม่เคยทดสอบ) สำหรับการค้นหา NOLOCK
David Browne - Microsoft

1
ไม่มีการทำซ้ำสำหรับฉันใน Microsoft SQL Server 2016 (SP1) (KB3182545) - 13.0.4001.0 (X64) localdb
Martin Smith

คำตอบ:


12

หมายเหตุ: นี่อาจไม่ใช่คำตอบที่คุณต้องการ แต่อาจเป็นประโยชน์กับผู้ตอบที่มีศักยภาพอื่น ๆ เท่าที่ให้เบาะแสว่าจะเริ่มมองหาที่ไหน

เมื่อฉันเรียกใช้แบบสอบถามเหล่านี้ภายใต้การติดตาม ETW (โดยใช้ PerfView) ฉันได้รับผลลัพธ์ต่อไปนี้:

Plain  - 608 ms  
NOLOCK - 659 ms

ดังนั้นความแตกต่างคือ51ms นี่เป็นสิ่งที่ตายไปแล้วในความแตกต่างของคุณ (~ 50ms) ตัวเลขของฉันสูงขึ้นเล็กน้อยโดยรวมเนื่องจากค่าใช้จ่ายในการสุ่มตัวอย่าง profiler

การค้นหาความแตกต่าง

นี่คือการเปรียบเทียบแบบเคียงข้างกันแสดงให้เห็นว่าความแตกต่าง 51ms อยู่ในFetchNextRowวิธีการใน sqlmin.dll:

FetchNextRow

ตัวเลือกธรรมดาอยู่ทางซ้ายที่ 332 ms ในขณะที่รุ่น nolock อยู่ทางขวาที่ 383 ( ยาวกว่า51ms ) คุณสามารถเห็นได้ว่าทั้งสองเส้นทางรหัสแตกต่างกันด้วยวิธีนี้:

  • ธรรมดา SELECT

    • sqlmin!RowsetNewSS::FetchNextRow โทร
      • sqlmin!IndexDataSetSession::GetNextRowValuesInternal
  • การใช้ NOLOCK

    • sqlmin!RowsetNewSS::FetchNextRow โทร
      • sqlmin!DatasetSession::GetNextRowValuesNoLock ซึ่งเรียกอย่างใดอย่างหนึ่ง
        • sqlmin!IndexDataSetSession::GetNextRowValuesInternal หรือ
        • kernel32!TlsGetValue

นี่แสดงให้เห็นว่ามีการแตกแขนงบางส่วนในFetchNextRowวิธีการโดยยึดตามระดับการแยก / คำใบ้ nolock

เหตุใดNOLOCKสาขาจึงใช้เวลานานกว่า

สาขา nolock ใช้เวลาในการโทรน้อยลงGetNextRowValuesInternal(น้อยกว่า 25ms) แต่รหัสโดยตรงGetNextRowValuesNoLock(ไม่รวมวิธีที่เรียก AKA คอลัมน์ "Exc") ทำงานเป็นเวลา 63ms - ซึ่งเป็นความแตกต่างส่วนใหญ่ (63 - 25 = 38ms เพิ่มขึ้นสุทธิในเวลา CPU)

ดังนั้นสิ่งที่เป็น 13ms อื่น ๆ (51ms รวม - 38ms คิดเพื่อให้ห่างไกล) ค่าใช้จ่ายในFetchNextRow?

อินเทอร์เฟซการจัดส่ง

ฉันคิดว่านี่เป็นความอยากรู้มากกว่าทุกอย่าง แต่รุ่น nolock ดูเหมือนจะมีค่าใช้จ่ายในการจัดส่งอินเทอร์เฟซโดยการเรียกใช้วิธีการของ Windows API kernel32!TlsGetValueผ่านkernel32!TlsGetValueStub- รวม 17ms ตัวเลือกธรรมดาดูเหมือนจะไม่ผ่านอินเทอร์เฟซดังนั้นจึงไม่เคยกระทบ stub และใช้เวลาเพียง 6ms เท่านั้นTlsGetValue(ต่างกัน11ms ) คุณสามารถเห็นสิ่งนี้ได้ในภาพหน้าจอแรก

ฉันน่าจะเรียกใช้การติดตามนี้อีกครั้งด้วยการทำซ้ำของแบบสอบถามมากขึ้นฉันคิดว่ามีบางสิ่งเล็ก ๆ เช่นการขัดจังหวะฮาร์ดแวร์ที่ไม่ได้รับโดยอัตราตัวอย่าง 1ms ของ PerfView


นอกเหนือจากวิธีการนั้นฉันสังเกตเห็นความแตกต่างเล็ก ๆ น้อย ๆ ที่ทำให้รุ่น nolock ทำงานช้าลง:

ปล่อยล็อค

สาขา nolock ปรากฏขึ้นเพื่อเรียกใช้sqlmin!RowsetNewSS::ReleaseRowsวิธีการเชิงรุกมากขึ้นซึ่งคุณสามารถเห็นได้ในภาพหน้าจอนี้:

การปลดล็อค

ตัวเลือกธรรมดาอยู่ด้านบน 12ms ในขณะที่รุ่น nolock อยู่ด้านล่างที่ 26ms ( อีก14ms ) นอกจากนี้คุณยังสามารถดูได้ในคอลัมน์ "เมื่อ" ที่รหัสถูกเรียกใช้บ่อยขึ้นในระหว่างตัวอย่าง นี่อาจเป็นรายละเอียดการนำไปปฏิบัติของ nolock แต่ดูเหมือนว่าจะแนะนำค่าใช้จ่ายเล็กน้อยสำหรับตัวอย่างเล็ก ๆ


มีความแตกต่างเล็ก ๆ น้อย ๆ มากมาย แต่นั่นเป็นก้อนใหญ่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.