यह प्रश्न इस फोरम थ्रेड से संबंधित है ।
मेरे वर्कस्टेशन पर SQL सर्वर 2008 डेवलपर संस्करण और दो-नोड वर्चुअल मशीन क्लस्टर पर एक एंटरप्राइज़ संस्करण चलाना जहां मैं "अल्फा क्लस्टर" का संदर्भ देता हूं।
जिस समय एक वार्बिनरी (अधिकतम) कॉलम वाली पंक्तियों को हटाने में समय लगता है, सीधे उस कॉलम में डेटा की लंबाई से संबंधित होता है। यह पहली बार में सहज लग सकता है, लेकिन जांच के बाद, यह मेरी समझ से टकराता है कि SQL सर्वर वास्तव में सामान्य रूप से पंक्तियों को कैसे हटाता है और इस तरह के डेटा से संबंधित है।
समस्या को एक टाइमआउट (> 30 सेकंड) समस्या से उपजा है जो हम अपने .NET वेब एप्लिकेशन में देख रहे हैं, लेकिन मैंने इसे इस चर्चा के लिए सरल बनाया है।
जब कोई रिकॉर्ड हटा दिया जाता है, तो लेन-देन के शुरू होने के बाद SQL सर्वर इसे एक भूत क्लीनअप टास्क द्वारा साफ किए जाने वाले भूत के रूप में चिह्नित करता है ( पॉल रैंडल का ब्लॉग देखें )। एक परीक्षण में क्रमशः 16 KB, 4 MB, और 50 MB डेटा के साथ एक भिन्न (अधिकतम) कॉलम में तीन पंक्तियों को हटाते हुए, मैं इस पृष्ठ पर डेटा के इन-पंक्ति भाग के साथ-साथ लेनदेन में भी हो रहा हूं। लॉग इन करें।
मुझे जो अजीब लगता है वह यह है कि एक्स लॉक्स को डिलीट के दौरान सभी एलओबी डेटा पेजों पर रखा जाता है, और पेजों को पीएफएस में निपटा दिया जाता है। मैं इसे लेनदेन लॉग में देखता हूं, साथ ही साथ डीएमवी ( ) sp_lock
के परिणाम भी देखता हूं । dm_db_index_operational_stats
page_lock_count
यह मेरे कार्य केंद्र और हमारे अल्फा क्लस्टर पर एक I / O अड़चन पैदा करता है अगर वे पृष्ठ पहले से ही बफर कैश में नहीं हैं। वास्तव में, एक page_io_latch_wait_in_ms
ही DMV से व्यावहारिक रूप से हटाए जाने की पूरी अवधि है, और page_io_latch_wait_count
लॉक किए गए पृष्ठों की संख्या के साथ मेल खाती है। मेरे कार्य केंद्र पर 50 एमबी फ़ाइल के लिए, यह खाली बफर कैश ( checkpoint
/ dbcc dropcleanbuffers
) के साथ शुरू होने पर 3 सेकंड से अधिक अनुवाद करता है , और मुझे कोई संदेह नहीं है कि यह भारी विखंडन और लोड के तहत अधिक लंबा होगा।
मैंने यह सुनिश्चित करने की कोशिश की कि यह उस समय तक कैश में जगह आवंटित नहीं कर रहा था। मैं checkpoint
विधि के बजाय डिलीट को निष्पादित करने से पहले अन्य पंक्तियों से 2 जीबी डेटा में पढ़ता हूं , जो कि SQL सर्वर प्रक्रिया के लिए आवंटित से अधिक है। सुनिश्चित नहीं है कि यह वैध परीक्षण है या नहीं, क्योंकि मुझे नहीं पता कि SQL सर्वर डेटा को कैसे फेरबदल करता है। मुझे लगता है कि यह हमेशा पुराने को नए के पक्ष में धकेल देगा।
इसके अलावा, यह पृष्ठों को संशोधित भी नहीं करता है। यह मैं देख सकता हूं dm_os_buffer_descriptors
। डिलीट के बाद पेज साफ होते हैं, जबकि संशोधित पेजों की संख्या तीनों छोटे, मध्यम और बड़े डिलीट के लिए 20 से कम है। मैंने DBCC PAGE
देखे गए पृष्ठों के नमूने के लिए आउटपुट की तुलना की , और कोई बदलाव नहीं हुआ (केवल ALLOCATED
पीएफएस से थोड़ा हटा दिया गया था)। यह सिर्फ उन्हें समझाता है।
आगे यह साबित करने के लिए कि पेज लुकअप / डील-डौल के मुद्दे पैदा कर रहे हैं, मैंने वैनिला वार्बिनरी (अधिकतम) के बजाय फ़िलेस्ट्रीम कॉलम का उपयोग करके एक ही परीक्षण की कोशिश की। हटाए गए निरंतर समय थे, एलओबी आकार की परवाह किए बिना।
इसलिए, पहले मेरे शैक्षणिक प्रश्न:
- SQL सर्वर को X लॉक करने के लिए सभी LOB डेटा पृष्ठों को देखने की आवश्यकता क्यों है? क्या यह केवल एक विस्तार है कि कैसे मेमोरी में ताले का प्रतिनिधित्व किया जाता है (किसी तरह पृष्ठ के साथ संग्रहीत)? यह आई / ओ प्रभाव पूरी तरह से कैश नहीं होने पर डेटा आकार पर दृढ़ता से निर्भर करता है।
- क्यों एक्स सभी पर ताला लगाता है, बस उन्हें निपटाने के लिए? क्या यह केवल इन-पंक्ति भाग के साथ इंडेक्स लीफ को लॉक करने के लिए पर्याप्त नहीं है, क्योंकि डीलक्लोकेशन को स्वयं पृष्ठों को संशोधित करने की आवश्यकता नहीं है? क्या एलओबी डेटा प्राप्त करने के लिए कोई अन्य तरीका है जो लॉक से बचाता है?
- पृष्ठों को बिल्कुल सामने क्यों रखा जाए, यह देखते हुए कि इस तरह के काम के लिए पहले से ही एक पृष्ठभूमि कार्य है?
और शायद अधिक महत्वपूर्ण, मेरा व्यावहारिक प्रश्न:
- क्या डिलीट करने का कोई तरीका अलग तरीके से संचालित होता है? मेरा लक्ष्य लगातार आकार की परवाह किए बिना हटा दिया जाता है, फिल्मस्ट्रीम के समान, जहां तथ्य के बाद पृष्ठभूमि में कोई भी सफाई होती है। यह एक विन्यास बात है? क्या मैं चीजों को अजीब तरीके से संग्रहीत कर रहा हूं?
यहां बताया गया है कि वर्णित परीक्षा को कैसे दोबारा करें (SSMS क्वेरी विंडो के माध्यम से निष्पादित):
CREATE TABLE [T] (
[ID] [uniqueidentifier] NOT NULL PRIMARY KEY,
[Data] [varbinary](max) NULL
)
DECLARE @SmallID uniqueidentifier
DECLARE @MediumID uniqueidentifier
DECLARE @LargeID uniqueidentifier
SELECT @SmallID = NEWID(), @MediumID = NEWID(), @LargeID = NEWID()
-- May want to keep these IDs somewhere so you can use them in the deletes without var declaration
INSERT INTO [T] VALUES (@SmallID, CAST(REPLICATE(CAST('a' AS varchar(max)), 16 * 1024) AS varbinary(max)))
INSERT INTO [T] VALUES (@MediumID, CAST(REPLICATE(CAST('a' AS varchar(max)), 4 * 1024 * 1024) AS varbinary(max)))
INSERT INTO [T] VALUES (@LargeID, CAST(REPLICATE(CAST('a' AS varchar(max)), 50 * 1024 * 1024) AS varbinary(max)))
-- Do this before test
CHECKPOINT
DBCC DROPCLEANBUFFERS
BEGIN TRAN
-- Do one of these deletes to measure results or profile
DELETE FROM [T] WHERE ID = @SmallID
DELETE FROM [T] WHERE ID = @MediumID
DELETE FROM [T] WHERE ID = @LargeID
-- Do this after test
ROLLBACK
मेरे वर्कस्टेशन पर डिलीट को प्रोफाइल करने के कुछ परिणाम यहां दिए गए हैं:
| स्तंभ प्रकार | डिलीट साइज | अवधि (एमएस) | पढ़ता है | लिखता है | सीपीयू | -------------------------------------------------- ------------------ | VarBinary | 16 केबी | 40 | 13 | 2 | 0 | | VarBinary | 4 एमबी | 952 | 2318 | 2 | 0 | | VarBinary | 50 एमबी | 2976 | 28594 | 1 | 62 | -------------------------------------------------- ------------------ | FileStream | 16 केबी | 1 | 12 | 1 | 0 | | FileStream | 4 एमबी | 0 | 9 | 0 | 0 | | FileStream | 50 एमबी | 1 | 9 | 0 | 0 |
हम जरूरी नहीं कि सिर्फ filestream का उपयोग कर सकते हैं क्योंकि:
- हमारा डेटा आकार वितरण इसे वॉरंट नहीं करता है।
- व्यवहार में, हम कई खंडों में डेटा जोड़ते हैं, और फ़ाइलस्ट्रीम आंशिक अपडेट का समर्थन नहीं करता है। हमें इसके चारों ओर डिजाइन करने की आवश्यकता होगी।
अपडेट १
एक सिद्धांत का परीक्षण किया गया कि डेटा को डिलीट लॉग के रूप में ट्रांजेक्शन लॉग में लिखा जा रहा है, और ऐसा प्रतीत नहीं होता है। क्या मैं इसके लिए गलत तरीके से परीक्षण कर रहा हूं? निचे देखो।
SELECT MAX([Current LSN]) FROM fn_dblog(NULL, NULL)
--0000002f:000001d9:0001
BEGIN TRAN
DELETE FROM [T] WHERE ID = @ID
SELECT
SUM(
DATALENGTH([RowLog Contents 0]) +
DATALENGTH([RowLog Contents 1]) +
DATALENGTH([RowLog Contents 3]) +
DATALENGTH([RowLog Contents 4])
) [RowLog Contents Total],
SUM(
DATALENGTH([Log Record])
) [Log Record Total]
FROM fn_dblog(NULL, NULL)
WHERE [Current LSN] > '0000002f:000001d9:0001'
5 एमबी आकार से अधिक फ़ाइल के लिए, यह वापस आ गया 1651 | 171860
।
इसके अलावा, मैं उम्मीद करूंगा कि अगर लॉग में डेटा लिखा गया है तो पेज खुद ही गंदे हो जाएंगे। केवल डीलॉक्शन्स लॉग इन लगते हैं, जो डिलीट होने के बाद गंदे से मेल खाते हैं।
अपडेट २
मुझे पॉल रैंडल से प्रतिक्रिया मिली। उन्होंने इस तथ्य की पुष्टि की कि पेड़ को उखाड़ने के लिए सभी पृष्ठों को पढ़ना पड़ता है और यह पता लगाने के लिए कि कौन से पृष्ठों को निस्तारित करना है, और कहा कि उन पृष्ठों को देखने का कोई अन्य तरीका नहीं है। यह 1 & 2 का आधा उत्तर है (हालांकि आउट-ऑफ-द-रो डेटा पर ताले की आवश्यकता की व्याख्या नहीं करता है, लेकिन यह छोटे बर्तन है)।
प्रश्न 3 अभी भी खुला है: यदि पहले से ही हटाए जाने के लिए सफाई करने के लिए एक पृष्ठभूमि कार्य है, तो पृष्ठों को सामने क्यों हटाएं?
और निश्चित रूप से, सभी महत्वपूर्ण प्रश्न: क्या यह आकार-निर्भर डिलीट व्यवहार को सीधे मिटाने (यानी काम नहीं करने) का एक तरीका है? मुझे लगता है कि यह एक अधिक सामान्य मुद्दा होगा, जब तक कि हम वास्तव में केवल SQL सर्वर में 50 एमबी पंक्तियों को संग्रहीत और हटा नहीं रहे हैं? क्या बाकी सभी लोग इसके आसपास काम करते हैं जो किसी न किसी तरह से कचरा संग्रहण कार्य करते हैं?