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

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

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

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

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

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

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

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

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

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

पहले से लिखने की सुविधा चालू करना

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

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

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

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

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

Kotlin

db.execSQL("PRAGMA synchronous = NORMAL")

Java

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 id नाम शहर
1 456 जॉन लेनन लिवरपूल, इंग्लैंड
2 123 माइकल जैक्सन गैरी, इंडियाना
3 789 डॉली पार्टन सेवर काउंटी, टेनेसी

कॉलम rowid एक इंडेक्स है, जो इंसर्शन ऑर्डर को बनाए रखता है. rowid के हिसाब से फ़िल्टर की गई क्वेरी, तेज़ बी-ट्री सर्च के तौर पर लागू की जाती हैं. हालांकि, id के हिसाब से फ़िल्टर की गई क्वेरी, टेबल को धीरे-धीरे स्कैन करती हैं.

अगर आपको id के हिसाब से लुकअप करने हैं, तो rowid कॉलम को स्टोर करने से बचा जा सकता है. इससे, स्टोरेज में कम डेटा सेव होगा और डेटाबेस तेज़ी से काम करेगा:

CREATE TABLE Customers(
  id INTEGER PRIMARY KEY,
  name TEXT,
  city TEXT
);

आपकी टेबल अब इस तरह दिखेगी:

id नाम शहर
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 कॉलम या फ़ाइल में सेव किया जा सकता है. इसके बाद, कॉलम में फ़ाइल का पाथ सेव करें.

आम तौर पर, फ़ाइलों के साइज़ को चार केबी के हिसाब से राउंड अप किया जाता है. बहुत छोटी फ़ाइलों के लिए, जहां राउंडिंग में होने वाली गड़बड़ी काफ़ी ज़्यादा होती है, उन्हें डेटाबेस में BLOB के तौर पर सेव करना ज़्यादा बेहतर होता है. SQLite, फ़ाइल सिस्टम कॉल को कम करता है. साथ ही, कुछ मामलों में इसे इस्तेमाल करने से, फ़ाइल सिस्टम को इस्तेमाल करने से ज़्यादा तेज़ी से काम होता है.

क्वेरी की परफ़ॉर्मेंस को बेहतर बनाना

SQLite में क्वेरी की परफ़ॉर्मेंस को बेहतर बनाने के लिए, इन सबसे सही तरीकों का पालन करें. ऐसा करने से, रिस्पॉन्स में लगने वाले समय को कम किया जा सकता है और प्रोसेसिंग की क्षमता को बढ़ाया जा सकता है.

सिर्फ़ उन पंक्तियों को पढ़ें जिनकी आपको ज़रूरत है

फ़िल्टर की मदद से, तारीख की सीमा, जगह या नाम जैसी कुछ शर्तों के हिसाब से नतीजों को कम किया जा सकता है. सीमाओं की मदद से, आपको दिखने वाले नतीजों की संख्या को कंट्रोल करने की सुविधा मिलती है:

Kotlin

db.rawQuery("""
    SELECT name
    FROM Customers
    LIMIT 10;
    """.trimIndent(),
    null
).use { cursor ->
    while (cursor.moveToNext()) {
        ...
    }
}

Java

try (Cursor cursor = db.rawQuery("""
    SELECT name
    FROM Customers
    LIMIT 10;
    """, null)) {
  while (cursor.moveToNext()) {
    ...
  }
}

सिर्फ़ उन कॉलम को पढ़ना जिनकी आपको ज़रूरत है

ज़रूरत के बिना कॉलम चुनने से बचें. इससे आपकी क्वेरी धीमी हो सकती हैं और संसाधनों का गलत इस्तेमाल हो सकता है. इसके बजाय, सिर्फ़ उन कॉलम को चुनें जिनका इस्तेमाल किया जाता है.

नीचे दिए गए उदाहरण में, id, name, और phone को चुना गया है:

Kotlin

// 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)
        // ...
    }
}

Java

// This is not the most efficient way of doing this.
// See the following example for a better approach.

try (Cursor cursor = db.rawQuery("""
    SELECT id, name, phone
    FROM customers;
    """, null)) {
  while (cursor.moveToNext()) {
    String name = cursor.getString(1);
    ...
  }
}

हालांकि, आपको सिर्फ़ name कॉलम की ज़रूरत है:

Kotlin

db.rawQuery("""
    SELECT name
    FROM Customers;
    """.trimIndent(),
    null
).use { cursor ->
    while (cursor.moveToNext()) {
        val name = cursor.getString(0)
        ...
    }
}

Java

try (Cursor cursor = db.rawQuery("""
    SELECT name
    FROM Customers;
    """, null)) {
  while (cursor.moveToNext()) {
    String name = cursor.getString(0);
    ...
  }
}

स्ट्रिंग को जोड़ने के बजाय, SQL कार्ड की मदद से क्वेरी को पैरामीटराइज़ करें

आपकी क्वेरी स्ट्रिंग में ऐसा पैरामीटर शामिल हो सकता है जिसकी जानकारी सिर्फ़ रनटाइम के दौरान मिलती है. जैसे:

Kotlin

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
        }
    }
}

Java

@Nullable
public String getNameById(long id) {
  try (Cursor cursor = db.rawQuery(
      "SELECT name FROM customers WHERE id=" + id, null)) {
    if (cursor.moveToFirst()) {
      return cursor.getString(0);
    } else {
      return null;
    }
  }
}

पिछले कोड में, हर क्वेरी एक अलग स्ट्रिंग बनाती है. इसलिए, इसे स्टेटमेंट कैश मेमोरी का फ़ायदा नहीं मिलता. हर कॉल को लागू करने से पहले, SQLite को उसे संकलित करना पड़ता है. इसके बजाय, id आर्ग्युमेंट को पैरामीटर से बदला जा सकता है और वैल्यू को selectionArgs से बांधें:

Kotlin

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
        }
    }
}

Java

@Nullable
public String getNameById(long id) {
  try (Cursor cursor = db.rawQuery("""
          SELECT name
          FROM customers
          WHERE id=?
      """, new String[] {String.valueOf(id)})) {
    if (cursor.moveToFirst()) {
      return cursor.getString(0);
    } else {
      return null;
    }
  }
}

अब क्वेरी को एक बार कंपाइल करके कैश मेमोरी में सेव किया जा सकता है. getNameById(long) के अलग-अलग इनवोकेशन के बीच, संकलित की गई क्वेरी का फिर से इस्तेमाल किया जाता है.

कोड में नहीं, बल्कि SQL में दोहराएं

अलग-अलग नतीजे दिखाने के लिए, प्रोग्राम के हिसाब से चलने वाले उस लूप के बजाय, एक ऐसी क्वेरी का इस्तेमाल करें जो टारगेट किए गए सभी नतीजे दिखाती हो. प्रोग्रामैटिक लूप, एक एसक्यूएल क्वेरी के मुकाबले करीब 1,000 गुना धीमा होता है.

यूनीक वैल्यू के लिए DISTINCT का इस्तेमाल करना

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

Kotlin

db.rawQuery("""
    SELECT DISTINCT name
    FROM Customers;
    """.trimIndent(),
    null
).use { cursor ->
    while (cursor.moveToNext()) {
        // Only iterate over distinct names in Kotlin
        ...
    }
}

Java

try (Cursor cursor = db.rawQuery("""
    SELECT DISTINCT name
    FROM Customers;
    """, null)) {
  while (cursor.moveToNext()) {
    // Only iterate over distinct names in Java
    ...
  }
}

जब भी हो सके, एग्रीगेट फ़ंक्शन का इस्तेमाल करें

लाइन डेटा के बिना एग्रीगेट किए गए नतीजों के लिए, एग्रीगेट फ़ंक्शन का इस्तेमाल करें. उदाहरण के लिए, यहां दिया गया कोड यह जांचता है कि मैच करने वाली कम से कम एक पंक्ति मौजूद है या नहीं:

Kotlin

// 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
        ...
    } else {
        // No customers from Paris
        ...
}

Java

// This is not the most efficient way of doing this.
// See the following example for a better approach.

try (Cursor cursor = db.rawQuery("""
    SELECT id, name
    FROM Customers
    WHERE city = 'Paris';
    """, null)) {
  if (cursor.moveToFirst()) {
    // At least one customer from Paris
    ...
  } else {
    // No customers from Paris
    ...
  }
}

सिर्फ़ पहली पंक्ति फ़ेच करने के लिए, EXISTS() का इस्तेमाल करके 0 दिखाया जा सकता है. ऐसा तब किया जाता है, जब मैच करने वाली कोई पंक्ति मौजूद न हो. अगर एक या उससे ज़्यादा पंक्तियां मैच करती हैं, तो 1 दिखाया जा सकता है:

Kotlin

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
        ...
    } else {
        // No customers from Paris
        ...
    }
}

Java

try (Cursor cursor = db.rawQuery("""
    SELECT EXISTS (
      SELECT null
      FROM Customers
      WHERE city = 'Paris'
    );
    """, null)) {
  if (cursor.moveToFirst() && cursor.getInt(0) == 1) {
    // At least one customer from Paris
    ...
  } else {
    // No customers from Paris
    ...
  }
}

अपने ऐप्लिकेशन कोड में SQLite के एग्रीगेट फ़ंक्शन का इस्तेमाल करें:

  • COUNT: यह पता लगाता है कि किसी कॉलम में कितनी पंक्तियां हैं.
  • SUM: किसी कॉलम में मौजूद सभी संख्यात्मक वैल्यू जोड़ता है.
  • MIN या MAX: सबसे कम या सबसे ज़्यादा वैल्यू तय करता है. यह अंकों वाले कॉलम, DATE टाइप, और टेक्स्ट टाइप के लिए काम करता है.
  • AVG: संख्या वाली औसत वैल्यू ढूंढता है.
  • GROUP_CONCAT: वैकल्पिक सेपरेटर की मदद से स्ट्रिंग को जोड़ता है.

Cursor.getCount() के बजाय COUNT() का इस्तेमाल करें

इस उदाहरण में, Cursor.getCount() फ़ंक्शन, डेटाबेस की सभी पंक्तियों को पढ़ता है और सभी पंक्तियों की वैल्यू दिखाता है:

Kotlin

// 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()
}

Java

// This is not the most efficient way of doing this.
// See the following example for a better approach.

try (Cursor cursor = db.rawQuery("""
    SELECT id
    FROM Customers;
    """, null)) {
  int count = cursor.getCount();
  ...
}

हालांकि, COUNT() का इस्तेमाल करने पर, डेटाबेस सिर्फ़ संख्या दिखाता है:

Kotlin

db.rawQuery("""
    SELECT COUNT(*)
    FROM Customers;
    """.trimIndent(),
    null
).use { cursor ->
    cursor.moveToFirst()
    val count = cursor.getInt(0)
}

Java

try (Cursor cursor = db.rawQuery("""
    SELECT COUNT(*)
    FROM Customers;
    """, null)) {
  cursor.moveToFirst();
  int count = cursor.getInt(0);
  ...
}

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

SQL को कॉम्पोज़ किया जा सकता है. साथ ही, इसमें सबक्वेरी, जॉइन, और फ़ॉरेन-की की पाबंदियां काम करती हैं. ऐप्लिकेशन कोड के बिना, एक क्वेरी के नतीजे को दूसरी क्वेरी में इस्तेमाल किया जा सकता है. इससे SQLite से डेटा कॉपी करने की ज़रूरत कम हो जाती है. साथ ही, डेटाबेस इंजन आपकी क्वेरी को ऑप्टिमाइज़ कर पाता है.

नीचे दिए गए उदाहरण में, किसी क्वेरी को चलाकर यह पता लगाया जा सकता है कि किस शहर में सबसे ज़्यादा ग्राहक हैं. इसके बाद, उस शहर के सभी ग्राहकों को ढूंढने के लिए, नतीजे का इस्तेमाल किसी दूसरी क्वेरी में किया जा सकता है:

Kotlin

// 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()) {
                ...
            }
        }
    }
}

Java

// This is not the most efficient way of doing this.
// See the following example for a better approach.

try (Cursor cursor = db.rawQuery("""
    SELECT city
    FROM Customers
    GROUP BY city
    ORDER BY COUNT(*) DESC
    LIMIT 1;
    """, null)) {
  if (cursor.moveToFirst()) {
    String topCity = cursor.getString(0);
    try (Cursor innerCursor = db.rawQuery("""
        SELECT name, city
        FROM Customers
        WHERE city = ?;
        """, new String[] {topCity})) {
        while (innerCursor.moveToNext()) {
          ...
        }
    }
  }
}

पिछले उदाहरण के मुकाबले आधे समय में नतीजा पाने के लिए, नेस्ट किए गए स्टेटमेंट के साथ एक SQL क्वेरी का इस्तेमाल करें:

Kotlin

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()) {
        ...
    }
}

Java

try (Cursor cursor = db.rawQuery("""
    SELECT name, city
    FROM Customers
    WHERE city IN (
      SELECT city
      FROM Customers
      GROUP BY city
      ORDER BY COUNT(*) DESC
      LIMIT 1
    );
    """, null)) {
  while(cursor.moveToNext()) {
    ...
  }
}

एसक्यूएल में यूनीक होने की जांच करना

अगर किसी कॉलम की वैल्यू टेबल में यूनीक होने तक कोई पंक्ति नहीं डाली जानी चाहिए, तो उस यूनीक वैल्यू को कॉलम की पाबंदी के तौर पर लागू करना ज़्यादा असरदार हो सकता है.

यहां दिए गए उदाहरण में, डाली जाने वाली लाइन की पुष्टि करने के लिए एक क्वेरी और लाइन डालने के लिए दूसरी क्वेरी चलाई गई है:

Kotlin

// 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
    )
)

Java

// This is not the most efficient way of doing this.
// See the following example for a better approach.

try (Cursor cursor = db.rawQuery("""
    SELECT EXISTS (
      SELECT null
      FROM customers
      WHERE username = ?
    );
    """, new String[] { customer.username })) {
  if (cursor.moveToFirst() && cursor.getInt(0) == 1) {
    throw new AddCustomerException(customer);
  }
}
db.execSQL(
    "INSERT INTO customers VALUES (?, ?, ?)",
    new String[] {
      String.valueOf(customer.id),
      customer.name,
      customer.username,
    });

Kotlin या Java में यूनीक कंस्ट्रेंट की जांच करने के बजाय, टेबल तय करते समय इसे 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 को शर्त की जांच करने की अनुमति है:

Kotlin

try {
    db.execSql(
        "INSERT INTO Customers VALUES (?, ?, ?)",
        arrayOf(customer.id.toString(), customer.name, customer.username)
    )
} catch(e: SQLiteConstraintException) {
    throw AddCustomerException(customer, e)
}

Java

try {
  db.execSQL(
      "INSERT INTO Customers VALUES (?, ?, ?)",
      new String[] {
        String.valueOf(customer.id),
        customer.name,
        customer.username,
      });
} catch (SQLiteConstraintException e) {
  throw new AddCustomerException(customer, e);
}

SQLite, एक से ज़्यादा कॉलम वाले यूनीक इंडेक्स के साथ काम करता है:

CREATE TABLE table(...);
CREATE UNIQUE INDEX unique_table ON table(column1, column2, ...);

SQLite, Kotlin या Java कोड की तुलना में तेज़ी से और कम ओवरहेड के साथ सीमाओं की पुष्टि करता है. ऐप्लिकेशन कोड के बजाय, SQLite का इस्तेमाल करना सबसे सही तरीका है.

एक ही ट्रांज़ैक्शन में कई इंसर्शन एक साथ करना

लेन-देन में कई कार्रवाइयां की जाती हैं. इससे न सिर्फ़ परफ़ॉर्मेंस बेहतर होती है, बल्कि सटीक जानकारी भी मिलती है. डेटा की एक जैसी क्वालिटी बनाए रखने और परफ़ॉर्मेंस को बेहतर बनाने के लिए, एक साथ कई आइटम जोड़े जा सकते हैं:

Kotlin

db.beginTransaction()
try {
    customers.forEach { customer ->
        db.execSql(
            "INSERT INTO Customers VALUES (?, ?, ...)",
            arrayOf(customer.id.toString(), customer.name, ...)
        )
    }
} finally {
    db.endTransaction()
}

Java

db.beginTransaction();
try {
  for (customer : Customers) {
    db.execSQL(
        "INSERT INTO Customers VALUES (?, ?, ...)",
        new String[] {
          String.valueOf(customer.id),
          customer.name,
          ...
        });
  }
} 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 के डाउनलोड पेज पर जाकर, जीयूआई टूल SQLite ब्राउज़र भी इंस्टॉल किया जा सकता है.

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"
    }
  }
}