SQL सर्वर में LOB डेटा के लिए प्रदर्शन हटाएं


16

यह प्रश्न इस फोरम थ्रेड से संबंधित है ।

मेरे वर्कस्टेशन पर SQL सर्वर 2008 डेवलपर संस्करण और दो-नोड वर्चुअल मशीन क्लस्टर पर एक एंटरप्राइज़ संस्करण चलाना जहां मैं "अल्फा क्लस्टर" का संदर्भ देता हूं।

जिस समय एक वार्बिनरी (अधिकतम) कॉलम वाली पंक्तियों को हटाने में समय लगता है, सीधे उस कॉलम में डेटा की लंबाई से संबंधित होता है। यह पहली बार में सहज लग सकता है, लेकिन जांच के बाद, यह मेरी समझ से टकराता है कि SQL सर्वर वास्तव में सामान्य रूप से पंक्तियों को कैसे हटाता है और इस तरह के डेटा से संबंधित है।

समस्या को एक टाइमआउट (> 30 सेकंड) समस्या से उपजा है जो हम अपने .NET वेब एप्लिकेशन में देख रहे हैं, लेकिन मैंने इसे इस चर्चा के लिए सरल बनाया है।

जब कोई रिकॉर्ड हटा दिया जाता है, तो लेन-देन के शुरू होने के बाद SQL सर्वर इसे एक भूत क्लीनअप टास्क द्वारा साफ किए जाने वाले भूत के रूप में चिह्नित करता है ( पॉल रैंडल का ब्लॉग देखें )। एक परीक्षण में क्रमशः 16 KB, 4 MB, और 50 MB डेटा के साथ एक भिन्न (अधिकतम) कॉलम में तीन पंक्तियों को हटाते हुए, मैं इस पृष्ठ पर डेटा के इन-पंक्ति भाग के साथ-साथ लेनदेन में भी हो रहा हूं। लॉग इन करें।

मुझे जो अजीब लगता है वह यह है कि एक्स लॉक्स को डिलीट के दौरान सभी एलओबी डेटा पेजों पर रखा जाता है, और पेजों को पीएफएस में निपटा दिया जाता है। मैं इसे लेनदेन लॉग में देखता हूं, साथ ही साथ डीएमवी ( ) sp_lockके परिणाम भी देखता हूं । dm_db_index_operational_statspage_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पीएफएस से थोड़ा हटा दिया गया था)। यह सिर्फ उन्हें समझाता है।

आगे यह साबित करने के लिए कि पेज लुकअप / डील-डौल के मुद्दे पैदा कर रहे हैं, मैंने वैनिला वार्बिनरी (अधिकतम) के बजाय फ़िलेस्ट्रीम कॉलम का उपयोग करके एक ही परीक्षण की कोशिश की। हटाए गए निरंतर समय थे, एलओबी आकार की परवाह किए बिना।

इसलिए, पहले मेरे शैक्षणिक प्रश्न:

  1. SQL सर्वर को X लॉक करने के लिए सभी LOB डेटा पृष्ठों को देखने की आवश्यकता क्यों है? क्या यह केवल एक विस्तार है कि कैसे मेमोरी में ताले का प्रतिनिधित्व किया जाता है (किसी तरह पृष्ठ के साथ संग्रहीत)? यह आई / ओ प्रभाव पूरी तरह से कैश नहीं होने पर डेटा आकार पर दृढ़ता से निर्भर करता है।
  2. क्यों एक्स सभी पर ताला लगाता है, बस उन्हें निपटाने के लिए? क्या यह केवल इन-पंक्ति भाग के साथ इंडेक्स लीफ को लॉक करने के लिए पर्याप्त नहीं है, क्योंकि डीलक्लोकेशन को स्वयं पृष्ठों को संशोधित करने की आवश्यकता नहीं है? क्या एलओबी डेटा प्राप्त करने के लिए कोई अन्य तरीका है जो लॉक से बचाता है?
  3. पृष्ठों को बिल्कुल सामने क्यों रखा जाए, यह देखते हुए कि इस तरह के काम के लिए पहले से ही एक पृष्ठभूमि कार्य है?

और शायद अधिक महत्वपूर्ण, मेरा व्यावहारिक प्रश्न:

  • क्या डिलीट करने का कोई तरीका अलग तरीके से संचालित होता है? मेरा लक्ष्य लगातार आकार की परवाह किए बिना हटा दिया जाता है, फिल्मस्ट्रीम के समान, जहां तथ्य के बाद पृष्ठभूमि में कोई भी सफाई होती है। यह एक विन्यास बात है? क्या मैं चीजों को अजीब तरीके से संग्रहीत कर रहा हूं?

यहां बताया गया है कि वर्णित परीक्षा को कैसे दोबारा करें (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 का उपयोग कर सकते हैं क्योंकि:

  1. हमारा डेटा आकार वितरण इसे वॉरंट नहीं करता है।
  2. व्यवहार में, हम कई खंडों में डेटा जोड़ते हैं, और फ़ाइलस्ट्रीम आंशिक अपडेट का समर्थन नहीं करता है। हमें इसके चारों ओर डिजाइन करने की आवश्यकता होगी।

अपडेट १

एक सिद्धांत का परीक्षण किया गया कि डेटा को डिलीट लॉग के रूप में ट्रांजेक्शन लॉग में लिखा जा रहा है, और ऐसा प्रतीत नहीं होता है। क्या मैं इसके लिए गलत तरीके से परीक्षण कर रहा हूं? निचे देखो।

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 एमबी पंक्तियों को संग्रहीत और हटा नहीं रहे हैं? क्या बाकी सभी लोग इसके आसपास काम करते हैं जो किसी न किसी तरह से कचरा संग्रहण कार्य करते हैं?


काश एक बेहतर समाधान होता, लेकिन एक नहीं मिला। मेरे पास 1MB + तक भिन्न आकार की पंक्तियों के बड़े संस्करणों को लॉग करने की स्थिति है, और मेरे पास पुराने रिकॉर्ड को हटाने के लिए "शुद्ध" प्रक्रिया है। क्योंकि डिलीट इतनी धीमी थी, इसलिए मुझे इसे दो चरणों में विभाजित करना पड़ा - पहले तालिकाओं (जो बहुत तेज़ है) के बीच संदर्भ हटा दें, फिर अनाथ पंक्तियों को हटा दें। डेटा हटाने के लिए हटाए गए कार्य का औसत ~ 2.2 सेकंड / MB था। इसलिए निश्चित रूप से मुझे विवाद को कम करना था, इसलिए मेरे पास "DELETE TOP (250)" के साथ एक लूप के अंदर एक संग्रहीत प्रक्रिया है जब तक कि कोई पंक्तियाँ हटा दी नहीं जातीं।
अबेकस

जवाबों:


5

मैं यह नहीं कह सकता कि फ़ाइल स्ट्रीम की तुलना में VARBINARY (MAX) को हटाने के लिए वास्तव में यह इतना अधिक अक्षम क्यों होगा, लेकिन एक विचार जिसे आप समझ सकते हैं कि क्या आप इन LOBS को हटाते समय अपने वेब एप्लिकेशन से समय से बचने की कोशिश कर रहे हैं। आप VARBINARY (MAX) मानों को एक अलग तालिका में संग्रहीत कर सकते हैं (इसे tblLOB कहते हैं) जो मूल तालिका द्वारा संदर्भित है (इस tblParent को कॉल करने देता है)।

यहां से जब आप कोई रिकॉर्ड हटाते हैं, तो आप इसे मूल रिकॉर्ड से हटा सकते हैं और फिर LOB तालिका में रिकॉर्ड्स को साफ करने और उसमें जाने के लिए एक सामयिक कचरा संग्रहण प्रक्रिया है। इस कचरा संग्रहण प्रक्रिया के दौरान अतिरिक्त हार्ड ड्राइव गतिविधि हो सकती है लेकिन यह कम से कम फ्रंट एंड वेब से अलग होगी और गैर-पीक समय के दौरान प्रदर्शन की जा सकती है।


धन्यवाद। यह बोर्ड पर हमारे विकल्पों में से एक है। तालिका एक फ़ाइल सिस्टम है, और हम वर्तमान में बाइनरी डेटा को पदानुक्रम मेटा से पूरी तरह से अलग डेटाबेस से अलग करने की प्रक्रिया में हैं। जैसा कि आपने कहा था हम या तो कर सकते हैं और पदानुक्रम पंक्ति को हटा सकते हैं, और एक जीसी प्रक्रिया को अनाथ एलओबी पंक्तियों को साफ कर सकते हैं। या समान लक्ष्य को पूरा करने के लिए डेटा के साथ एक डिलीट टाइमस्टैम्प है। यदि हम समस्या का कोई संतोषजनक उत्तर नहीं देते हैं तो यह मार्ग है।
जेरेमी रोसेनबर्ग

1
मुझे लगता है कि इसे हटाए जाने के संकेत देने के लिए केवल एक समय टिकट होने से सावधान रहना होगा। यह काम करेगा लेकिन तब आपके पास अंततः सक्रिय पंक्तियों में बहुत अधिक उपयोग की जाने वाली जगह होगी। आपको किसी बिंदु पर किसी प्रकार की जीसी प्रक्रिया की आवश्यकता होगी, यह इस बात पर निर्भर करता है कि कितना हटा दिया गया है, और यह नियमित रूप से कम को हटाने के लिए कम प्रभावकारी होगा, बल्कि एक सामयिक आधार पर बहुत सारे।
इयान चैंबरलैंड
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.