SQLite की परफ़ॉर्मेंस के लिए सबसे सही तरीके

Android, SQLite के लिए बिल्ट-इन सपोर्ट उपलब्ध कराता है. यह एक असरदार एसक्यूएल डेटाबेस है. अपने ऐप्लिकेशन की परफ़ॉर्मेंस को ऑप्टिमाइज़ करने के लिए, इन सबसे सही तरीकों का इस्तेमाल करें. इससे यह पक्का किया जा सकेगा कि आपका ऐप्लिकेशन तेज़ी से काम करता रहे और डेटा बढ़ने पर भी इसकी परफ़ॉर्मेंस में कोई बदलाव न हो. इन सबसे सही तरीकों का इस्तेमाल करने से, परफ़ॉर्मेंस से जुड़ी उन समस्याओं के होने की संभावना भी कम हो जाती है जिन्हें दोहराना और ठीक करना मुश्किल होता है.

बेहतर परफ़ॉर्मेंस के लिए, परफ़ॉर्मेंस के इन सिद्धांतों का पालन करें:

  • कम पंक्तियां और कॉलम पढ़ें: सिर्फ़ ज़रूरी डेटा पाने के लिए, अपनी क्वेरी को ऑप्टिमाइज़ करें. डेटाबेस से कम से कम डेटा पढ़ें, क्योंकि ज़्यादा डेटा पाने से परफ़ॉर्मेंस पर असर पड़ सकता है.

  • SQLite इंजन को काम सौंपना: एसक्यूएल क्वेरी में कैलकुलेशन, फ़िल्टर करने, और क्रम से लगाने के ऑपरेशन करना. SQLite के क्वेरी इंजन का इस्तेमाल करने से, परफ़ॉर्मेंस को बेहतर बनाया जा सकता है.

  • डेटाबेस स्कीमा में बदलाव करना: अपने डेटाबेस स्कीमा को इस तरह से डिज़ाइन करें कि SQLite, क्वेरी के बेहतर प्लान और डेटा प्रज़ेंटेशन बना सके. टेबल को सही तरीके से इंडेक्स करें और परफ़ॉर्मेंस को बेहतर बनाने के लिए, टेबल के स्ट्रक्चर को ऑप्टिमाइज़ करें.

इसके अलावा, समस्या हल करने के लिए उपलब्ध टूल का इस्तेमाल करके, अपने SQLite डेटाबेस की परफ़ॉर्मेंस का आकलन किया जा सकता है. इससे उन क्षेत्रों की पहचान करने में मदद मिलती है जिनमें ऑप्टिमाइज़ेशन की ज़रूरत होती है.

हमारा सुझाव है कि आप Jetpack Room लाइब्रेरी का इस्तेमाल करें.

परफ़ॉर्मेंस के लिए डेटाबेस कॉन्फ़िगर करना

SQLite में बेहतर परफ़ॉर्मेंस के लिए, अपने डेटाबेस को कॉन्फ़िगर करने के लिए, इस सेक्शन में दिया गया तरीका अपनाएं.

Write-Ahead Logging की सुविधा चालू करना

SQLite, म्यूटेशन को लॉग में जोड़कर लागू करता है. यह लॉग को कभी-कभी डेटाबेस में कंप्रेस करता है. इसे राइट-अहेड लॉगिंग (डब्ल्यूएएल) कहा जाता है.

अगर ATTACH DATABASE का इस्तेमाल नहीं किया जा रहा है, तो WAL चालू करें.

सिंक करने का मोड बदलना

WAL का इस्तेमाल करते समय, डिफ़ॉल्ट रूप से हर कमिट एक fsync जारी करता है, ताकि यह पक्का किया जा सके कि डेटा डिस्क तक पहुंच गया है. इससे डेटा की ड्यूरेबिलिटी बेहतर होती है, लेकिन कमिट करने की प्रोसेस धीमी हो जाती है.

SQLite में, सिंक्रोनस मोड को कंट्रोल करने का विकल्प होता है. अगर आपने WAL चालू किया है, तो सिंक्रोनस मोड को NORMAL पर सेट करें:

// When opening the database
val paramsBuilder: SQLiteDatabase.OpenParams.Builder = SQLiteDatabase.OpenParams.Builder()
paramsBuilder.journalMode = SQLiteDatabase.SYNC_MODE_NORMAL

// Or: after having opened the database
db.execSQL("PRAGMA synchronous = NORMAL");

इस सेटिंग में, डेटा को डिस्क में सेव करने से पहले ही कमिट किया जा सकता है. अगर डिवाइस बंद हो जाता है, जैसे कि बिजली चले जाने या कर्नल पैनिक की वजह से, तो हो सकता है कि कमिट किया गया डेटा मिट जाए. हालांकि, लॉगिंग की वजह से आपका डेटाबेस खराब नहीं होता.

अगर सिर्फ़ आपका ऐप्लिकेशन क्रैश होता है, तो भी आपका डेटा डिस्क तक पहुंच जाता है. ज़्यादातर ऐप्लिकेशन के लिए, इस सेटिंग से परफ़ॉर्मेंस बेहतर होती है. इसके लिए, कोई शुल्क नहीं लिया जाता.

टेबल के बेहतर स्कीमा तय करना

परफ़ॉर्मेंस को ऑप्टिमाइज़ करने और डेटा की खपत को कम करने के लिए, टेबल का बेहतर स्कीमा तय करें. 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) के अलग-अलग इनवोकेशन के बीच किया जाता है.

कोड में नहीं, बल्कि एसक्यूएल में बदलाव करना

एक ऐसी क्वेरी का इस्तेमाल करें जो टारगेट किए गए सभी नतीजे दिखाती हो. इसके बजाय, प्रोग्राम के हिसाब से SQL क्वेरी पर लूप चलाने से अलग-अलग नतीजे मिलते हैं. प्रोग्राम के हिसाब से बनाए गए लूप की स्पीड, एसक्यूएल क्वेरी की तुलना में करीब 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
}

कोड के बजाय नेस्ट क्वेरी

SQL को कंपोज़ किया जा सकता है. साथ ही, यह सबक्वेरी, जॉइन, और फ़ॉरेन की की पाबंदियों के साथ काम करता है. ऐप्लिकेशन कोड का इस्तेमाल किए बिना, एक क्वेरी के नतीजे का इस्तेमाल दूसरी क्वेरी में किया जा सकता है. इससे 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")
        )
    }
} finally {
    db.endTransaction()
}

समस्या हल करने वाले टूल का इस्तेमाल करना

SQLite, परफ़ॉर्मेंस को मेज़र करने में मदद करने के लिए, समस्या हल करने वाले ये टूल उपलब्ध कराता है.

SQLite के इंटरैक्टिव प्रॉम्प्ट का इस्तेमाल करना

क्वेरी चलाने और सीखने के लिए, अपनी मशीन पर SQLite चलाएं. Android प्लैटफ़ॉर्म के अलग-अलग वर्शन, SQLite के अलग-अलग वर्शन का इस्तेमाल करते हैं. Android डिवाइस पर मौजूद इंजन का इस्तेमाल करने के लिए, adb shell का इस्तेमाल करें. इसके बाद, टारगेट डिवाइस पर sqlite3 चलाएं.

SQLite से क्वेरी के लिए समय का अनुरोध किया जा सकता है:

sqlite> .timer on
sqlite> SELECT ...
Run Time: real ... user ... sys ...

EXPLAIN QUERY PLAN

SQLite से यह पूछा जा सकता है कि वह EXPLAIN QUERY PLAN का इस्तेमाल करके, किसी क्वेरी का जवाब कैसे देगा:

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 ऐनालाइज़र

SQLite, परफ़ॉर्मेंस से जुड़ी समस्याओं को हल करने के लिए, sqlite3_analyzer कमांड-लाइन इंटरफ़ेस (सीएलआई) का इस्तेमाल करता है. इससे अतिरिक्त जानकारी डंप की जा सकती है. इसे इंस्टॉल करने के लिए, SQLite का डाउनलोड पेज पर जाएं.

विश्लेषण के लिए, टारगेट डिवाइस से डेटाबेस फ़ाइल को अपने वर्कस्टेशन पर डाउनलोड करने के लिए, adb pull का इस्तेमाल किया जा सकता है:

adb pull /data/data/<app_package_name>/databases/<db_name>.db

SQLite ब्राउज़र

SQLite के डाउनलोड पेज पर जाकर, GUI टूल 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

Perfetto ट्रेसिंग

Perfetto को कॉन्फ़िगर करते समय, अलग-अलग क्वेरी के लिए ट्रैक शामिल करने के लिए, यह जानकारी जोड़ी जा सकती है:

data_sources {
  config {
    name: "linux.ftrace"
    ftrace_config {
      atrace_categories: "database"
    }
  }
}

dumpsys meminfo

adb shell dumpsys meminfo <package-name>, ऐप्लिकेशन की मेमोरी के इस्तेमाल से जुड़े आंकड़े प्रिंट करेगा. इसमें 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

DATABASES में आपको यह जानकारी मिलेगी:

  • pgsz: डेटाबेस के एक पेज का साइज़, केबी में.
  • dbsz: पूरे डेटाबेस का साइज़, पेजों में. केबी में साइज़ पाने के लिए, pgsz को dbsz से गुणा करें.
  • Lookaside(b): हर कनेक्शन के लिए, SQLite लुकअसाइड बफ़र को असाइन की गई मेमोरी. यह बाइट में होती है. ये आम तौर पर बहुत छोटे होते हैं.
  • cache hits: SQLite, डेटाबेस पेजों की कैश मेमोरी बनाए रखता है. यह पेज कैश मेमोरी के हिट की संख्या है.
  • cache misses: पेज कैश मेमोरी में मौजूद नहीं होने की संख्या (गिनती).
  • cache size: कैश मेमोरी में मौजूद पेजों की संख्या (गिनती). केबी में साइज़ पाने के लिए, इस संख्या को pgsz से गुणा करें.
  • Dbname: DB फ़ाइल का पाथ. हमारे उदाहरण में, कुछ डेटाबेस के नाम के आगे (1) या कोई दूसरा नंबर जोड़ा गया है. इससे पता चलता है कि एक ही डेटाबेस से एक से ज़्यादा कनेक्शन हैं. आंकड़ों को हर कनेक्शन के हिसाब से ट्रैक किया जाता है.

POOL STATS में आपको यह जानकारी मिलेगी:

  • cache hits: SQLite, तैयार किए गए स्टेटमेंट को कैश मेमोरी में सेव करता है. साथ ही, क्वेरी चलाने के दौरान उन्हें फिर से इस्तेमाल करने की कोशिश करता है. इससे एसक्यूएल स्टेटमेंट को कंपाइल करने में लगने वाली मेहनत और मेमोरी को बचाया जा सकता है. यह स्टेटमेंट कैश हिट की संख्या है.
  • cache misses: स्टेटमेंट कैश मेमोरी में मौजूद नहीं है (गिनती).
  • cache size: Android 17 से शुरू होने वाले वर्शन में, इससे कैश मेमोरी में मौजूद तैयार स्टेटमेंट की कुल संख्या का पता चलता है. पिछले वर्शन में, यह वैल्यू अन्य दो कॉलम में दी गई हिट और मिस की संख्या के बराबर होती है. साथ ही, यह कैश मेमोरी के साइज़ को नहीं दिखाती है.

अन्य संसाधन

कॉन्टेंट देखना