Database
 sql >> डेटाबेस >  >> RDS >> Database

आखिरी बार, नहीं, आप IDENT_CURRENT() पर भरोसा नहीं कर सकते

मैंने कल केंडल वैन डाइक (@SQLDBA) के साथ IDENT_CURRENT() के बारे में चर्चा की थी। मूल रूप से, केंडल के पास यह कोड था, जिसका उसने स्वयं परीक्षण किया था और उस पर भरोसा किया था, और जानना चाहता था कि क्या वह उच्च-स्तरीय, समवर्ती वातावरण में IDENT_CURRENT() के सटीक होने पर भरोसा कर सकता है:

BEGIN TRANSACTION;
INSERT dbo.TableName(ColumnName) VALUES('Value');
SELECT IDENT_CURRENT('dbo.TableName');
COMMIT TRANSACTION;

उसे ऐसा करने का कारण यह है कि उसे क्लाइंट को उत्पन्न पहचान मूल्य वापस करने की आवश्यकता है। हम ऐसा करने के सामान्य तरीके हैं:

  • SCOPE_IDENTITY()
  • आउटपुट क्लॉज
  • @@ पहचान
  • IDENT_CURRENT()

इनमें से कुछ दूसरों की तुलना में बेहतर हैं, लेकिन यह मौत के लिए किया गया है, और मैं यहां इसमें शामिल नहीं होने जा रहा हूं। केंडल के मामले में, IDENT_CURRENT उनका अंतिम और एकमात्र सहारा था, क्योंकि:

  • TableName में INSERT ट्रिगर का एक INSTEAD था, जिससे SCOPE_IDENTITY() और OUTPUT क्लॉज दोनों कॉलर से बेकार हो गए, क्योंकि:
    • SCOPE_IDENTITY() NULL लौटाता है, क्योंकि इंसर्ट वास्तव में एक अलग दायरे में हुआ था
    • आउटपुट क्लॉज ट्रिगर के कारण त्रुटि संदेश 334 उत्पन्न करता है
  • उन्होंने @@IDENTITY को हटा दिया; विचार करें कि INSTAD OF INSERT ट्रिगर अब अन्य तालिकाओं में सम्मिलित हो सकता है (या बाद में बदला जा सकता है) जिनके पास अपने स्वयं के पहचान कॉलम हैं, जो लौटाए गए मूल्य को गड़बड़ कर देंगे। यदि संभव हो तो यह SCOPE_IDENTITY() को भी विफल कर देगा।
  • और अंत में, वह ट्रिगर के भीतर OUTPUT क्लॉज (या अंतिम इंसर्ट के बाद सम्मिलित छद्म-तालिका की दूसरी क्वेरी से परिणामसेट) का उपयोग नहीं कर सका, क्योंकि इस क्षमता के लिए एक वैश्विक सेटिंग की आवश्यकता होती है, और तब से इसे बहिष्कृत कर दिया गया है SQL सर्वर 2005। जाहिर है, केंडल के कोड को आगे संगत होना चाहिए और जब संभव हो, पूरी तरह से कुछ डेटाबेस या सर्वर सेटिंग्स पर निर्भर नहीं होना चाहिए।

तो, वापस केंडल की वास्तविकता पर। उनका कोड काफी सुरक्षित लगता है - यह एक लेन-देन में है, आखिरकार; क्या गलत हो सकता था? खैर, आइए IDENT_CURRENT दस्तावेज़ीकरण से कुछ महत्वपूर्ण वाक्यों पर एक नज़र डालें (जोर मेरा है, क्योंकि ये चेतावनियां अच्छे कारण के लिए हैं):

किसी निर्दिष्ट तालिका या दृश्य के लिए उत्पन्न अंतिम पहचान मान लौटाता है। जनरेट किया गया अंतिम पहचान मान किसी भी सत्र . के लिए हो सकता है और कोई भी दायरा

अगले उत्पन्न पहचान मूल्य की भविष्यवाणी करने के लिए IDENT_CURRENT का उपयोग करने के बारे में सतर्क रहें। वास्तविक उत्पन्न मान भिन्न हो सकता है IDENT_CURRENT प्लस IDENT_INCR से अन्य सत्रों द्वारा किए गए सम्मिलन के कारण .

दस्तावेज़ के मुख्य भाग में लेन-देन का बमुश्किल उल्लेख किया गया है (केवल विफलता के संदर्भ में, समवर्ती नहीं), और किसी भी नमूने में लेनदेन का उपयोग नहीं किया जाता है। तो, आइए देखें कि केंडल क्या कर रहा था, और देखें कि जब कई सत्र एक साथ चल रहे हों तो क्या हम इसे विफल कर सकते हैं। मैं प्रत्येक सत्र द्वारा उत्पन्न मूल्यों का ट्रैक रखने के लिए एक लॉग तालिका बनाने जा रहा हूं - दोनों पहचान मूल्य जो वास्तव में उत्पन्न हुए थे (एक ट्रिगर के बाद का उपयोग करके), और मूल्य जो IDENT_CURRENT() के अनुसार उत्पन्न होने का दावा किया गया था।

सबसे पहले, टेबल और ट्रिगर:

-- the destination table:
 
CREATE TABLE dbo.TableName
(
  ID INT IDENTITY(1,1), 
  seq INT
);
 
-- the log table:
 
CREATE TABLE dbo.IdentityLog
(
  SPID INT, 
  seq INT, 
  src VARCHAR(20), -- trigger or ident_current 
  id INT
);
GO
 
-- the trigger, adding my logging:
 
CREATE TRIGGER dbo.InsteadOf_TableName
ON dbo.TableName
INSTEAD OF INSERT
AS
BEGIN
  INSERT dbo.TableName(seq) SELECT seq FROM inserted;
 
  -- this is just for our logging purposes here:
  INSERT dbo.IdentityLog(SPID,seq,src,id)
    SELECT @@SPID, seq, 'trigger', SCOPE_IDENTITY() 
    FROM inserted;
END
GO

अब, मुट्ठी भर क्वेरी विंडो खोलें, और इस कोड को पेस्ट करें, अधिक से अधिक ओवरलैप सुनिश्चित करने के लिए उन्हें यथासंभव एक साथ निष्पादित करें:

SET NOCOUNT ON;
 
DECLARE @seq INT = 0;
 
WHILE @seq <= 100000
BEGIN
  BEGIN TRANSACTION;
 
  INSERT dbo.TableName(seq) SELECT @seq;
  INSERT dbo.IdentityLog(SPID,seq,src,id)
    SELECT @@SPID,@seq,'ident_current',IDENT_CURRENT('dbo.TableName');
 
  COMMIT TRANSACTION;
  SET @seq += 1;
END

एक बार सभी क्वेरी विंडो पूरी हो जाने के बाद, कुछ यादृच्छिक पंक्तियों को देखने के लिए इस क्वेरी को चलाएँ जहाँ IDENT_CURRENT ने गलत मान लौटाया, और कुल कितनी पंक्तियाँ इस गलत रिपोर्ट की गई संख्या से प्रभावित हुईं:

SELECT TOP (10)
  id_cur.SPID,  
  [ident_current] = id_cur.id, 
  [actual id] = tr.id, 
  total_bad_results = COUNT(*) OVER()
FROM dbo.IdentityLog AS id_cur
INNER JOIN dbo.IdentityLog AS tr
   ON id_cur.SPID = tr.SPID 
   AND id_cur.seq = tr.seq 
   AND id_cur.id <> tr.id
WHERE id_cur.src = 'ident_current' 
   AND tr.src     = 'trigger'
ORDER BY NEWID();
पर

यहाँ एक परीक्षण के लिए मेरी 10 पंक्तियाँ हैं:

मुझे यह आश्चर्यजनक लगा कि लगभग एक तिहाई पंक्तियाँ बंद थीं। आपके परिणाम निश्चित रूप से भिन्न होंगे, और आपके ड्राइव की गति, पुनर्प्राप्ति मॉडल, लॉग फ़ाइल सेटिंग्स, या अन्य कारकों पर निर्भर हो सकते हैं। दो अलग-अलग मशीनों पर, मेरी विफलता दर बहुत अलग थी - 10 के कारक से (एक धीमी मशीन केवल 10,000 विफलताओं के पड़ोस में थी, या लगभग 3%)।

तुरंत यह स्पष्ट हो जाता है कि IDENT_CURRENT को अन्य सत्रों द्वारा उत्पन्न IDENTITY मानों को खींचने से रोकने के लिए लेन-देन पर्याप्त नहीं है। एक सीरियल लेनदेन के बारे में कैसे? सबसे पहले, दो टेबल साफ़ करें:

TRUNCATE TABLE dbo.TableName;
TRUNCATE TABLE dbo.IdentityLog;

फिर, इस कोड को कई क्वेरी विंडो में स्क्रिप्ट की शुरुआत में जोड़ें, और उन्हें यथासंभव समवर्ती रूप से फिर से चलाएँ:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

इस बार, जब मैं IdentityLog तालिका के विरुद्ध क्वेरी चलाता हूं, तो यह दिखाता है कि SERIALIZABLE ने थोड़ी मदद की हो सकती है, लेकिन इसने समस्या का समाधान नहीं किया है:

और जबकि गलत गलत है, यह मेरे नमूना परिणामों से दिखता है कि IDENT_CURRENT मान आमतौर पर केवल एक या दो से बंद होता है। हालाँकि, इस क्वेरी से यह पता चलेगा कि यह *रास्ता* बंद हो सकता है। मेरे टेस्ट रन में, यह परिणाम 236 जितना ऊंचा था:

SELECT MAX(ABS(id_cur.id - tr.id))
FROM dbo.IdentityLog AS id_cur
INNER JOIN dbo.IdentityLog AS tr
  ON id_cur.SPID = tr.SPID 
  AND id_cur.seq = tr.seq 
  AND id_cur.id <> tr.id
WHERE id_cur.src = 'ident_current' 
  AND tr.src     = 'trigger';

इस साक्ष्य के माध्यम से हम यह निष्कर्ष निकाल सकते हैं कि IDENT_CURRENT लेन-देन-सुरक्षित नहीं है। यह एक समान लेकिन लगभग विपरीत समस्या की याद दिलाता है, जहां OBJECT_NAME() जैसे मेटाडेटा कार्य अवरुद्ध हो जाते हैं - तब भी जब अलगाव स्तर पढ़ा जाता है - क्योंकि वे आसपास के अलगाव शब्दार्थ का पालन नहीं करते हैं। (अधिक जानकारी के लिए कनेक्ट आइटम #432497 देखें।)

सतह पर, और वास्तुकला और अनुप्रयोगों के बारे में बहुत कुछ जानने के बिना, मेरे पास केंडल के लिए वास्तव में एक अच्छा सुझाव नहीं है; मुझे बस इतना पता है कि IDENT_CURRENT उत्तर *नहीं* है। :-) बस इसका इस्तेमाल न करें। किसी भी चीज़ के लिए। हमेशा। जब तक आप मान पढ़ते हैं, तब तक यह गलत हो सकता है।


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. प्रदर्शन आश्चर्य और अनुमान :STRING_SPLIT ()

  2. निष्पादन योजना ऑपरेटर समय को समझना

  3. शुरुआती के लिए गिट टिप्स और सर्वोत्तम अभ्यास

  4. SQL डेवलपर का औसत वेतन क्या है?

  5. आईआरआई कार्यक्षेत्र में ईआर आरेख