Android में SQLite के लिए पहले से मौजूद सहायता मिलती है. यह एक असरदार एसक्यूएल डेटाबेस है. अपने ऐप्लिकेशन की परफ़ॉर्मेंस को ऑप्टिमाइज़ करने के लिए, इन सबसे सही तरीकों को अपनाएं. इससे यह पक्का किया जा सकेगा कि डेटा बढ़ने पर भी आपका ऐप्लिकेशन तेज़ी से काम करे. इन सबसे सही तरीकों को अपनाकर, परफ़ॉर्मेंस की समस्याओं का सामना करने की संभावना भी कम हो जाती है जिन्हें फिर से पैदा करना और समस्या का हल करना मुश्किल होता है.
बेहतर परफ़ॉर्मेंस पाने के लिए, परफ़ॉर्मेंस से जुड़े इन सिद्धांतों का पालन करें:
कम पंक्तियां और कॉलम पढ़ें: अपनी क्वेरी को ऑप्टिमाइज़ करें, ताकि सिर्फ़ ज़रूरी डेटा ही वापस लाया जा सके. डेटाबेस से कम से कम डेटा पढ़ें, क्योंकि ज़्यादा डेटा वापस पाने से परफ़ॉर्मेंस पर असर पड़ सकता है.
SQLite इंजन पर काम को पुश करना: एसक्यूएल क्वेरी में कंप्यूटेशन, फ़िल्टरिंग, और क्रम से लगाने की कार्रवाइयां करें. SQLite के क्वेरी इंजन का इस्तेमाल करने से, परफ़ॉर्मेंस को काफ़ी बेहतर बनाया जा सकता है.
डेटाबेस स्कीमा में बदलाव करना: अपने डेटाबेस स्कीमा को इस तरह डिज़ाइन करें कि SQLite, क्वेरी प्लान और डेटा को बेहतर तरीके से दिखा सके. टेबल को सही तरीके से इंडेक्स करें और टेबल के स्ट्रक्चर को ऑप्टिमाइज़ करें, ताकि परफ़ॉर्मेंस को बेहतर बनाया जा सके.
इसके अलावा, समस्या हल करने के लिए उपलब्ध टूल का इस्तेमाल करके, अपने SQLite डेटाबेस की परफ़ॉर्मेंस को मेज़र किया जा सकता है. इससे उन हिस्सों की पहचान करने में मदद मिलती है जिन्हें ऑप्टिमाइज़ करने की ज़रूरत है.
हमारा सुझाव है कि आप Jetpack Room लाइब्रेरी का इस्तेमाल करें.
परफ़ॉर्मेंस के लिए डेटाबेस को कॉन्फ़िगर करना
SQLite में अपने डेटाबेस को बेहतर परफ़ॉर्मेंस के लिए कॉन्फ़िगर करने के लिए, इस सेक्शन में दिया गया तरीका अपनाएं.
राइट-अहेड लॉगिंग की सुविधा चालू करना
SQLite, म्यूटेशन को लॉग में जोड़कर लागू करता है. कभी-कभी, यह उन्हें डेटाबेस में कंपैक्ट कर देता है. इसे राइट-अहेड लॉगिंग (डब्ल्यूएएल) कहा जाता है.
WAL को चालू करें. हालांकि, अगर ATTACH DATABASE का इस्तेमाल किया जा रहा है, तो ऐसा करने की ज़रूरत नहीं है.
PRAGMA journal_mode=WAL को खुद चलाने के बजाय, फ़्रेमवर्क के ज़रिए WAL को चालू करें. Room, कम रैम वाले डिवाइसों को छोड़कर, अन्य डिवाइसों पर WAL को डिफ़ॉल्ट रूप से चालू करता है.
अगर रूम नहीं है, तो इनमें से किसी एक विकल्प का इस्तेमाल करें:
enableWriteAheadLoggingतरीकाENABLE_WRITE_AHEAD_LOGGINGओपन फ़्लैगSQLiteOpenHelperकाsetWriteAheadLoggingEnabledतरीका
जब फ़्रेमवर्क को पता चलता है कि डेटाबेस, WAL का इस्तेमाल करता है, तो वह कनेक्शन का एक पूल खोलता है, ताकि रीड और राइट की प्रोसेस एक साथ चल सकें. साथ ही, वह WAL सिंक्रोनस मोड लागू करता है. इसके बारे में अगले सेक्शन में बताया गया है. अगर आपने खुद PRAGMA journal_mode=WAL को चलाया है, तो फ़्रेमवर्क अब भी डेटाबेस को रोलबैक जर्नल डेटाबेस के तौर पर ही ट्रीट करेगा. यह एक ही कनेक्शन का इस्तेमाल करता है. साथ ही, कमिट डिफ़ॉल्ट FULL सिंक्रोनस मोड को बनाए रखते हैं.
सिंक करने के मोड को आसान बनाना
SQLite में सिंक्रोनस मोड को कंट्रोल करने का विकल्प होता है. FULL मोड में, हर कमिट एक fsync जारी करता है, ताकि यह पक्का किया जा सके कि डेटा डिस्क तक पहुंच जाए. इससे डेटा की ड्यूरेबिलिटी बेहतर होती है, लेकिन कमिट की प्रोसेस धीमी हो जाती है. WAL के साथ, NORMAL मोड हर कमिट पर सिंक नहीं होता. इसके बावजूद, यह डेटाबेस को खराब होने से बचाता है.
फ़्रेमवर्क की मदद से WAL को चालू करने पर, Android डिवाइस के डिफ़ॉल्ट WAL सिंक्रोनस मोड को लागू करता है. यह Android 9 और इसके बाद के वर्शन के लिए, एओएसपी में NORMAL होता है.
डिवाइस बनाने वाली कंपनियां, इस डिफ़ॉल्ट सेटिंग को बदल सकती हैं. साथ ही, WAL का इस्तेमाल न करने वाले डेटाबेस, डिफ़ॉल्ट रूप से FULL पर सेट होते हैं. यह पक्का करने के लिए कि आपका डेटाबेस NORMAL का इस्तेमाल करता है, डेटाबेस खोलते समय इसे सेट करें:
// When opening the database
val params = SQLiteDatabase.OpenParams.Builder()
.addOpenFlags(SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING)
// SYNC_MODE_NORMAL is available in API level 33 and higher.
// On API levels 28 to 32, pass "NORMAL" instead.
.setSynchronousMode(SQLiteDatabase.SYNC_MODE_NORMAL)
.build()
// With SQLiteOpenHelper, call setOpenParams(params) in your constructor.
// Or: after having opened the database
db.execSQL("PRAGMA synchronous = NORMAL")
इस सेटिंग में, डिस्क में डेटा सेव होने से पहले कमिट वापस आ सकता है. अगर डिवाइस बंद हो जाता है, जैसे कि पावर बंद होने या कर्नेल पैनिक की वजह से, तो कमिट किया गया डेटा मिट सकता है. हालांकि, लॉगिंग की वजह से आपका डेटाबेस खराब नहीं होता.
अगर सिर्फ़ आपका ऐप्लिकेशन क्रैश होता है, तो भी आपका डेटा डिस्क में सेव हो जाता है. ज़्यादातर ऐप्लिकेशन के लिए, इस सेटिंग से परफ़ॉर्मेंस में सुधार होता है. इसके लिए, कोई शुल्क नहीं देना पड़ता.
कनेक्शन और मेमोरी मैनेज करना
हर ओपन डेटाबेस कनेक्शन में SQLite मेमोरी होती है: एक पेज कैश, एक लुकअसाइड एलोकेटर, और कंपाइल किए गए स्टेटमेंट का कैश. डेटाबेस फ़ाइल के पेज भी कर्नेल पेज कैश में रहते हैं. अपनी क्वेरी की स्पीड को कम किए बिना, इस मेमोरी को कंट्रोल में रखने के लिए, इस सेक्शन में दिए गए तरीकों का पालन करें.
हर डेटाबेस को एक बार खोलें
अपनी पूरी प्रोसेस के लिए, हर डेटाबेस फ़ाइल के लिए एक SQLiteOpenHelper या RoomDatabase इंस्टेंस का इस्तेमाल करें. उदाहरण के लिए, इसे सिंगलटन बनाकर. हर इंस्टेंस का अपना कनेक्शन पूल, पेज कैश, और स्टेटमेंट कैश होता है. एक ही फ़ाइल को खोलने वाले दो इंस्टेंस, इन्हें शेयर नहीं कर सकते. एक मानदंड में, एक ही फ़ाइल को खोलने वाले दो हेल्पर को एक में मिलाने से, SQLite पेज कैश मेमोरी करीब आधी हो गई. साथ ही, p95 क्वेरी के इंतज़ार का समय आधे से ज़्यादा कम हो गया.
डुप्लीकेट इंस्टेंस ढूंढने के लिए, एक से ज़्यादा Connection pool for हेडिंग में एक ही डेटाबेस पाथ के लिए dumpsys dbinfo देखें.
स्कीमा में किए गए बदलावों को ओपन कॉलबैक से बाहर रखना
डेटाबेस खुलने पर, हर बार SQLiteOpenHelper का onOpen कॉलबैक चलता है. इसी तरह, RoomDatabase.Callback का onOpen कॉलबैक भी चलता है. CREATE TABLE IF NOT EXISTS और CREATE INDEX IF NOT EXISTS जैसे स्टेटमेंट को onCreate और onUpgrade या रूम माइग्रेशन में डालें. onOpen में setVersion को कॉल न करें, क्योंकि SQLiteOpenHelper पहले ही onCreate और onUpgrade के बाद वर्शन सेट कर देता है. स्कीमा पहले से मौजूद होने पर भी, ये स्टेटमेंट हर बार ऐप्लिकेशन खोलने पर चलते हैं. साथ ही, setVersion डेटाबेस फ़ाइल में डेटा सेव करता है.
तैयार किए गए स्टेटमेंट की कैश मेमोरी का साइज़ तय करना
Android, हर कनेक्शन के लिए कंपाइल किए गए स्टेटमेंट को कैश मेमोरी में सेव करता है. कैश मेमोरी में डिफ़ॉल्ट रूप से 25 स्टेटमेंट सेव होते हैं. हालांकि, setMaxSqlCacheSize का इस्तेमाल करके, इसे MAX_SQL_CACHE_SIZE (100) तक बढ़ाया जा सकता है. अगर कोई डेटाबेस अपने हॉट पाथ में 25 से ज़्यादा अलग-अलग स्टेटमेंट चलाता है, तो कैश मेमोरी थ्रैश हो जाती है और SQLite एक ही स्टेटमेंट को बार-बार कंपाइल करता है.
सबसे पहले, अपनी क्वेरी को पैरामीटर के हिसाब से बनाएं. स्ट्रिंग जोड़ने की प्रोसेस से बनाए गए एसक्यूएल के लिए, कैश मेमोरी का कोई भी साइज़ काम नहीं करता. इसकी वजह यह है कि हर स्ट्रिंग एक नया स्टेटमेंट होती है. इसके बाद, dumpsys meminfo में POOL STATS के तहत, स्टेटमेंट कैश हिट और मिस देखें. उदाहरण के लिए, उस सेक्शन के सैंपल आउटपुट में phenotype.db डेटाबेस में 1,47,921 हिट और 89, 616 मिस हैं. यह 38% मिस रेट है. इस तरह के डेटाबेस पर कैश मेमोरी का साइज़ बढ़ाने से,
लगभग सभी मिस को हटाया जा सकता है. कैश मेमोरी में सेव किया गया हर स्टेटमेंट, कुछ किलोबाइट मेमोरी का इस्तेमाल करता है.
कैश मेमोरी का साइज़ बढ़ाने के लिए, अपने SQLiteOpenHelper सबक्लास में onConfigure को बदलें:
override fun onConfigure(db: SQLiteDatabase) {
db.setMaxSqlCacheSize(SQLiteDatabase.MAX_SQL_CACHE_SIZE)
}
अगर Room का इस्तेमाल किया जाता है, तो RoomDatabase.Callback के onOpen कॉलबैक में SupportSQLiteDatabase पर उसी तरीके को कॉल करें.
इस्तेमाल में न होने वाले डेटाबेस बंद करें, लेकिन इस्तेमाल में होने वाले डेटाबेस खुले रखें
एक खुला डेटाबेस अपने कनेक्शन, कैश मेमोरी, और फ़ाइल डिस्क्रिप्टर को तब भी बनाए रखता है, जब वह इस्तेमाल में न हो. एक मानदंड में, WAL मोड में मौजूद एक खाली Room डेटाबेस, जो खुला रहा, उसमें SQLite पेज कैश मेमोरी का करीब 678 केबी डेटा और फ़ाइल की जानकारी देने वाले पांच फ़ाइल डिस्क्रिप्टर मौजूद थे. अगर डेटाबेस का इस्तेमाल कभी-कभी किया जाता है, जैसे कि रोज़ाना सिंक करने के दौरान, तो काम पूरा होने के बाद उसे बंद कर दें.
इस्तेमाल में न होने वाले डेटाबेस को सुरक्षित तरीके से बंद करने के लिए, इन दिशा-निर्देशों का पालन करें:
- Room,
setAutoCloseTimeoutकी मदद से, इस्तेमाल नहीं किए जा रहे डेटाबेस को बंद कर सकता है. यह एक एक्सपेरिमेंटल एपीआई है. अगली बार ऐक्सेस करने पर, Room डेटाबेस को फिर से खोलता है और आपकेonOpenकॉलबैक को फिर से चलाता है. इसलिए, कनेक्शन का कोई भी कॉन्फ़िगरेशन वहां डालें. SQLiteOpenHelperऔरSQLiteDatabase.OpenParams.BuilderकेsetIdleConnectionTimeoutतरीकों का इस्तेमाल न करें. इन्हें एपीआई लेवल 29 में बंद कर दिया गया है, क्योंकि ये कनेक्शन बंद कर देते हैं. साथ ही,PRAGMAस्टेटमेंट की मदद से लागू की गई सेटिंग को वापस नहीं लाते.- ऐसे डेटाबेस को बंद न करें जिसे हर कुछ सेकंड में क्वेरी किया जाता है. अगर टाइम आउट की अवधि, आपकी क्वेरी के बीच के गैप से कम है, तो डेटाबेस बार-बार बंद और चालू होता है. इससे हर बार उसका पेज कैश और स्टेटमेंट कैश खारिज हो जाता है. साथ ही, अगली क्वेरी की प्रोसेस धीमी हो जाती है.
SQLite मेमोरी के लिए खाता
SQLite मेमोरी का पता लगाना मुश्किल हो सकता है. SQLite पेज कैश और स्टेटमेंट कैश, नेटिव हीप ऐलोकेशन हैं. हालांकि, Android डिफ़ॉल्ट रूप से SQLite के मेमोरी-मैप किए गए I/O को चालू नहीं करता है. इसलिए, SQLite सामान्य फ़ाइल रीड के साथ डेटाबेस फ़ाइलें पढ़ता है. साथ ही, कर्नेल की कैश मेमोरी में मौजूद फ़ाइल का कॉन्टेंट, अनमैप किए गए फ़ाइल पेज होता है. इन्हें dumpsys meminfo में आपके ऐप्लिकेशन के पीएसएस में नहीं गिना जाता. cgroup v2 मेमोरी अकाउंटिंग का इस्तेमाल करने वाले डिवाइसों पर, कर्नेल अब भी इन पेजों को आपके ऐप्लिकेशन के मेमोरी cgroup में चार्ज करता है. इस छिपे हुए फ़ुटप्रिंट को कम करने के लिए, इंडेक्स का इस्तेमाल करके कम पेज पढ़ें. साथ ही, सिर्फ़ उन पंक्तियों और कॉलम को पढ़ें जिनकी आपको ज़रूरत है. जिन डेटाबेस की ज़रूरत नहीं है उन्हें बंद करने से, उनकी SQLite कैश मेमोरी भी खाली हो जाती है.
बेहतर टेबल स्कीमा तय करना
परफ़ॉर्मेंस को ऑप्टिमाइज़ करने और डेटा की खपत को कम करने के लिए, टेबल का बेहतर स्कीमा तय करें. SQLite, क्वेरी प्लान और डेटा को बेहतर तरीके से तैयार करता है. इससे डेटा को तेज़ी से वापस पाया जा सकता है. इस सेक्शन में, टेबल स्कीमा बनाने के सबसे सही तरीके बताए गए हैं.
इस चीज़ को ध्यान में रखते हुए सुझाव: INTEGER PRIMARY KEY
इस उदाहरण के लिए, इस तरह से टेबल तय करें और उसमें जानकारी भरें:
CREATE TABLE Customers(
id INTEGER,
name TEXT,
city TEXT
);
INSERT INTO Customers Values(456, 'John Lennon', 'Liverpool, England');
INSERT INTO Customers Values(123, 'Michael Jackson', 'Gary, IN');
INSERT INTO Customers Values(789, 'Dolly Parton', 'Sevier County, TN');
टेबल का आउटपुट इस तरह का होगा:
| rowid | आईडी | नाम | शहर |
|---|---|---|---|
| 1 | 456 | जॉन लेनन | लिवरपूल, इंग्लैंड |
| 2 | 123 | माइकल जैक्सन | गैरी, इंडियाना |
| 3 | 789 | डॉली पार्टन | सेवियर काउंटी, टेनेसी |
कॉलम rowid एक इंडेक्स है, जो इंसर्शन ऑर्डर को सुरक्षित रखता है. rowid के हिसाब से फ़िल्टर करने वाली क्वेरी, तेज़ी से काम करने वाले बी-ट्री सर्च के तौर पर लागू की जाती हैं. हालांकि, id के हिसाब से फ़िल्टर करने वाली क्वेरी, टेबल को धीरे-धीरे स्कैन करती हैं.
अगर आपको id के हिसाब से लुकअप करना है, तो स्टोरेज में कम डेटा रखने और डेटाबेस को तेज़ बनाने के लिए, rowid कॉलम को सेव करने से बचा जा सकता है:
CREATE TABLE Customers(
id INTEGER PRIMARY KEY,
name TEXT,
city TEXT
);
अब आपकी टेबल कुछ इस तरह दिखेगी:
| आईडी | नाम | शहर |
|---|---|---|
| 123 | माइकल जैक्सन | गैरी, इंडियाना |
| 456 | जॉन लेनन | लिवरपूल, इंग्लैंड |
| 789 | डॉली पार्टन | सेवियर काउंटी, टेनेसी |
rowid कॉलम को स्टोर करने की ज़रूरत नहीं होती. इसलिए, id क्वेरी तेज़ी से काम करती हैं. ध्यान दें कि टेबल को अब इंसर्शन ऑर्डर के बजाय, id के हिसाब से क्रम में लगाया गया है.
इंडेक्स की मदद से क्वेरी तेज़ी से करना
SQLite, क्वेरी को तेज़ी से प्रोसेस करने के लिए इंडेक्स का इस्तेमाल करता है. किसी कॉलम को फ़िल्टर (WHERE), क्रम से (ORDER BY) या एग्रीगेट (GROUP BY) करते समय, अगर टेबल में उस कॉलम के लिए कोई इंडेक्स मौजूद है, तो क्वेरी तेज़ी से पूरी होती है.
पिछले उदाहरण में, city के हिसाब से फ़िल्टर करने के लिए, पूरी टेबल को स्कैन करना होगा:
SELECT id, name
WHERE city = 'London, England';
अगर किसी ऐप्लिकेशन में शहर से जुड़ी कई क्वेरी हैं, तो इंडेक्स की मदद से उन क्वेरी को तेज़ी से प्रोसेस किया जा सकता है:
CREATE INDEX city_index ON Customers(city);
इंडेक्स को एक अतिरिक्त टेबल के तौर पर लागू किया जाता है. इसे इंडेक्स कॉलम के हिसाब से क्रम में लगाया जाता है और rowid पर मैप किया जाता है:
| शहर | rowid |
|---|---|
| गैरी, इंडियाना | 2 |
| लिवरपूल, इंग्लैंड | 1 |
| सेवियर काउंटी, टेनेसी | 3 |
ध्यान दें कि city कॉलम के स्टोरेज की लागत अब दोगुनी हो गई है, क्योंकि यह अब ओरिजनल टेबल और इंडेक्स, दोनों में मौजूद है. इंडेक्स का इस्तेमाल करने पर, आपको ज़्यादा स्टोरेज के लिए शुल्क देना होगा. हालांकि, इससे क्वेरी के नतीजे तेज़ी से मिलते हैं.
हालांकि, ऐसा इंडेक्स न बनाएं जिसका इस्तेमाल नहीं किया जा रहा है. इससे आपको स्टोरेज की लागत नहीं चुकानी पड़ेगी और क्वेरी की परफ़ॉर्मेंस में भी कोई फ़ायदा नहीं मिलेगा.
कई कॉलम वाले इंडेक्स बनाना
अगर आपकी क्वेरी में कई कॉलम शामिल हैं, तो क्वेरी को पूरी तरह से तेज़ करने के लिए, मल्टी-कॉलम इंडेक्स बनाए जा सकते हैं. बाहरी कॉलम पर इंडेक्स का इस्तेमाल भी किया जा सकता है. साथ ही, अंदरूनी खोज को लीनियर स्कैन के तौर पर किया जा सकता है.
उदाहरण के लिए, इस क्वेरी को देखें:
SELECT id, name
WHERE city = 'London, England'
ORDER BY city, name
क्वेरी में बताए गए क्रम में मल्टी-कॉलम इंडेक्स का इस्तेमाल करके, क्वेरी को तेज़ी से चलाया जा सकता है:
CREATE INDEX city_name_index ON Customers(city, name);
हालांकि, अगर आपके पास सिर्फ़ city पर इंडेक्स है, तो बाहरी क्रम अब भी तेज़ है. वहीं, अंदरूनी क्रम के लिए लीनियर स्कैन की ज़रूरत होती है.
यह सुविधा, प्रीफ़िक्स वाली क्वेरी के साथ भी काम करती है. उदाहरण के लिए, इंडेक्स ON Customers (city, name), city के हिसाब से फ़िल्टर करने, क्रम से लगाने, और ग्रुप बनाने की प्रोसेस को भी तेज़ करता है. ऐसा इसलिए होता है, क्योंकि मल्टी-कॉलम इंडेक्स के लिए इंडेक्स टेबल को दिए गए इंडेक्स के हिसाब से क्रम में लगाया जाता है.
इस चीज़ को ध्यान में रखते हुए सुझाव: WITHOUT ROWID
डिफ़ॉल्ट रूप से, SQLite आपकी टेबल के लिए एक rowid कॉलम बनाता है. इसमें rowid एक इंप्लिसिट INTEGER PRIMARY KEY AUTOINCREMENT होता है. अगर आपके पास पहले से ही INTEGER PRIMARY KEY नाम का कॉलम है, तो यह कॉलम rowid का दूसरा नाम बन जाता है.
जिन टेबल में INTEGER के अलावा कोई अन्य प्राइमरी कुंजी या कॉलम का कंपोज़िट है उनके लिए, WITHOUT ROWID का इस्तेमाल करें.
छोटे डेटा को BLOB के तौर पर और बड़े डेटा को फ़ाइल के तौर पर सेव करना
अगर आपको किसी पंक्ति के साथ ज़्यादा डेटा जोड़ना है, जैसे कि किसी इमेज का थंबनेल या किसी संपर्क की फ़ोटो, तो डेटा को BLOB कॉलम या किसी फ़ाइल में सेव किया जा सकता है. इसके बाद, पाथ को कॉलम में सेव किया जा सकता है.
आम तौर पर, फ़ाइलों का साइज़ 4 केबी के हिसाब से बढ़ाया जाता है. बहुत छोटी फ़ाइलों के लिए, जहां राउंडिंग एरर काफ़ी ज़्यादा होता है, उन्हें डेटाबेस में BLOB के तौर पर सेव करना ज़्यादा बेहतर होता है. SQLite, फ़ाइल सिस्टम कॉल को कम करता है. साथ ही, कुछ मामलों में यह मौजूदा फ़ाइल सिस्टम से ज़्यादा तेज़ होता है.
क्वेरी की परफ़ॉर्मेंस को बेहतर बनाना
SQLite में क्वेरी की परफ़ॉर्मेंस को बेहतर बनाने के लिए, यहां दिए गए सबसे सही तरीके अपनाएं. इनसे, जवाब देने में लगने वाले समय को कम किया जा सकता है और प्रोसेसिंग की क्षमता को बढ़ाया जा सकता है.
सिर्फ़ अपनी ज़रूरत की लाइनें पढ़ना
फ़िल्टर की मदद से, नतीजों को तारीख की सीमा, जगह या नाम जैसी कुछ शर्तों के हिसाब से सीमित किया जा सकता है. सीमाओं की मदद से, आपको दिखने वाले नतीजों की संख्या को कंट्रोल किया जा सकता है:
db.rawQuery("""
SELECT name
FROM Customers
LIMIT 10;
""".trimIndent(),
null
).use { cursor ->
while (cursor.moveToNext()) {
// Process cursor data
}
}
सिर्फ़ अपनी ज़रूरत के कॉलम पढ़ें
ऐसे कॉलम चुनने से बचें जिनकी ज़रूरत नहीं है. इससे आपकी क्वेरी की रफ़्तार धीमी हो सकती है और संसाधनों की बर्बादी हो सकती है. इसके बजाय, सिर्फ़ उन कॉलम को चुनें जिनका इस्तेमाल किया जाता है.
यहां दिए गए उदाहरण में, id, name, और phone को चुना गया है:
// This is not the most efficient way of doing this.
// See the following example for a better approach.
db.rawQuery(
"""
SELECT id, name, phone
FROM customers;
""".trimIndent(),
null
).use { cursor ->
while (cursor.moveToNext()) {
val name = cursor.getString(1)
// Further processing
}
}
हालांकि, आपको सिर्फ़ name कॉलम की ज़रूरत होगी:
db.rawQuery("""
SELECT name
FROM Customers;
""".trimIndent(),
null
).use { cursor ->
while (cursor.moveToNext()) {
val name = cursor.getString(0)
// Further processing
}
}
क्वेरी को पैरामीटर के हिसाब से बनाना
आपकी क्वेरी स्ट्रिंग में ऐसा पैरामीटर शामिल हो सकता है जो सिर्फ़ रनटाइम के दौरान पता चलता है. जैसे:
fun getNameById(id: Long): String?
db.rawQuery(
"SELECT name FROM customers WHERE id=$id", null
).use { cursor ->
return if (cursor.moveToFirst()) {
cursor.getString(0)
} else {
null
}
}
}
ऊपर दिए गए कोड में, हर क्वेरी एक अलग स्ट्रिंग बनाती है. इसलिए, इसे स्टेटमेंट कैश मेमोरी से फ़ायदा नहीं मिलता. हर कॉल को SQLite की ज़रूरत होती है, ताकि उसे कंपाइल किया जा सके. इसके बाद ही, उसे एक्ज़ीक्यूट किया जा सकता है. इसके बजाय, id आर्ग्युमेंट को पैरामीटर से बदला जा सकता है और वैल्यू को selectionArgs से बाइंड किया जा सकता है:
fun getNameById(id: Long): String? {
db.rawQuery(
"""
SELECT name
FROM customers
WHERE id=?
""".trimIndent(), arrayOf(id.toString())
).use { cursor ->
return if (cursor.moveToFirst()) {
cursor.getString(0)
} else {
null
}
}
}
अब क्वेरी को एक बार कंपाइल करके कैश मेमोरी में सेव किया जा सकता है. कंपाइल की गई क्वेरी को getNameById(long) के अलग-अलग इन्वोकेशन के बीच फिर से इस्तेमाल किया जाता है.
कोड के बजाय एसक्यूएल में बार-बार दोहराएं
अलग-अलग नतीजे पाने के लिए, एसक्यूएल क्वेरी पर प्रोग्राम के हिसाब से लूप को दोहराने के बजाय, ऐसी क्वेरी का इस्तेमाल करें जिससे टारगेट किए गए सभी नतीजे मिलें. प्रोग्रामैटिक लूप, एक एसक्यूएल क्वेरी की तुलना में करीब 1,000 गुना धीमा होता है.
यूनीक वैल्यू के लिए DISTINCT का इस्तेमाल करना
DISTINCT कीवर्ड का इस्तेमाल करके, क्वेरी की परफ़ॉर्मेंस को बेहतर बनाया जा सकता है. इसके लिए, प्रोसेस किए जाने वाले डेटा की मात्रा को कम करना होगा. उदाहरण के लिए, अगर आपको किसी कॉलम से सिर्फ़ यूनीक वैल्यू चाहिए, तो DISTINCT का इस्तेमाल करें:
db.rawQuery("""
SELECT DISTINCT name
FROM Customers;
""".trimIndent(),
null
).use { cursor ->
while (cursor.moveToNext()) {
// Only iterate over distinct names in Kotlin
// Process distinct name
}
}
जब भी संभव हो, एग्रीगेट फ़ंक्शन का इस्तेमाल करें
बिना लाइन डेटा के एग्रीगेट किए गए नतीजे पाने के लिए, एग्रीगेट फ़ंक्शन का इस्तेमाल करें. उदाहरण के लिए, यहां दिया गया कोड यह जांच करता है कि मैच होने वाली कम से कम एक लाइन मौजूद है या नहीं:
// This is not the most efficient way of doing this.
// See the following example for a better approach.
db.rawQuery("""
SELECT id, name
FROM Customers
WHERE city = 'Paris';
""".trimIndent(),
null
).use { cursor ->
if (cursor.moveToFirst()) {
// At least one customer from Paris
// Handle found
} else {
// No customers from Paris
// Handle not found
}
सिर्फ़ पहली लाइन फ़ेच करने के लिए, EXISTS() का इस्तेमाल किया जा सकता है. इससे, मैच होने वाली लाइन मौजूद न होने पर 0 और एक या उससे ज़्यादा लाइनें मैच होने पर 1 दिखेगा:
db.rawQuery("""
SELECT EXISTS (
SELECT null
FROM Customers
WHERE city = 'Paris';
);
""".trimIndent(),
null
).use { cursor ->
if (cursor.moveToFirst() && cursor.getInt(0) == 1) {
// At least one customer from Paris
// Handle found
} else {
// No customers from Paris
// Handle not found
}
}
अपने ऐप्लिकेशन में SQLite एग्रीगेट फ़ंक्शन का इस्तेमाल करना
COUNT: इससे किसी कॉलम में मौजूद पंक्तियों की संख्या का पता चलता है.SUM: किसी कॉलम में मौजूद सभी अंकों वाली वैल्यू को जोड़ता है.MINयाMAX: इससे सबसे कम या सबसे ज़्यादा वैल्यू तय होती है. यह न्यूमेरिक कॉलम,DATEटाइप, और टेक्स्ट टाइप के लिए काम करता है.AVG: इससे औसत संख्यात्मक वैल्यू का पता चलता है.GROUP_CONCAT: यह फ़ंक्शन, स्ट्रिंग को जोड़ने के लिए इस्तेमाल किया जाता है. इसके लिए, सेपरेटर का इस्तेमाल किया जा सकता है.
Cursor.getCount() के बजाय COUNT() का इस्तेमाल करें
इस उदाहरण में, Cursor.getCount() फ़ंक्शन, डेटाबेस की सभी लाइनें पढ़ता है और उनकी वैल्यू दिखाता है:
// This is not the most efficient way of doing this.
// See the following example for a better approach.
db.rawQuery("""
SELECT id
FROM Customers;
""".trimIndent(),
null
).use { cursor ->
val count = cursor.getCount()
// Use count
}
हालांकि, COUNT() का इस्तेमाल करने पर, डेटाबेस सिर्फ़ संख्या दिखाता है:
db.rawQuery("""
SELECT COUNT(*)
FROM Customers;
""".trimIndent(),
null
).use { cursor ->
cursor.moveToFirst()
val count = cursor.getInt(0)
// Use count
}
कोड के बजाय क्वेरी को नेस्ट करना
एसक्यूएल को कंपोज़ किया जा सकता है. साथ ही, यह सबक्वेरी, जॉइन, और फ़ॉरेन की की शर्तों के साथ काम करता है. ऐप्लिकेशन कोड का इस्तेमाल किए बिना, एक क्वेरी के नतीजे को दूसरी क्वेरी में इस्तेमाल किया जा सकता है. इससे SQLite से डेटा कॉपी करने की ज़रूरत कम हो जाती है. साथ ही, डेटाबेस इंजन आपकी क्वेरी को ऑप्टिमाइज़ कर पाता है.
यहां दिए गए उदाहरण में, क्वेरी चलाकर यह पता लगाया जा सकता है कि किस शहर में सबसे ज़्यादा ग्राहक हैं. इसके बाद, उस शहर के सभी ग्राहकों को ढूंढने के लिए, नतीजे का इस्तेमाल दूसरी क्वेरी में किया जा सकता है:
// This is not the most efficient way of doing this.
// See the following example for a better approach.
db.rawQuery("""
SELECT city
FROM Customers
GROUP BY city
ORDER BY COUNT(*) DESC
LIMIT 1;
""".trimIndent(),
null
).use { cursor ->
if (cursor.moveToFirst()) {
val topCity = cursor.getString(0)
db.rawQuery("""
SELECT name, city
FROM Customers
WHERE city = ?;
""".trimIndent(),
arrayOf(topCity)).use { innerCursor ->
while (innerCursor.moveToNext()) {
// Process inner cursor data
}
}
}
}
पिछले उदाहरण के मुकाबले आधे समय में नतीजा पाने के लिए, नेस्ट किए गए स्टेटमेंट वाली एक SQL क्वेरी का इस्तेमाल करें:
db.rawQuery("""
SELECT name, city
FROM Customers
WHERE city IN (
SELECT city
FROM Customers
GROUP BY city
ORDER BY COUNT (*) DESC
LIMIT 1;
);
""".trimIndent(),
null
).use { cursor ->
if (cursor.moveToNext()) {
// Process cursor data
}
}
एसक्यूएल में यूनीक वैल्यू की जांच करना
अगर किसी पंक्ति को तब तक शामिल नहीं किया जाना चाहिए, जब तक कि टेबल में किसी खास कॉलम की वैल्यू यूनीक न हो, तो उस यूनीक वैल्यू को कॉलम की शर्त के तौर पर लागू करना ज़्यादा असरदार हो सकता है.
यहां दिए गए उदाहरण में, इंसर्ट की जाने वाली लाइन की पुष्टि करने के लिए एक क्वेरी चलाई गई है. साथ ही, असल में इंसर्ट करने के लिए दूसरी क्वेरी चलाई गई है:
// This is not the most efficient way of doing this.
// See the following example for a better approach.
db.rawQuery(
"""
SELECT EXISTS (
SELECT null
FROM customers
WHERE username = ?
);
""".trimIndent(),
arrayOf(customer.username)
).use { cursor ->
if (cursor.moveToFirst() && cursor.getInt(0) == 1) {
throw AddCustomerException(customer)
}
}
db.execSQL(
"INSERT INTO customers VALUES (?, ?, ?)",
arrayOf(
customer.id.toString(),
customer.name,
customer.username
)
)
Kotlin में यूनीक कंस्ट्रेंट की जांच करने के बजाय, टेबल तय करते समय SQL में इसकी जांच की जा सकती है:
CREATE TABLE Customers(
id INTEGER PRIMARY KEY,
name TEXT,
username TEXT UNIQUE
);
SQLite भी यही काम करता है:
CREATE TABLE Customers(...);
CREATE UNIQUE INDEX CustomersUsername ON Customers(username);
अब आपके पास एक लाइन डालने और SQLite को कंस्ट्रेंट की जांच करने की अनुमति देने का विकल्प है:
try {
db.execSQL(
"INSERT INTO Customers VALUES (?, ?, ?)",
arrayOf(customer.id.toString(), customer.name, customer.username)
)
} catch(e: SQLiteConstraintException) {
throw AddCustomerException(customer, e)
}
SQLite में, एक से ज़्यादा कॉलम वाले यूनीक इंडेक्स इस्तेमाल किए जा सकते हैं:
CREATE TABLE table(...);
CREATE UNIQUE INDEX unique_table ON table(column1, column2, ...);
SQLite, Kotlin कोड की तुलना में कंस्ट्रेंट की पुष्टि तेज़ी से और कम ओवरहेड के साथ करता है. ऐप्लिकेशन कोड के बजाय SQLite का इस्तेमाल करना सबसे सही तरीका है.
एक ही लेन-देन में कई इंसर्शन को बैच करना
एक लेन-देन में कई कार्रवाइयां की जाती हैं. इससे न सिर्फ़ काम करने की क्षमता बेहतर होती है, बल्कि सही तरीके से काम करने में भी मदद मिलती है. डेटा की एकरूपता को बेहतर बनाने और परफ़ॉर्मेंस को तेज़ करने के लिए, इंसर्शन को बैच में किया जा सकता है:
db.beginTransaction()
try {
customers.forEach { customer ->
db.execSQL(
"INSERT INTO Customers VALUES (?, ?, ?)",
arrayOf(customer.id.toString(), customer.name, "customerValue")
)
}
db.setTransactionSuccessful()
} finally {
db.endTransaction()
}
एक्सप्लिसिट ट्रांज़ैक्शन के अलावा, हर राइट अपने इंप्लिसिट ट्रांज़ैक्शन में चलता है. साथ ही, कमिट की पूरी कीमत चुकाता है. इसमें FULL सिंक्रोनस मोड में fsync शामिल है. बैचिंग की सुविधा का इस्तेमाल करने पर, यह शुल्क सिर्फ़ एक बार देना पड़ता है. इससे, बैच में डेटा लिखने की प्रोसेस काफ़ी तेज़ हो जाती है.
समस्या हल करने वाले टूल इस्तेमाल करना
SQLite, परफ़ॉर्मेंस को मेज़र करने के लिए, समस्या हल करने वाले ये टूल उपलब्ध कराता है.
SQLite के इंटरैक्टिव प्रॉम्प्ट का इस्तेमाल करना
क्वेरी चलाने और सीखने के लिए, अपनी मशीन पर SQLite चलाएं.
Android प्लैटफ़ॉर्म के अलग-अलग वर्शन, SQLite के अलग-अलग वर्शन इस्तेमाल करते हैं. Android डिवाइस पर मौजूद इंजन का इस्तेमाल करने के लिए, adb shell का इस्तेमाल करें और अपने टारगेट डिवाइस पर sqlite3 चलाएं.
SQLite से क्वेरी का समय बताने के लिए कहा जा सकता है:
sqlite> .timer on
sqlite> SELECT ...
Run Time: real ... user ... sys ...
EXPLAIN QUERY PLAN
EXPLAIN QUERY PLAN का इस्तेमाल करके, SQLite से यह बताने के लिए कहा जा सकता है कि वह किसी क्वेरी का जवाब कैसे देगा:
sqlite> EXPLAIN QUERY PLAN
SELECT id, name
FROM Customers
WHERE city = 'Paris';
QUERY PLAN
`--SCAN Customers
पिछले उदाहरण में, पेरिस के सभी ग्राहकों को ढूंढने के लिए, इंडेक्स के बिना पूरी टेबल को स्कैन करना पड़ता है. इसे लीनियर कॉम्प्लेक्सिटी कहा जाता है. SQLite को सभी पंक्तियां पढ़नी होंगी और सिर्फ़ उन पंक्तियों को रखना होगा जो पेरिस के ग्राहकों से मेल खाती हैं. इसे ठीक करने के लिए, इंडेक्स जोड़ा जा सकता है:
sqlite> CREATE INDEX Idx1 ON Customers(city);
sqlite> EXPLAIN QUERY PLAN
SELECT id, name
FROM Customers
WHERE city = 'Paris';
QUERY PLAN
`--SEARCH test USING INDEX Idx1 (city=?
अगर इंटरैक्टिव शेल का इस्तेमाल किया जा रहा है, तो SQLite से क्वेरी प्लान के बारे में हमेशा जानकारी देने के लिए कहा जा सकता है:
sqlite> .eqp on
ज़्यादा जानकारी के लिए, क्वेरी प्लानिंग देखें.
SQLite Analyzer
SQLite, sqlite3_analyzer कमांड-लाइन इंटरफ़ेस (सीएलआई) उपलब्ध कराता है, ताकि ऐसी अतिरिक्त जानकारी को डंप किया जा सके जिसका इस्तेमाल परफ़ॉर्मेंस से जुड़ी समस्या को हल करने के लिए किया जा सकता है. इंस्टॉल करने के लिए,
SQLite डाउनलोड पेज पर जाएं.
adb pull का इस्तेमाल करके, टारगेट डिवाइस से डेटाबेस फ़ाइल को अपने वर्कस्टेशन पर डाउनलोड किया जा सकता है, ताकि उसका विश्लेषण किया जा सके:
adb pull /data/data/<app_package_name>/databases/<db_name>.db
SQLite Browser
SQLite के डाउनलोड पेज पर जाकर, जीयूआई टूल SQLite Browser भी इंस्टॉल किया जा सकता है.
Android लॉगिंग
Android, SQLite क्वेरी को टाइम करता है और उन्हें आपके लिए लॉग करता है:
# Enable query time logging
$ adb shell setprop log.tag.SQLiteTime VERBOSE
# Disable query time logging
$ adb shell setprop log.tag.SQLiteTime ERROR
Android 13 और इसके बाद के वर्शन में, सिर्फ़ धीमी कार्रवाइयों को लॉग किया जा सकता है. थ्रेशोल्ड को मिलीसेकंड में सेट करें. इसके बाद, अपने ऐप्लिकेशन को रीस्टार्ट करें, ताकि वह लॉग टैग को पढ़ सके:
$ adb shell setprop log.tag.SQLiteSlowQueries VERBOSE
$ adb shell setprop db.log.slow_query_threshold 100
$ adb shell am force-stop <package-name>
धीमे ऑपरेशन, logcat में SQLiteConnection टैग के तहत दिखते हैं. इनमें उनकी अवधि, एसक्यूएल, और डेटाबेस पाथ की जानकारी शामिल होती है. किसी ऐप्लिकेशन के लिए थ्रेशोल्ड सेट करने के लिए, db.log.slow_query_threshold.<uid> का इस्तेमाल करें. यहां <uid>, ऐप्लिकेशन का यूआईडी है.
Perfetto ट्रेसिंग
Perfetto को कॉन्फ़िगर करते समय, ट्रेस में SQLite ऑपरेशन शामिल करने के लिए, database atrace कैटगरी जोड़ें. SQL चलाते समय हर थ्रेड क्या कर रहा है, यह देखने के लिए शेड्यूलर इवेंट भी जोड़ें:
data_sources {
config {
name: "linux.ftrace"
ftrace_config {
ftrace_events: "sched/sched_switch"
ftrace_events: "sched/sched_waking"
ftrace_events: "sched/sched_blocked_reason"
atrace_categories: "database"
}
}
}
Android, हर डेटाबेस ऑपरेशन को एक स्लाइस के तौर पर ट्रेस करता है. इसका नाम, ऑपरेशन और उसके एसक्यूएल के नाम पर रखा जाता है. उदाहरण के लिए, executeForCursorWindow SELECT * FROM message WHERE body LIKE ?. SQL में ? प्लेसहोल्डर बने रहते हैं और इसे 256 वर्णों तक छोटा कर दिया जाता है. इसमें बाइंड आर्ग्युमेंट शामिल नहीं होते. इनमें open, close, prepare, execute, और executeFor के वैरिएंट शामिल हैं. prepare स्लाइस का यह मतलब नहीं है कि स्टेटमेंट कैश मेमोरी में मौजूद नहीं है.
ट्रैक का लेआउट, Android वर्शन के हिसाब से तय होता है:
- Android 17 और इसके बाद के वर्शन में, हर कनेक्शन के लिए ऐप्लिकेशन प्रोसेस में एक एसिंक्रोनस ट्रैक होता है. इसका नाम
SQLiteConnection-होता है और इसके बाद कनेक्शन नंबर होता है. लेन-देन,TRANSACTION-EXCLUSIVE,TRANSACTION-IMMEDIATEयाTRANSACTION-DEFERREDस्लाइस के तौर पर दिखते हैं. साथ ही, उनमें मौजूद स्टेटमेंट, उनके नीचे नेस्ट किए गए होते हैं. ऑपरेशन को चलाने वाले थ्रेड को, हर लेन-देन और लेन-देन के बाहर के हर स्टेटमेंट के लिए, अपने थ्रेड ट्रैक परSTART-औरEND-इंस्टैंट इवेंट भी मिलते हैं. इन इवेंट के नाम, कनेक्शन ट्रैक के नाम के साथ खत्म होते हैं. उदाहरण के लिए,-SQLiteConnection-0. - Android 16 और इससे पहले के वर्शन में, हर अलग एसक्यूएल स्टेटमेंट का ऐप्लिकेशन प्रोसेस में अपना एसिंक्रोनस ट्रैक होता है. साथ ही, लेन-देन को ट्रेस नहीं किया जाता.
हर डेटाबेस के लिए कनेक्शन नंबर 0 से शुरू होते हैं. इसलिए, किसी प्रोसेस में सभी डेटाबेस के प्राइमरी कनेक्शन, SQLiteConnection-0 ट्रैक को शेयर करते हैं. अगर आपका ऐप्लिकेशन एक ही समय पर एक से ज़्यादा डेटाबेस का इस्तेमाल करता है, तो थ्रेड ट्रैक पर START- और END- इवेंट की मदद से, समयसीमा को मेज़र करें.
पहली इमेज. Android 17 में, मुख्य थ्रेड पर पूरी टेबल को स्कैन किया जाता है.
इन स्लाइस का विश्लेषण, Perfetto यूज़र इंटरफ़ेस (यूआई) में PerfettoSQL की मदद से या trace_processor की मदद से किया जा सकता है. नीचे दी गई क्वेरी, हर प्रोसेस में सबसे ज़्यादा इस्तेमाल होने वाले SQLite ऑपरेशन ढूंढती है. यह क्वेरी, Android के सभी वर्शन पर काम करती है:
INCLUDE PERFETTO MODULE slices.with_context;
SELECT process_name, name AS operation, COUNT(*) AS count
FROM process_slice
WHERE name GLOB 'open *' OR name GLOB 'close *'
OR name GLOB 'prepare *' OR name GLOB 'execute*'
OR name GLOB 'TRANSACTION-*'
GROUP BY process_name, name
ORDER BY count DESC
LIMIT 10;
यहां दी गई क्वेरी में, SQLite के उन ऑपरेशन की सूची दी गई है जिन्हें Android 17 और इसके बाद के वर्शन में, मुख्य थ्रेड पर पूरा होने में एक मिलीसेकंड से ज़्यादा समय लगा:
INCLUDE PERFETTO MODULE slices.with_context;
WITH marks AS (
SELECT process_name, utid, is_main_thread, ts, name,
LEAD(ts) OVER (PARTITION BY utid ORDER BY ts) AS end_ts,
LEAD(name) OVER (PARTITION BY utid ORDER BY ts) AS end_name
FROM thread_slice
WHERE name GLOB 'START-*' OR name GLOB 'END-*'
)
SELECT process_name, ts, ROUND((end_ts - ts) / 1e6, 1) AS dur_ms,
substr(name, 7) AS operation
FROM marks
WHERE name GLOB 'START-*'
AND end_name = 'END-' || substr(name, 7)
AND is_main_thread
AND end_ts - ts > 1e6
ORDER BY dur_ms DESC
LIMIT 10;
दूसरी इमेज. मुख्य थ्रेड पर एक मिलीसेकंड से ज़्यादा समय तक चलने वाली SQLite कार्रवाइयां.
यहां दी गई क्वेरी, Android 17 और इसके बाद के वर्शन में, ऐसे थ्रेड ढूंढती है जो किसी खास लेन-देन के बाहर लिखते हैं. इसमें हर राइट, अपने कमिट के लिए पेमेंट करता है:
INCLUDE PERFETTO MODULE slices.with_context;
WITH ops AS (
SELECT process_name, thread_name, utid, name,
upper(ltrim(substr(name, instr(name, ' ') + 1))) AS sql
FROM thread_slice
WHERE name GLOB 'START-*'
)
SELECT process_name, thread_name,
SUM(name GLOB 'START-TRANSACTION-*') AS transactions,
SUM(name GLOB 'START-execute*' AND (sql GLOB 'INSERT *'
OR sql GLOB 'UPDATE *' OR sql GLOB 'DELETE *' OR sql GLOB 'REPLACE *'))
AS autocommit_writes
FROM ops
GROUP BY utid
HAVING autocommit_writes > 0
ORDER BY autocommit_writes DESC
LIMIT 10;
dumpsys meminfo
adb shell dumpsys meminfo <package-name>, ऐप्लिकेशन की मेमोरी के इस्तेमाल से जुड़े आंकड़े प्रिंट करता है. इसमें DATABASES सेक्शन में SQLite के बारे में जानकारी शामिल होती है. उदाहरण के लिए, इसे डेवलपर के डिवाइस पर adb shell dumpsys meminfo com.google.android.gms.persistent के आउटपुट से लिया गया है:
DATABASES
pgsz dbsz Lookaside(b) cache hits cache misses cache size Dbname
PER CONNECTION STATS
4 52 45 8 41 6 /data/user/10/com.google.android.gms/databases/gaia-discovery
4 8 0 0 0 (attached) temp
4 52 56 5 23 6 /data/user/10/com.google.android.gms/databases/gaia-discovery (1)
4 252 95 233 124 12 /data/user_de/10/com.google.android.gms/databases/phenotype.db
4 8 0 0 0 (attached) temp
4 252 17 0 17 1 /data/user_de/10/com.google.android.gms/databases/phenotype.db (1)
4 9280 105 103169 69805 25 /data/user/10/com.google.android.gms/databases/phenotype.db
4 20 0 0 0 (attached) temp
4 9280 108 13877 6394 25 /data/user/10/com.google.android.gms/databases/phenotype.db (2)
4 8 0 0 0 (attached) temp
4 9280 105 12548 5519 25 /data/user/10/com.google.android.gms/databases/phenotype.db (3)
4 8 0 0 0 (attached) temp
4 9280 107 18328 7886 25 /data/user/10/com.google.android.gms/databases/phenotype.db (1)
4 8 0 0 0 (attached) temp
4 36 51 156 29 5 /data/user/10/com.google.android.gms/databases/mobstore_gc_db_v0
4 36 97 47 27 10 /data/user/10/com.google.android.gms/databases/context_feature_default.db
4 36 56 3 16 4 /data/user/10/com.google.android.gms/databases/context_feature_default.db (2)
4 300 40 2111 24 5 /data/user/10/com.google.android.gms/databases/gservices.db
4 300 39 3 17 4 /data/user/10/com.google.android.gms/databases/gservices.db (1)
4 20 17 0 14 1 /data/user/10/com.google.android.gms/databases/gms.notifications.db
4 20 33 1 15 2 /data/user/10/com.google.android.gms/databases/gms.notifications.db (1)
4 120 40 143 163 4 /data/user/10/com.google.android.gms/databases/android_pay
4 120 123 86 32 19 /data/user/10/com.google.android.gms/databases/android_pay (1)
4 28 33 4 17 3 /data/user/10/com.google.android.gms/databases/googlesettings.db
POOL STATS
cache hits cache misses cache size Dbname
13 68 81 /data/user/10/com.google.android.gms/databases/gaia-discovery
233 145 378 /data/user_de/10/com.google.android.gms/databases/phenotype.db
147921 89616 237537 /data/user/10/com.google.android.gms/databases/phenotype.db
156 30 186 /data/user/10/com.google.android.gms/databases/mobstore_gc_db_v0
50 57 107 /data/user/10/com.google.android.gms/databases/context_feature_default.db
2114 43 2157 /data/user/10/com.google.android.gms/databases/gservices.db
1 31 32 /data/user/10/com.google.android.gms/databases/gms.notifications.db
229 197 426 /data/user/10/com.google.android.gms/databases/android_pay
4 18 22 /data/user/10/com.google.android.gms/databases/googlesettings.db
Android 14 और इसके बाद के वर्शन, उदाहरण में दिखाई गई PER CONNECTION STATS और POOL STATS टेबल को प्रिंट करते हैं. Android 13 और इससे पहले के वर्शन, सिर्फ़ हर कनेक्शन वाली टेबल को प्रिंट करते हैं. इसमें कैश मेमोरी के आंकड़े, cache कॉलम में hits/misses/size के तौर पर शामिल होते हैं.
PER CONNECTION STATS में जाकर, आपको ये विकल्प दिखेंगे:
pgsz: केबी में, एक डेटाबेस पेज का साइज़.dbsz: पूरी डेटाबेस फ़ाइल का साइज़, केबी में.Lookaside(b): कनेक्शन में इस्तेमाल किए जाने वाले लुकअसाइड मेमोरी स्लॉट की संख्या. हेडर में(b)होने के बावजूद, यह स्लॉट की संख्या है, बाइट की नहीं.cache hits,cache misses, औरcache size: कनेक्शन की तैयार की गई स्टेटमेंट कैश मेमोरी के आंकड़े. इन वैल्यू में, कंपाइल किए गए एसक्यूएल स्टेटमेंट गिने जाते हैं, न कि डेटाबेस पेज.Dbname: डेटाबेस फ़ाइल का पाथ. एक ही डेटाबेस के अन्य कनेक्शन के नाम के आगे ब्रैकेट में एक नंबर जुड़ा होता है, जैसे कि(1). WAL की मदद से, डेटाबेस में कई कनेक्शन हो सकते हैं, ताकि डेटा को एक साथ पढ़ा जा सके.
POOL STATS में, आपको हर कनेक्शन पूल के लिए, तैयार किए गए स्टेटमेंट के कैश मेमोरी के आंकड़े दिखेंगे:
cache hits: SQLite, तैयार किए गए स्टेटमेंट को कैश मेमोरी में सेव करता है और क्वेरी चलाते समय उन्हें फिर से इस्तेमाल करने की कोशिश करता है. इससे एसक्यूएल स्टेटमेंट को कंपाइल करने में लगने वाली मेहनत और मेमोरी की बचत होती है. यह स्टेटमेंट कैश मेमोरी में हिट की संख्या है.cache misses: स्टेटमेंट कैश मेमोरी में मौजूद नहीं है.cache size: Android 16 QPR2 और इसके बाद के वर्शन में, कैश मेमोरी में सेव किए गए तैयार स्टेटमेंट की संख्या. इसमें पूल के इस्तेमाल में न होने वाले कनेक्शन भी शामिल हैं. Android 14 से लेकर Android 16 QPR1 तक, यह वैल्यू हिट और मिस का कुल योग होती है. यह कैश मेमोरी के साइज़ को नहीं दिखाती.
इन आंकड़ों का इस्तेमाल करके, यह तय करें कि आपको क्या ठीक करना है:
- मिस रेट ज़्यादा होने का मतलब है कि SQLite, एक ही स्टेटमेंट को बार-बार कंपाइल करता है.
सबसे पहले, स्ट्रिंग जोड़ने की प्रोसेस से बने एसक्यूएल को ढूंढें और उसे पैरामीटर के हिसाब से बनाएं. अगर आपकी क्वेरी पहले से ही पैरामीटर के हिसाब से तय हैं और हर कनेक्शन के लिए
cache sizeकी संख्या 25 है, तो कैश मेमोरी का साइज़ बढ़ाएं. - अगर कोई डेटाबेस पाथ,
(1)-स्टाइल वाले सफ़िक्स के बिना एक से ज़्यादा बार दिखता है, तो इसका मतलब है कि आपके ऐप्लिकेशन ने एक ही फ़ाइल को दो बार खोला है. हर डेटाबेस को एक बार खोलें. - ऐसा डेटाबेस जिसका इस्तेमाल आपका ऐप्लिकेशन बहुत कम करता है, वह भी खुला रहने पर मेमोरी का इस्तेमाल करता है. काम पूरा होने के बाद, इसे बंद करें.
dumpsys dbinfo
adb shell dumpsys dbinfo <package-name> आपके ऐप्लिकेशन में हर कनेक्शन पूल की स्थिति को प्रिंट करता है. हर कनेक्शन के तैयार किए गए स्टेटमेंट कैश में स्टेटमेंट की सूची बनाने के लिए, -v जोड़ें. यहां दिया गया उदाहरण, Android 17 का आउटपुट है. इसमें पाथ और लंबी लाइनों को छोटा किया गया है:
** Database info for pid 2705 [com.example.app] **
Connection pool for /data/user/0/com.example.app/databases/settings.db:
Open: true
Max connections: 4
Total execution time (ms): 4963
Total statements executed: 231239
Average time per statement (ms): 0
Configuration: openFlags=805306368, ..., journalMode=WAL, syncMode=NORMAL
IsReadOnlyDatabase: false
Available primary connection:
<none>
Available non-primary connections:
<none>
Acquired connections:
<none>
Connection waiters:
<none>
Connection pool for /data/user/0/com.example.app/databases/events.db:
Open: true
Max connections: 1
Total execution time (ms): 842
Total statements executed: 840
Average time per statement (ms): 1
Configuration: openFlags=268435456, ..., journalMode=TRUNCATE, syncMode=FULL
IsReadOnlyDatabase: false
Available primary connection:
Connection #0:
isPrimaryConnection: true
onlyAllowReadOnlyOperations: false
totalLongOperations: 0
Most recently executed operations:
0: [10-26 04:22:32.133] executeForChangedRowCount took 0ms -
succeeded, sql="DELETE FROM params WHERE app_id=?", path=...
1: [10-26 04:22:32.133] prepare took 0ms - succeeded,
sql="DELETE FROM params WHERE app_id=?", path=...
...
इन बातों पर ध्यान दें:
Configuration: पूल का जर्नल मोड और सिंक्रोनस मोड. Android 13 और इसके बाद के वर्शन में, इस लाइन में वे मोड दिखते हैं जिन्हें फ़्रेमवर्क ने लागू किया है. इनमें प्लैटफ़ॉर्म के डिफ़ॉल्ट मोड भी शामिल हैं. उदाहरण में,events.dbFULLसिंक्रोनस मोड के साथ रोलबैक जर्नल का इस्तेमाल करता है. इसलिए, हर कमिट पूरी तरह से सिंक होने का इंतज़ार करता है. इस लाइन में, आपके जनरेट किए गएPRAGMAस्टेटमेंट नहीं दिखते.Max connections: राइटेबल डेटाबेस के लिए 1 वैल्यू का मतलब है कि फ़्रेमवर्क, डेटाबेस को WAL मोड में नहीं मानता. इसलिए, रीड और राइट के लिए एक ही कनेक्शन शेयर किया जाता है.Total execution time,Total statements executed, औरAverage time per statement: Android 13 और इसके बाद के वर्शन में उपलब्ध है. इनका इस्तेमाल करके, सबसे ज़्यादा इस्तेमाल किए जाने वाले डेटाबेस ढूंढें.Most recently executed operations: हर कनेक्शन पर की गई पिछली 20 कार्रवाइयां, उनकी अवधि, और एसक्यूएल. लूप में चल रही एक ही क्वेरी या किसी लेन-देन के बाहर एक पंक्ति में कई बार डेटा लिखने जैसे पैटर्न देखें. Android 15 QPR1 और इसके बाद के वर्शन में, इस सूची में साफ़ तौर पर किए गए लेन-देन भी शामिल होते हैं.totalLongOperationsऔरOperations exceeding 2000ms: Android 15 QPR1 और इसके बाद के वर्शन में उपलब्ध है. इनसे पता चलता है कि कितने ऑपरेशन और लेन-देन में दो सेकंड से ज़्यादा समय लगा. साथ ही, इनमें आखिरी 10 ऑपरेशन और लेन-देन की सूची दिखती है.Acquired connectionsऔरConnection waiters: इस्तेमाल किए जा रहे कनेक्शन और कनेक्शन का इंतज़ार कर रहे थ्रेड. वेटर, कॉन्टेंट के बारे में बताते हैं. जैसे, लंबे समय तक चलने वाला राइट ट्रांज़ैक्शन, जो अन्य राइट को ब्लॉक करता है.Prepared statement cache:-vसे प्रिंट किया गया. कई एंट्री ऐसी होती हैं जो सिर्फ़ लिटरल वैल्यू में अलग होती हैं. ये एंट्री, स्ट्रिंग जोड़ने की प्रोसेस से बने एसक्यूएल की ओर इशारा करती हैं.- एक से ज़्यादा
Connection pool forहेडिंग के तहत एक ही पाथ होने का मतलब है कि आपके ऐप्लिकेशन ने एक ही फ़ाइल को दो बार खोला है.
अन्य संसाधन
कॉन्टेंट देखना
आपके लिए सुझाव
- ध्यान दें: JavaScript बंद होने पर, लिंक टेक्स्ट दिखता है
- लगातार इंटिग्रेशन में बेंचमार्क टेस्ट चलाना
- रुके हुए फ़्रेम
- Macrobenchmark के बिना बेसलाइन प्रोफ़ाइलें बनाना और उन्हें मेज़र करना