कुछ लोगों के लिए, यह गलत सवाल है। SQL कर्सर IS भूल। दुष्ट का विस्तार में वर्णन! आप SQL CURSOR के नाम से पूरे SQL ब्लॉग जगत में हर तरह की ईशनिंदा पढ़ सकते हैं।
अगर आप भी ऐसा ही महसूस करते हैं, तो आप इस नतीजे पर किस वजह से पहुंचे?
यदि यह किसी विश्वसनीय मित्र और सहकर्मी की ओर से है, तो मैं आपको दोष नहीं दे सकता। हो जाता है। कभी-कभी बहुत। लेकिन अगर किसी ने आपको सबूत के साथ मना लिया, तो वह अलग कहानी है।
हम पहले नहीं मिले हैं। आप मुझे एक दोस्त के रूप में नहीं जानते हैं। लेकिन मुझे उम्मीद है कि मैं इसे उदाहरणों के साथ समझा सकता हूं और आपको विश्वास दिलाता हूं कि SQL CURSOR का अपना स्थान है। यह ज्यादा नहीं है, लेकिन हमारे कोड में उस छोटी सी जगह के नियम हैं।
लेकिन पहले, मैं आपको अपनी कहानी बता दूं।
मैंने xBase का उपयोग करके डेटाबेस के साथ प्रोग्रामिंग शुरू की। वह मेरे पहले दो साल के पेशेवर प्रोग्रामिंग तक कॉलेज में वापस आ गया था। मैं आपको यह इसलिए बता रहा हूं क्योंकि पुराने जमाने में, हम डेटा को क्रमिक रूप से प्रोसेस करते थे, SQL जैसे सेट बैचों में नहीं। जब मैंने एसक्यूएल सीखा, तो यह एक आदर्श बदलाव की तरह था। डेटाबेस इंजन मेरे द्वारा जारी किए गए सेट-आधारित आदेशों के साथ मेरे लिए निर्णय लेता है। जब मैंने SQL CURSOR के बारे में सीखा, तो ऐसा लगा कि मैं पुराने लेकिन आरामदायक तरीकों के साथ वापस आ गया हूँ।
लेकिन कुछ वरिष्ठ सहयोगियों ने मुझे चेतावनी दी, "हर कीमत पर SQL CURSOR से बचें!" मुझे कुछ मौखिक स्पष्टीकरण मिले, और वह था।
SQL CURSOR खराब हो सकता है यदि आप इसे गलत कार्य के लिए उपयोग करते हैं। जैसे लकड़ी काटने के लिए हथौड़े का इस्तेमाल करना, यह हास्यास्पद है। बेशक, गलतियाँ हो सकती हैं, और हमारा ध्यान इसी पर होगा।
1. SQL CURSOR का उपयोग करते समय सेट आधारित कमांड काम करेंगे
मैं इस पर पर्याप्त जोर नहीं दे सकता, लेकिन यही समस्या की जड़ है। जब मैंने पहली बार सीखा कि SQL CURSOR क्या है, तो एक लाइट बल्ब जलाया गया। "लूप्स! मुझे पता है कि!" हालांकि, तब तक नहीं जब तक कि इसने मुझे सिरदर्द नहीं दिया और मेरे वरिष्ठों ने मुझे डांटा।
आप देखिए, SQL का दृष्टिकोण सेट-आधारित है। आप तालिका मानों से INSERT कमांड जारी करते हैं, और यह आपके कोड पर लूप के बिना काम करेगा। जैसा कि मैंने पहले कहा, यह डेटाबेस इंजन का काम है। इसलिए, यदि आप किसी लूप को किसी तालिका में रिकॉर्ड जोड़ने के लिए बाध्य करते हैं, तो आप उस प्राधिकरण को दरकिनार कर रहे हैं। यह बदसूरत होने वाला है।
इससे पहले कि हम एक हास्यास्पद उदाहरण का प्रयास करें, आइए डेटा तैयार करें:
SELECT TOP (500)
val = ROW_NUMBER() OVER (ORDER BY sod.SalesOrderDetailID)
, modified = GETDATE()
, status = 'inserted'
INTO dbo.TestTable
FROM AdventureWorks.Sales.SalesOrderDetail sod
CROSS JOIN AdventureWorks.Sales.SalesOrderDetail sod2
SELECT
tt.val
,GETDATE() AS modified
,'inserted' AS status
INTO dbo.TestTable2
FROM dbo.TestTable tt
WHERE CAST(val AS VARCHAR) LIKE '%2%'
पहला स्टेटमेंट डेटा के 500 रिकॉर्ड जेनरेट करेगा। दूसरे को इसका सबसेट मिलेगा। फिर, हम तैयार हैं। हम लापता डेटा को टेस्टटेबल . से सम्मिलित करने जा रहे हैं टेस्टटेबल2 . में एसक्यूएल कर्सर का उपयोग करना। नीचे देखें:
DECLARE @val INT
DECLARE test_inserts CURSOR FOR
SELECT val FROM TestTable tt
WHERE NOT EXISTS(SELECT val FROM TestTable2 tt1
WHERE tt1.val = tt.val)
OPEN test_inserts
FETCH NEXT FROM test_inserts INTO @val
WHILE @@fetch_status = 0
BEGIN
INSERT INTO TestTable2
(val, modified, status)
VALUES
(@val, GETDATE(),'inserted')
FETCH NEXT FROM test_inserts INTO @val
END
CLOSE test_inserts
DEALLOCATE test_inserts
एक-एक करके गुम रिकॉर्ड डालने के लिए SQL CURSOR का उपयोग करके लूप कैसे करें। काफी लंबा है, है ना?
अब, एक बेहतर तरीका आजमाते हैं - सेट-आधारित विकल्प। ये रहा:
INSERT INTO TestTable2
(val, modified, status)
SELECT val, GETDATE(), status
FROM TestTable tt
WHERE NOT EXISTS(SELECT val FROM TestTable2 tt1
WHERE tt1.val = tt.val)
यह छोटा, साफ-सुथरा और तेज है। कितना तेज? नीचे चित्र 1 देखें:
SQL सर्वर प्रबंधन स्टूडियो में xEvent Profiler का उपयोग करते हुए, मैंने CPU समय के आंकड़े, अवधि और तार्किक पढ़ने की तुलना की। जैसा कि आप चित्र 1 में देख सकते हैं, INSERT रिकॉर्ड के लिए सेट-आधारित कमांड का उपयोग करके प्रदर्शन परीक्षण जीत जाता है। अंक खुद ही अपनी बात कर रहे हैं। SQL CURSOR का उपयोग करने से अधिक संसाधनों और संसाधन समय की खपत होती है।
इसलिए, इससे पहले कि आप SQL CURSOR का उपयोग करें, पहले एक सेट-आधारित कमांड लिखने का प्रयास करें। यह लंबे समय में बेहतर भुगतान करेगा।
लेकिन क्या होगा अगर आपको काम पूरा करने के लिए SQL CURSOR की आवश्यकता है?
2. उपयुक्त SQL CURSOR विकल्पों का उपयोग नहीं करना
एक और गलती जो मैंने पहले भी की थी, वह थी DECLARE CURSOR में उपयुक्त विकल्पों का उपयोग नहीं करना। गुंजाइश, मॉडल, समवर्ती, और स्क्रॉल करने योग्य या नहीं के विकल्प हैं। ये तर्क वैकल्पिक हैं, और इन्हें अनदेखा करना आसान है। हालांकि, यदि कार्य करने का एकमात्र तरीका SQL कर्सर है, तो आपको अपने इरादे से स्पष्ट होना चाहिए।
तो, अपने आप से पूछें:
- लूप को पार करते समय, क्या आप केवल पंक्तियों को आगे की ओर नेविगेट करेंगे, या पहली, अंतिम, पिछली या अगली पंक्ति में चले जाएंगे? आपको यह निर्दिष्ट करने की आवश्यकता है कि क्या CURSOR केवल-अग्रेषित या स्क्रॉल करने योग्य है। यह DECLARE
CURSOR FORWARD_ONLY है या घोषित करें<कर्सर_नाम> कर्सर स्क्रॉल करें । - क्या आप कर्सर में कॉलम अपडेट करने जा रहे हैं? READ_ONLY का उपयोग करें यदि यह अपडेट करने योग्य नहीं है।
- लूप पार करते समय क्या आपको नवीनतम मूल्यों की आवश्यकता है? STATIC का उपयोग करें यदि मान नवीनतम हैं या नहीं, तो कोई फर्क नहीं पड़ेगा। यदि अन्य लेन-देन कॉलम अपडेट करते हैं या CURSOR में आपके द्वारा उपयोग की जाने वाली पंक्तियों को हटाते हैं, और आपको नवीनतम मानों की आवश्यकता है, तो DYNAMIC का उपयोग करें। नोट :डायनामिक महंगा होगा।
- क्या कनेक्शन के लिए CURSOR वैश्विक है या बैच के लिए स्थानीय है या संग्रहीत कार्यविधि है? निर्दिष्ट करें कि LOCAL या GLOBAL।
इन तर्कों के बारे में अधिक जानकारी के लिए, Microsoft डॉक्स से संदर्भ देखें।
उदाहरण
आइए xEvents Profiler का उपयोग करके CPU समय, तार्किक पढ़ने और अवधि के लिए तीन CURSOR की तुलना करने वाले एक उदाहरण का प्रयास करें। DECLARE CURSOR के बाद पहले वाले के पास कोई उपयुक्त विकल्प नहीं होगा। दूसरा है लोकल स्टेटिक FORWARD_ONLY READ_ONLY। अंतिम LOtyuiCAL FAST_FORWARD है।
ये रहा पहला:
-- NOTE: Don't just COPY and PASTE this code then run in your machine. Read and assess.
-- DECLARE CURSOR with no options
SET NOCOUNT ON
DECLARE @command NVARCHAR(2000) = N'SET NOCOUNT ON;'
CREATE TABLE #commands (
ID INT IDENTITY (1, 1) PRIMARY KEY CLUSTERED
,Command NVARCHAR(2000)
);
INSERT INTO #commands (Command)
VALUES (@command)
INSERT INTO #commands (Command)
SELECT
'SELECT ' + CHAR(39) + a.TABLE_SCHEMA + '.' + a.TABLE_NAME
+ ' - ' + CHAR(39)
+ ' + cast(count(*) as varchar) from '
+ a.TABLE_SCHEMA + '.' + a.TABLE_NAME
FROM INFORMATION_SCHEMA.tables a
WHERE a.TABLE_TYPE = 'BASE TABLE';
DECLARE command_builder CURSOR FOR
SELECT
Command
FROM #commands
OPEN command_builder
FETCH NEXT FROM command_builder INTO @command
WHILE @@fetch_status = 0
BEGIN
PRINT @command
FETCH NEXT FROM command_builder INTO @command
END
CLOSE command_builder
DEALLOCATE command_builder
DROP TABLE #commands
GO
निश्चित रूप से उपरोक्त कोड से बेहतर विकल्प है। यदि उद्देश्य केवल मौजूदा उपयोगकर्ता तालिकाओं से एक स्क्रिप्ट उत्पन्न करना है, तो SELECT करेगा। फिर, आउटपुट को दूसरी क्वेरी विंडो में पेस्ट करें।
लेकिन अगर आपको एक स्क्रिप्ट तैयार करने और उसे एक ही बार में चलाने की आवश्यकता है, तो यह एक अलग कहानी है। आपको आउटपुट स्क्रिप्ट का मूल्यांकन करना चाहिए कि यह आपके सर्वर पर कर लगाने वाली है या नहीं। गलती #4 बाद में देखें।
आपको विभिन्न विकल्पों के साथ तीन CURSOR की तुलना दिखाने के लिए, यह काम करेगा।
अब, हमारे पास एक समान कोड है लेकिन LOCAL STATIC FORWARD_ONLY READ_ONLY के साथ।
--- STATIC LOCAL FORWARD_ONLY READ_ONLY
SET NOCOUNT ON
DECLARE @command NVARCHAR(2000) = N'SET NOCOUNT ON;'
CREATE TABLE #commands (
ID INT IDENTITY (1, 1) PRIMARY KEY CLUSTERED
,Command NVARCHAR(2000)
);
INSERT INTO #commands (Command)
VALUES (@command)
INSERT INTO #commands (Command)
SELECT
'SELECT ' + CHAR(39) + a.TABLE_SCHEMA + '.' + a.TABLE_NAME
+ ' - ' + CHAR(39)
+ ' + cast(count(*) as varchar) from '
+ a.TABLE_SCHEMA + '.' + a.TABLE_NAME
FROM INFORMATION_SCHEMA.tables a
WHERE a.TABLE_TYPE = 'BASE TABLE';
DECLARE command_builder CURSOR LOCAL STATIC FORWARD_ONLY READ_ONLY FOR SELECT
Command
FROM #commands
OPEN command_builder
FETCH NEXT FROM command_builder INTO @command
WHILE @@fetch_status = 0
BEGIN
PRINT @command
FETCH NEXT FROM command_builder INTO @command
END
CLOSE command_builder
DEALLOCATE command_builder
DROP TABLE #commands
GO
जैसा कि आप ऊपर देख सकते हैं, पिछले कोड से एकमात्र अंतर LOCAL STATIC FORWARD_ONLY READ_ONLY है तर्क।
तीसरे में LOCAL FAST_FORWARD होगा। अब, माइक्रोसॉफ्ट के अनुसार, FAST_FORWARD एक FORWARD_ONLY, READ_ONLY CURSOR है जिसमें ऑप्टिमाइज़ेशन सक्षम है। हम देखेंगे कि यह पहले दो के साथ कैसा रहेगा।
वे कैसे तुलना करते हैं? चित्र 2 देखें:
जो कम CPU समय और अवधि लेता है वह है LOCAL STATIC FORWARD_ONLY READ_ONLY CURSOR। यह भी ध्यान दें कि यदि आप STATIC या READ_ONLY जैसे तर्क निर्दिष्ट नहीं करते हैं तो SQL सर्वर में डिफ़ॉल्ट है। इसका एक भयानक परिणाम है जैसा कि आप अगले भाग में देखेंगे।
sp_describe_cursor ने क्या दिखाया
sp_describe_cursor मास्टर . द्वारा संग्रहीत कार्यविधि है डेटाबेस जिसे आप खुले कर्सर से जानकारी प्राप्त करने के लिए उपयोग कर सकते हैं। और बिना CURSOR विकल्पों वाले प्रश्नों के पहले बैच से यह पता चला है। sp_describe_cursor . के परिणाम के लिए चित्र 3 देखें :
बहुत ज्यादा मारो? बिलकुल। प्रश्नों के पहले बैच का CURSOR है:
- मौजूदा कनेक्शन के लिए वैश्विक।
- डायनामिक, जिसका अर्थ है कि यह अपडेट, डिलीट और इंसर्ट के लिए #commands टेबल में बदलावों को ट्रैक करता है।
- आशावादी, जिसका अर्थ है कि SQL सर्वर ने CWT नामक अस्थायी तालिका में एक अतिरिक्त कॉलम जोड़ा। यह #commands तालिका के मानों में परिवर्तनों को ट्रैक करने के लिए एक चेकसम कॉलम है।
- स्क्रॉल करने योग्य, जिसका अर्थ है कि आप कर्सर में पिछली, अगली, शीर्ष या निचली पंक्ति में जा सकते हैं।
बेतुका? मैं दृढ़ता से सहमत। आपको वैश्विक कनेक्शन की आवश्यकता क्यों है? आपको #commands अस्थायी तालिका में परिवर्तनों को ट्रैक करने की आवश्यकता क्यों है? क्या हमने CURSOR में अगले रिकॉर्ड के अलावा कहीं और स्क्रॉल किया?
चूंकि SQL सर्वर हमारे लिए इसे निर्धारित करता है, CURSOR लूप एक भयानक गलती बन जाता है।
अब आप महसूस करते हैं कि SQL CURSOR विकल्पों को स्पष्ट रूप से निर्दिष्ट करना इतना महत्वपूर्ण क्यों है। इसलिए, अब से, यदि आप किसी CURSOR का उपयोग करना चाहते हैं, तो हमेशा इन CURSOR तर्कों को निर्दिष्ट करें।
निष्पादन योजना अधिक प्रकट करती है
वास्तविक निष्पादन योजना के बारे में कहने के लिए कुछ और है कि हर बार क्या होता है जब एक FETCH NEXT FROM कमांड_बिल्डर INTO @command निष्पादित होता है। चित्र 4 में, क्लस्टर्ड इंडेक्स CWT_PrimaryKey में एक पंक्ति डाली गई है tempdb . में तालिका सीडब्ल्यूटी :
लेखन tempdb . को होता है प्रत्येक FETCH NEXT पर। इसके अलावा, और भी है। याद रखें कि चित्र 3 में कर्सर ऑप्टिमिस्टिक है? योजना के सबसे दाहिने हिस्से पर क्लस्टर्ड इंडेक्स स्कैन के गुण Chk1002 नामक अतिरिक्त अज्ञात कॉलम को प्रकट करते हैं :
क्या यह चेकसम कॉलम हो सकता है? प्लान एक्सएमएल पुष्टि करता है कि वास्तव में ऐसा ही है:
अब, FETCH NEXT की वास्तविक निष्पादन योजना की तुलना करें जब CURSOR LOCAL STATIC FORWARD_ONLY READ_ONLY:
यह tempdb . का उपयोग करता है भी, लेकिन यह बहुत आसान है। इस बीच, चित्र 8 निष्पादन योजना दिखाता है जब LOCAL FAST_FORWARD का उपयोग किया जाता है:
टेकअवे
SQL CURSOR के उपयुक्त उपयोगों में से एक स्क्रिप्ट उत्पन्न करना या डेटाबेस ऑब्जेक्ट्स के समूह की ओर कुछ प्रशासनिक आदेश चलाना है। भले ही इसके छोटे-मोटे उपयोग हों, आपका पहला विकल्प है LOCAL STATIC FORWARD_ONLY READ_ONLY CURSOR या LOCAL FAST_FORWARD का उपयोग करना। बेहतर योजना और तार्किक पढ़ने वाला ही जीतेगा।
फिर, इनमें से किसी को भी उपयुक्त के साथ बदलें जैसा कि आवश्यकता है। लेकिन आप जानते हैं कि क्या? अपने व्यक्तिगत अनुभव में, मैंने केवल फॉरवर्ड-ओनली ट्रैवर्सल के साथ एक स्थानीय रीड-ओनली कर्सर का उपयोग किया। मुझे कभी भी CURSOR को वैश्विक और अद्यतन करने योग्य बनाने की आवश्यकता नहीं पड़ी।
इन तर्कों का उपयोग करने के अलावा, निष्पादन का समय मायने रखता है।
3. दैनिक लेन-देन पर SQL CURSOR का उपयोग करना
मैं व्यवस्थापक नहीं हूं। लेकिन मुझे इस बात का अंदाजा है कि डीबीए के टूल (या कितने डेसिबल उपयोगकर्ता चिल्लाते हैं) से एक व्यस्त सर्वर कैसा दिखता है। इन परिस्थितियों में, क्या आप और बोझ डालना चाहेंगे?
यदि आप दिन-प्रतिदिन के लेन-देन के लिए CURSOR के साथ अपना कोड तैयार करने का प्रयास कर रहे हैं, तो फिर से सोचें। छोटे डेटासेट वाले कम व्यस्त सर्वर पर एक बार चलने के लिए CURSORs ठीक हैं। हालांकि, एक सामान्य व्यस्त दिन में, एक कर्सर यह कर सकता है:
- पंक्तियों को लॉक करें, खासकर अगर SCROLL_LOCKS समवर्ती तर्क स्पष्ट रूप से निर्दिष्ट है।
- उच्च-CPU उपयोग के कारण।
- tempdb का उपयोग करें व्यापक रूप से।
कल्पना कीजिए कि इनमें से कई एक सामान्य दिन में एक साथ चल रहे हैं।
हम समाप्त होने वाले हैं लेकिन एक और गलती है जिसके बारे में हमें बात करने की आवश्यकता है।
4. SQL CURSOR के प्रभाव का आकलन नहीं करना
आप जानते हैं कि कर्सर विकल्प अच्छे हैं। क्या आपको लगता है कि उन्हें निर्दिष्ट करना पर्याप्त है? आप ऊपर परिणाम पहले ही देख चुके हैं। टूल के बिना, हम सही निष्कर्ष पर नहीं पहुंच पाते।
इसके अलावा, CURSOR के अंदर कोड . है . यह क्या करता है इसके आधार पर, यह खपत किए गए संसाधनों में और अधिक जोड़ता है। ये अन्य प्रक्रियाओं के लिए उपलब्ध हो सकते हैं। आपका संपूर्ण बुनियादी ढांचा, आपका हार्डवेयर, और SQL सर्वर कॉन्फ़िगरेशन कहानी में और जोड़ देगा।
डेटा की मात्रा . के बारे में कैसा है ? मैंने केवल कुछ सौ रिकॉर्ड्स पर SQL CURSOR का उपयोग किया है। यह आपके लिए अलग हो सकता है। पहले उदाहरण ने केवल 500 रिकॉर्ड लिए क्योंकि यही वह संख्या थी जिसकी प्रतीक्षा करने के लिए मैं सहमत था। 10,000 या 1000 ने भी इसे नहीं काटा। उन्होंने खराब प्रदर्शन किया।
अंततः, चाहे कितना भी कम या अधिक क्यों न हो, उदाहरण के लिए, तार्किक पठन की जाँच करने से फर्क पड़ सकता है।
क्या होगा यदि आप निष्पादन योजना की जांच नहीं करते हैं, तार्किक पढ़ता है, या बीता हुआ समय? एसक्यूएल सर्वर फ्रीज के अलावा और क्या भयानक चीजें हो सकती हैं? हम केवल सभी प्रकार के कयामत के परिदृश्यों की कल्पना कर सकते हैं। आपको बात समझ में आ गई।
निष्कर्ष
SQL CURSOR डेटा पंक्ति को पंक्ति से संसाधित करके काम करता है। इसकी अपनी जगह है, लेकिन अगर आप सावधान नहीं हैं तो यह खराब हो सकता है। यह एक ऐसे टूल की तरह है जो टूलबॉक्स से विरले ही निकलता है।
तो, सबसे पहले, सेट-आधारित कमांड का उपयोग करके समस्या को हल करने का प्रयास करें। यह हमारी अधिकांश SQL आवश्यकताओं का उत्तर देता है। और यदि आप कभी भी SQL CURSOR का उपयोग करते हैं, तो इसे सही विकल्पों के साथ उपयोग करें। निष्पादन योजना, सांख्यिकी IO, और xEvent Profiler के साथ प्रभाव का अनुमान लगाएं। फिर, निष्पादित करने के लिए सही समय चुनें।
यह सब आपके SQL CURSOR के उपयोग को थोड़ा बेहतर बना देगा।