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 पर चल रहा है (शोर, लेकिन शायद एक ही परिणाम)
  • संकेत (एक ही परिणाम) के बजाय अलगाव स्तर सेट करें
  • मेज पर ताला वृद्धि को बंद करना (समान परिणाम)
  • वास्तविक क्वेरी प्लान में स्कैन के वास्तविक निष्पादन समय की जांच (वही परिणाम)
  • पुनरावर्ती संकेत (एक ही परिणाम)
  • केवल फ़ाइल समूह पढ़ें (समान परिणाम)

सबसे आशाजनक अन्वेषण कचरा चर को हटाने और नो-रिजल्ट क्वेरी का उपयोग करने से आता है। शुरुआत में इसने NOLOCK को थोड़ा और तेज दिखाया, लेकिन जब मैंने अपने बॉस को डेमो दिखाया, तो NOLOCK धीमा हो गया।

यह NOLOCK के बारे में क्या है जो चर असाइनमेंट के साथ स्कैन को धीमा कर देता है?


यह निश्चित उत्तर देने के लिए किसी को स्रोत कोड एक्सेस और एक प्रोफाइलर के साथ ले जाएगा। लेकिन NOLOCK को यह सुनिश्चित करने के लिए कुछ अतिरिक्त काम करना होगा कि यह परिवर्तनशील डेटा की उपस्थिति में अनंत लूप में प्रवेश न करे। और NOLOCK प्रश्नों के लिए ऐसे अनुकूलन हो सकते हैं जो अक्षम हैं (उर्फ कभी परीक्षण नहीं किए गए)।
डेविड ब्राउन - Microsoft

1
Microsoft SQL Server 2016 (SP1) (KB3182545) - 13.0.4001.0 (X64) लोकल पर मेरे लिए कोई रिप्रो नहीं।
मार्टिन स्मिथ

जवाबों:


12

ध्यान दें: यह आपके द्वारा खोजे जा रहे उत्तर का प्रकार नहीं हो सकता है। लेकिन शायद यह अन्य संभावित उत्तरदाताओं के लिए उपयोगी होगा जहां तक ​​सुराग प्रदान करना है कि कहां से शुरू करना है

जब मैं ETW अनुरेखण (PerfView का उपयोग करके) के तहत इन प्रश्नों को चलाता हूं, तो मुझे निम्नलिखित परिणाम मिलते हैं:

Plain  - 608 ms  
NOLOCK - 659 ms

तो अंतर 51ms है । यह आपके अंतर (~ 50ms) के साथ बहुत ही मृत है। प्रोफाइलर नमूना ओवरहेड के कारण मेरी संख्या थोड़ी अधिक है।

अंतर खोजना

यहां एक साइड-बाय-साइड तुलना दिखाते हुए कहा गया है कि sqlmin.dll में FetchNextRowविधि में 51ms का अंतर है :

FetchNextRow

प्लेन का चयन बाईं ओर 332 ms पर है, जबकि नोलॉक संस्करण दाईं ओर 383 ( 51ms लंबा) पर है। आप यह भी देख सकते हैं कि दो कोड पथ इस तरह से भिन्न हैं:

  • मैदान SELECT

    • sqlmin!RowsetNewSS::FetchNextRow कॉल
      • sqlmin!IndexDataSetSession::GetNextRowValuesInternal
  • का उपयोग करते हुए NOLOCK

    • sqlmin!RowsetNewSS::FetchNextRow कॉल
      • sqlmin!DatasetSession::GetNextRowValuesNoLock जो या तो फोन करता है
        • sqlmin!IndexDataSetSession::GetNextRowValuesInternal या
        • kernel32!TlsGetValue

इससे पता चलता है कि FetchNextRowअलगाव स्तर / नोलॉक संकेत के आधार पर विधि में कुछ शाखाएं हैं ।

NOLOCKशाखा को अधिक समय क्यों लगता है?

नोलॉक शाखा वास्तव में (25ms कम) कॉल करने में कम समय खर्च करती है GetNextRowValuesInternal। लेकिन सीधे कोड GetNextRowValuesNoLock(तरीकों को शामिल नहीं करता है जिसमें यह AKA को "Exc" स्तंभ कहता है) 63ms के लिए चलता है - जो कि अंतर का अधिकांश हिस्सा है (CPU समय में 63 - 25 = 38ms शुद्ध वृद्धि)।

तो ओवरहेड में अन्य 13ms (51ms कुल - 38ms अब तक का हिसाब) FetchNextRowक्या है ?

इंटरफ़ेस प्रेषण

मैंने सोचा कि यह किसी भी चीज़ की तुलना में अधिक उत्सुकता थी, लेकिन नॉल्क संस्करण कुछ इंटरफ़ेस प्रेषण ओवरहेड को विंडोज एपीआई विधि के kernel32!TlsGetValueमाध्यम से कॉल करके प्रकट होता है kernel32!TlsGetValueStub- कुल 17ms। सादे चयन इंटरफ़ेस के माध्यम से नहीं जाते हैं, इसलिए यह स्टब से कभी नहीं टकराता है , और केवल 6ms TlsGetValue( 1111 के अंतर पर ) खर्च करता है । आप इसे पहले स्क्रीनशॉट में देख सकते हैं।

मुझे शायद क्वेरी के अधिक पुनरावृत्तियों के साथ इस ट्रेस को फिर से चलाना चाहिए, मुझे लगता है कि कुछ छोटी चीजें हैं, जैसे कि हार्डवेयर इंटरप्ट, जिसे परफ्यूज़ के 1ms नमूना दर द्वारा नहीं उठाया गया था


उस पद्धति के बाहर, मैंने एक और छोटे अंतर पर ध्यान दिया, जो नोलॉक संस्करण को धीमा चलाने का कारण बनता है:

ताले जारी

नोलॉक शाखा अधिक आक्रामक रूप से sqlmin!RowsetNewSS::ReleaseRowsविधि को प्रकट करती है, जिसे आप इस स्क्रीनशॉट में देख सकते हैं:

तालों को छोड़ना

सादा चयन शीर्ष पर, 12ms पर है, जबकि नोलॉक संस्करण 26ms ( 14ms लंबे समय) में सबसे नीचे है । आप "जब" कॉलम में भी देख सकते हैं कि नमूना के दौरान कोड को अधिक बार निष्पादित किया गया था। यह नोलॉक का कार्यान्वयन विवरण हो सकता है, लेकिन यह छोटे नमूनों के लिए बहुत अधिक ओवरहेड का परिचय देता है।


कई अन्य छोटे अंतर हैं, लेकिन वे बड़े हिस्सा हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.