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

SQLite जाल और नुकसान

SQLite एक लोकप्रिय, रिलेशनल डेटाबेस है जिसे आप अपने एप्लिकेशन में एम्बेड करते हैं। हालांकि, ऐसे कई जाल और नुकसान हैं जिनसे आपको बचना चाहिए। यह लेख कई नुकसानों (और उनसे कैसे बचा जाए) पर चर्चा करता है, जैसे कि ओआरएम का उपयोग, डिस्क स्थान को कैसे पुनः प्राप्त करना है, क्वेरी चर की अधिकतम संख्या, कॉलम डेटा प्रकार और बड़े पूर्णांक को कैसे संभालना है।

परिचय

SQLite एक लोकप्रिय रिलेशनल डेटाबेस (DB) सिस्टम है . इसकी एक बहुत ही समान विशेषता है जो इसके बड़े भाइयों के रूप में सेट है, जैसे कि MySQL , जो क्लाइंट/सर्वर-आधारित सिस्टम हैं। हालांकि, SQLite एक एम्बेडेड है डेटाबेस . इसे आपके प्रोग्राम में एक स्थिर (या गतिशील) पुस्तकालय के रूप में शामिल किया जा सकता है। यह परिनियोजन को सरल करता है , क्योंकि कोई अलग सर्वर प्रक्रिया आवश्यक नहीं है। बाइंडिंग और रैपर लाइब्रेरी आपको अधिकांश प्रोग्रामिंग भाषाओं में SQLite एक्सेस करने . देती हैं ।

मैं अपने पीएचडी शोध प्रबंध के हिस्से के रूप में बीएसआईएनसी विकसित करते हुए बड़े पैमाने पर SQLite के साथ काम कर रहा हूं। यह लेख उन जालों और नुकसानों की एक (यादृच्छिक) सूची है, जिन पर मैंने विकास के दौरान ठोकर खाई थी . मुझे आशा है कि आप उन्हें उपयोगी पाएंगे और वही गलतियाँ करने से बचेंगे जो मैंने एक बार की थी।

जाल और नुकसान

ओआरएम लाइब्रेरी का सावधानी से उपयोग करें

ऑब्जेक्ट-रिलेशनल मैपिंग (ORM) लाइब्रेरी कंक्रीट डेटाबेस इंजन और उनके सिंटैक्स (जैसे विशिष्ट SQL स्टेटमेंट) से विवरण को एक उच्च-स्तरीय, ऑब्जेक्ट-ओरिएंटेड एपीआई में सार करती है। वहाँ कई तृतीय पक्ष पुस्तकालय हैं (विकिपीडिया देखें)। ORM पुस्तकालयों के कुछ लाभ हैं:

  • वे विकास के दौरान समय बचाते हैं , क्योंकि वे जल्दी से आपके कोड/कक्षाओं को DB संरचनाओं में मैप करते हैं,
  • वे अक्सर क्रॉस-प्लेटफ़ॉर्म . होते हैं , यानी, ठोस डीबी तकनीक (जैसे MySQL के साथ SQLite) के प्रतिस्थापन की अनुमति दें,
  • वे स्कीमा माइग्रेशन के लिए सहायक कोड ऑफ़र करते हैं ।

हालांकि, उनके कई गंभीर नुकसान भी हैं आपको इसके बारे में पता होना चाहिए:

  • वे डेटाबेस के साथ काम करते हैं दिखाई देते हैं आसान . हालांकि, वास्तव में, DB इंजनों में जटिल विवरण होते हैं जिन्हें आपको केवल जानना होता है . एक बार कुछ गलत हो जाता है, उदा। जब ORM लाइब्रेरी उन अपवादों को फेंकती है जिन्हें आप नहीं समझते हैं, या जब रन-टाइम प्रदर्शन ख़राब हो जाता है, ORM का उपयोग करके आपके द्वारा बचाए गए विकास समय को समस्या को डीबग करने के लिए आवश्यक प्रयासों द्वारा जल्दी से खा लिया जाएगा . उदाहरण के लिए, यदि आप नहीं जानते कि सूचकांक . क्या हैं हैं, आपको ओआरएम के कारण होने वाली प्रदर्शन बाधाओं का निवारण करने में कठिन समय होगा, जब यह स्वचालित रूप से सभी आवश्यक सूचकांक नहीं बनाता था। संक्षेप में:कोई मुफ्त लंच नहीं है।
  • ठोस डीबी विक्रेता के अमूर्त होने के कारण, विक्रेता-विशिष्ट कार्यक्षमता तक पहुंचना कठिन है, बिल्कुल भी पहुंच योग्य नहीं है
  • कुछ कम्प्यूटेशनल ओवरहेड है सीधे SQL क्वेरी लिखने और निष्पादित करने की तुलना में। हालांकि, मैं कहूंगा कि यह बिंदु व्यवहार में विवादास्पद है, क्योंकि उच्च स्तर के अमूर्तन पर स्विच करने के बाद यह सामान्य है कि आप प्रदर्शन खो देते हैं।

अंत में, ORM लाइब्रेरी का उपयोग करना व्यक्तिगत प्राथमिकता का मामला है। यदि आप ऐसा करते हैं, तो बस तैयार रहें कि आपको संबंधपरक डेटाबेस (और विक्रेता-विशिष्ट चेतावनी) के बारे में सीखना होगा, एक बार अप्रत्याशित व्यवहार या प्रदर्शन की अड़चनें आने पर।

शुरू से एक माइग्रेशन तालिका शामिल करें

अगर आप नहीं हैं ORM लाइब्रेरी का उपयोग करते हुए, आपको DB के स्कीमा माइग्रेशन . का ध्यान रखना होगा . इसमें माइग्रेशन कोड लिखना शामिल है जो आपके टेबल स्कीमा को बदल देता है और संग्रहीत डेटा को किसी तरह से बदल देता है। मैं अनुशंसा करता हूं कि आप "माइग्रेशन" या "संस्करण" नामक एक तालिका बनाएं, जिसमें एक पंक्ति और कॉलम हो, जो केवल स्कीमा संस्करण को संग्रहीत करता है, उदा। एक नीरस रूप से बढ़ते पूर्णांक का उपयोग करना। यह आपके माइग्रेशन फ़ंक्शन को यह पता लगाने देता है कि किन माइग्रेशन को अभी भी लागू करने की आवश्यकता है। जब भी कोई माइग्रेशन चरण सफलतापूर्वक पूरा हो जाता है, तो आपका माइग्रेशन टूलिंग कोड इस काउंटर को UPDATE . के माध्यम से बढ़ा देता है SQL कथन।

स्वतः निर्मित पंक्ति स्तंभ

जब भी आप कोई तालिका बनाते हैं, SQLite स्वचालित रूप से एक INTEGER बना देगा rowid . नाम का कॉलम आपके लिए - जब तक कि आपने WITHOUT ROWID प्रदान किया हो खंड (लेकिन संभावना है कि आप इस खंड के बारे में नहीं जानते थे)। rowid पंक्ति एक प्राथमिक कुंजी स्तंभ है। यदि आप स्वयं भी ऐसा प्राथमिक कुंजी कॉलम निर्दिष्ट करते हैं (उदाहरण के लिए सिंटैक्स some_column INTEGER PRIMARY KEY का उपयोग करके) ) यह कॉलम बस एक उपनाम . होगा rowid . के लिए . अधिक जानकारी के लिए यहां देखें, जो एक ही चीज़ का वर्णन गूढ़ शब्दों में करता है। ध्यान दें कि एक SELECT * FROM table बयान नहीं होगा rowid शामिल करें डिफ़ॉल्ट रूप से - आपको rowid . के लिए पूछना होगा स्तंभ स्पष्ट रूप से।

सत्यापित करें कि PRAGMA वास्तव में काम करता है

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

उदाहरण के लिए, यदि आप दिए गए क्रम में निम्नलिखित कथन जारी करते हैं, तो अंतिम कथन नहीं होगा कोई प्रभाव हो। चर auto_vacuum अभी भी मूल्य है 0 (NONE ), बिना किसी अच्छे कारण के।

PRAGMA journal_mode = WAL
PRAGMA synchronous = NORMAL
PRAGMA auto_vacuum = INCREMENTAL
Code language: SQL (Structured Query Language) (sql)

आप PRAGMA variableName . को क्रियान्वित करके एक वेरिएबल के मान को पढ़ सकते हैं और समान चिह्न और मान को छोड़ देना।

उपरोक्त उदाहरण को ठीक करने के लिए, किसी भिन्न क्रम का उपयोग करें। पंक्ति क्रमांक 3, 1, 2 का उपयोग करना अपेक्षानुसार काम करेगा।

आप अपने उत्पादन . में ऐसे चेक शामिल करना चाह सकते हैं कोड, क्योंकि ये दुष्प्रभाव ठोस SQLite संस्करण पर निर्भर हो सकते हैं और इसे कैसे बनाया गया था। उत्पादन में उपयोग की जाने वाली लाइब्रेरी आपके द्वारा विकास के दौरान उपयोग की जाने वाली लाइब्रेरी से भिन्न हो सकती है।

बड़े डेटाबेस के लिए डिस्क स्थान का दावा करना

डिफ़ॉल्ट रूप से, SQLite डेटाबेस फ़ाइल का आकार एकरस रूप से बढ़ रहा है . पंक्तियों को हटाना केवल विशिष्ट पृष्ठों को निःशुल्क . के रूप में चिह्नित करता है , ताकि उनका उपयोग INSERT . में किया जा सके भविष्य में डेटा। वास्तव में डिस्क स्थान को पुनः प्राप्त करने के लिए, और प्रदर्शन को गति देने के लिए, दो विकल्प हैं:

  1. VACUUM निष्पादित करें कथन . हालांकि, इसके कई दुष्प्रभाव हैं:
    • यह पूरे DB को लॉक कर देता है। VACUUM . के दौरान कोई समवर्ती संचालन नहीं हो सकता है ऑपरेशन।
    • इसमें लंबा समय लगता है (बड़े डेटाबेस के लिए), क्योंकि यह आंतरिक रूप से फिर से बनाता है डीबी एक अलग, अस्थायी फ़ाइल में, और अंत में मूल डेटाबेस को हटा देता है, इसे उस अस्थायी फ़ाइल से बदल देता है।
    • अस्थायी फ़ाइल अतिरिक्त की खपत करती है डिस्क स्थान जबकि ऑपरेशन चल रहा है। इस प्रकार, VACUUM . चलाना एक अच्छा विचार नहीं है यदि आपके पास डिस्क स्थान कम है। आप इसे अभी भी कर सकते हैं, लेकिन आपको नियमित रूप से यह जांचना होगा कि (freeDiskSpace - currentDbFileSize) > 0 .
  2. PRAGMA auto_vacuum = INCREMENTAL का उपयोग करें जब बनाना डीबी. इसे PRAGMA बनाएं पहला फ़ाइल बनाने के बाद बयान! यह कुछ आंतरिक हाउस-कीपिंग को सक्षम बनाता है, जब भी आप PRAGMA incremental_vacuum(N) कॉल करते हैं, तो डेटाबेस को स्थान पुनः प्राप्त करने में मदद मिलती है। . यह कॉल N . तक पुनः प्राप्त करता है पृष्ठ। आधिकारिक दस्तावेज़ auto_vacuum . के लिए अधिक विवरण, और अन्य संभावित मान भी प्रदान करते हैं .
    • नोट:आप यह निर्धारित कर सकते हैं कि PRAGMA incremental_vacuum(N) को कॉल करते समय कितना खाली डिस्क स्थान (बाइट्स में) प्राप्त होगा। :PRAGMA freelist_count . द्वारा लौटाए गए मान को गुणा करें PRAGMA page_size . के साथ ।

बेहतर विकल्प आपके संदर्भ पर निर्भर करता है। बहुत बड़ी डेटाबेस फ़ाइलों के लिए मैं विकल्प 2 का सुझाव देता हूं , क्योंकि विकल्प 1 आपके उपयोगकर्ताओं को डेटाबेस को साफ करने के लिए मिनटों या घंटों प्रतीक्षा करने से परेशान करेगा। विकल्प 1 छोटे डेटाबेस के लिए उपयुक्त है . इसका अतिरिक्त लाभ यह है कि प्रदर्शन डीबी में सुधार होगा (जो विकल्प 2 के मामले में नहीं है), क्योंकि मनोरंजन डेटा विखंडन के दुष्प्रभावों को समाप्त करता है।

प्रश्नों में वेरिएबल की अधिकतम संख्या का ध्यान रखें

डिफ़ॉल्ट रूप से, वेरिएबल की अधिकतम संख्या (“होस्ट पैरामीटर”) जिसका आप किसी क्वेरी में उपयोग कर सकते हैं, हार्ड-कोडेड 999 है (यहां देखें, अनुभाग एक एकल SQL कथन में होस्ट पैरामीटर की अधिकतम संख्या ) यह सीमा भिन्न हो सकती है, क्योंकि यह एक संकलन-समय . है पैरामीटर, जिसका डिफ़ॉल्ट मान आपने (या SQLite को संकलित करने वाला कोई भी) बदल दिया हो सकता है।

यह व्यवहार में समस्याग्रस्त है, क्योंकि यह असामान्य नहीं है कि आपका एप्लिकेशन डीबी इंजन को एक (मनमाने ढंग से बड़ी) सूची प्रदान करता है। उदाहरण के लिए यदि आप मास-DELETE . करना चाहते हैं (या SELECT ) पंक्तियों के आधार पर, कहते हैं, आईडी की एक सूची। एक बयान जैसे

DELETE FROM some_table WHERE rowid IN (?, ?, ?, ?, <999 times "?, ">, ?)Code language: SQL (Structured Query Language) (sql)

त्रुटि देगा और पूरा नहीं करेगा।

इसे ठीक करने के लिए, निम्न चरणों पर विचार करें:

  • अपनी सूचियों का विश्लेषण करें और उन्हें छोटी सूचियों में विभाजित करें,
  • यदि विभाजन आवश्यक था, तो सुनिश्चित करें कि BEGIN TRANSACTION का उपयोग करें और COMMIT परमाणुता का अनुकरण करने के लिए एक ही कथन होता
  • सुनिश्चित करें कि आप अन्य ? वे चर जो आप अपनी क्वेरी में उपयोग कर सकते हैं जो आने वाली सूची से संबंधित नहीं हैं (जैसे ? ORDER BY . में उपयोग किए जाने वाले चर शर्त), ताकि कुल चरों की संख्या सीमा से अधिक नहीं है।

एक वैकल्पिक समाधान अस्थायी तालिकाओं का उपयोग है। विचार एक अस्थायी तालिका बनाना है, क्वेरी चर को पंक्तियों के रूप में सम्मिलित करना है, और फिर उस अस्थायी तालिका का उपयोग एक सबक्वायरी में करना है, उदा.

DROP TABLE IF EXISTS temp.input_data
CREATE TABLE temp.input_data (some_column TEXT UNIQUE)
# Insert input data, running the next query multiple times
INSERT INTO temp.input_data (some_column) VALUES (...)
# The above DELETE statement now changes to this one:
DELETE FROM some_table WHERE rowid IN (SELECT some_column from temp.input_data)Code language: SQL (Structured Query Language) (sql)

SQLite के प्रकार एफ़िनिटी से सावधान रहें

SQLite कॉलम सख्ती से टाइप नहीं किए गए हैं, और जरूरी नहीं कि रूपांतरण आपकी अपेक्षा के अनुरूप हों। आपके द्वारा प्रदान किए जाने वाले प्रकार केवल संकेत हैं . SQLite अक्सर किसी भी . का डेटा संग्रहीत करेगा इसके मूल में टाइप करें प्रकार, और रूपांतरण दोषरहित होने की स्थिति में केवल डेटा को कॉलम के प्रकार में परिवर्तित करें। उदाहरण के लिए, आप बस एक "hello" . डाल सकते हैं एक INTEGER . में स्ट्रिंग करें कॉलम। SQLite शिकायत नहीं करेगा, या आपको बेमेल प्रकार के बारे में चेतावनी नहीं देगा। इसके विपरीत, आप उम्मीद नहीं कर सकते हैं कि डेटा SELECT . द्वारा लौटाया गया है INTEGER . का विवरण कॉलम हमेशा एक INTEGER होता है . इस प्रकार के संकेतों को SQLite-speak में "टाइप एफ़िनिटी" के रूप में संदर्भित किया जाता है, यहां देखें। नई तालिकाएँ बनाते समय आपके द्वारा निर्दिष्ट कॉलम प्रकारों के अर्थ को बेहतर ढंग से समझने के लिए, SQLite मैनुअल के इस भाग का बारीकी से अध्ययन करना सुनिश्चित करें।

बड़े पूर्णांकों से सावधान रहें

SQLite हस्ताक्षरित का समर्थन करता है 64-बिट पूर्णांक , जिसे वह स्टोर कर सकता है, या संगणना कर सकता है। दूसरे शब्दों में, केवल -2^63 . से संख्याएं से (2^63) - 1 समर्थित हैं, क्योंकि संकेत को दर्शाने के लिए एक बिट की आवश्यकता होती है!

इसका मतलब है कि यदि आप बड़ी संख्या के साथ काम करने की उम्मीद करते हैं, उदा। 128-बिट (हस्ताक्षरित) पूर्णांक या अहस्ताक्षरित 64-बिट पूर्णांक, आपको चाहिए डेटा को पाठ में बदलें डालने से पहले

डरावना तब शुरू होता है जब आप इसे अनदेखा करते हैं और बस बड़ी संख्याएं (पूर्णांक के रूप में) डालते हैं। SQLite शिकायत नहीं करेगा और एक गोलाकार . संग्रहित करेगा इसके बजाय नंबर! उदाहरण के लिए, यदि आप 2^63 (जो पहले से समर्थित सीमा से बाहर है) सम्मिलित करते हैं, तो SELECT ed मान 9223372036854776000 होगा, न कि 2^63=9223372036854775808। आपके द्वारा उपयोग की जाने वाली प्रोग्रामिंग भाषा और बाध्यकारी पुस्तकालय के आधार पर, व्यवहार भिन्न हो सकता है, यद्यपि! उदाहरण के लिए, Python का sqlite3 बाइंडिंग ऐसे पूर्णांक ओवरफ़्लो की जाँच करता है!

REPLACE() का उपयोग न करें फ़ाइल पथों के लिए

मान लें कि आप एक TEXT . में सापेक्ष या पूर्ण फ़ाइल पथ संग्रहीत करते हैं SQLite में कॉलम, उदा। वास्तविक फाइल सिस्टम पर फाइलों का ट्रैक रखने के लिए। यहां तीन पंक्तियों का उदाहरण दिया गया है:

foo/test.txt
foo/bar/
foo/bar/x.y

मान लीजिए कि आप निर्देशिका "foo" का नाम बदलकर "xyz" करना चाहते हैं। आप किस SQL ​​​​कमांड का उपयोग करेंगे? यह वाला?

REPLACE(path_column, old_path, new_path) Code language: SQL (Structured Query Language) (sql)

मैंने यही किया, जब तक कि अजीब चीजें होने लगीं। REPLACE() . के साथ समस्या क्या यह सभी . का स्थान ले लेगा घटनाएँ यदि पथ "foo/bar/foo/" के साथ एक पंक्ति थी, तो REPLACE(column_name, 'foo/', 'xyz/') कहर बरपाएगा, क्योंकि परिणाम “xyz/bar/foo/” नहीं, बल्कि “xyz/bar/xyz/” होगा।

एक बेहतर समाधान कुछ इस तरह है

UPDATE mytable SET path_column = 'xyz/' || substr(path_column, 4) WHERE path_column GLOB 'foo/*'"Code language: SQL (Structured Query Language) (sql)

4 पुराने पथ की लंबाई को दर्शाता है (इस मामले में 'फू/')। ध्यान दें कि मैंने GLOB . का उपयोग किया है LIKE . के बजाय केवल उन पंक्तियों को अपडेट करने के लिए जो शुरू 'फू/' के साथ।

निष्कर्ष

SQLite एक शानदार डेटाबेस इंजन है, जहाँ अधिकांश कमांड अपेक्षा के अनुरूप काम करते हैं। हालाँकि, विशिष्ट पेचीदगियों, जैसा कि मैंने अभी प्रस्तुत किया है, अभी भी एक डेवलपर के ध्यान की आवश्यकता है। इस लेख के अलावा, सुनिश्चित करें कि आपने आधिकारिक SQLite चेतावनी दस्तावेज़ भी पढ़ा है।

क्या आपने अतीत में अन्य चेतावनियों का सामना किया है? अगर ऐसा है, तो मुझे टिप्पणियों में बताएं।


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. SQLite - एक तालिका बदलें

  2. sqlite3_exec त्रुटि के साथ इंस्टॉलेशन पर एप्लिकेशन क्रैश - सिंक्रोनस मोड =1 (सामान्य) सेट करने में विफल

  3. रूम माइग्रेशन ऑल्टर टेबल नया कॉलम नहीं जोड़ रहा है और बार-बार कॉल करने पर माइग्रेट हो रहा है

  4. Android के लिए SQLite डेटाबेस के प्रदर्शन में सुधार कैसे करें

  5. SQLite SUM