SQLite পারফরম্যান্সের জন্য সর্বোত্তম অনুশীলন (ভিউ)

ধারণা এবং জেটপ্যাক কম্পোজ বাস্তবায়ন

অ্যান্ড্রয়েডে SQLite-এর জন্য বিল্ট-ইন সাপোর্ট রয়েছে, যা একটি কার্যকর SQL ডেটাবেস। আপনার অ্যাপের পারফরম্যান্স অপ্টিমাইজ করতে এই সেরা অনুশীলনগুলো অনুসরণ করুন, যাতে ডেটা বাড়ার সাথে সাথেও এটি দ্রুত এবং অনুমানযোগ্যভাবে দ্রুত থাকে। এই সেরা অনুশীলনগুলো ব্যবহার করে, আপনি এমন পারফরম্যান্স সমস্যার সম্মুখীন হওয়ার সম্ভাবনাও হ্রাস করেন যা পুনরায় তৈরি করা এবং সমাধান করা কঠিন।

দ্রুততর পারফরম্যান্স অর্জনের জন্য এই পারফরম্যান্স নীতিগুলো অনুসরণ করুন:

  • কম সারি এবং কলাম পড়ুন : শুধুমাত্র প্রয়োজনীয় ডেটা পুনরুদ্ধার করার জন্য আপনার কোয়েরিগুলোকে অপ্টিমাইজ করুন। ডাটাবেস থেকে পঠিত ডেটার পরিমাণ কমান, কারণ অতিরিক্ত ডেটা পুনরুদ্ধার পারফরম্যান্সকে প্রভাবিত করতে পারে।

  • SQLite ইঞ্জিনে কাজ স্থানান্তর করুন : SQL কোয়েরির মধ্যেই গণনা, ফিল্টারিং এবং সর্টিং অপারেশন সম্পাদন করুন। SQLite-এর কোয়েরি ইঞ্জিন ব্যবহার করে পারফরম্যান্স উল্লেখযোগ্যভাবে উন্নত করা যায়।

  • ডাটাবেস স্কিমা পরিবর্তন করুন : SQLite-কে কার্যকর কোয়েরি প্ল্যান এবং ডেটা উপস্থাপনা তৈরিতে সাহায্য করার জন্য আপনার ডাটাবেস স্কিমা ডিজাইন করুন। পারফরম্যান্স বাড়ানোর জন্য টেবিলগুলোকে সঠিকভাবে ইনডেক্স করুন এবং টেবিলের কাঠামো অপ্টিমাইজ করুন।

এছাড়াও, আপনার SQLite ডেটাবেসের পারফরম্যান্স পরিমাপ করতে এবং অপ্টিমাইজেশনের প্রয়োজন এমন ক্ষেত্রগুলো শনাক্ত করতে আপনি উপলব্ধ ট্রাবলশুটিং টুলগুলো ব্যবহার করতে পারেন।

আমরা জেটপ্যাক রুম লাইব্রেরি ব্যবহার করার পরামর্শ দিই।

পারফরম্যান্সের জন্য ডাটাবেস কনফিগার করুন।

SQLite-এ সর্বোত্তম পারফরম্যান্সের জন্য আপনার ডাটাবেস কনফিগার করতে এই বিভাগের ধাপগুলো অনুসরণ করুন।

সিঙ্ক্রোনাইজেশন মোড শিথিল করুন

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");

জাভা

// When opening the database
SQLiteDatabase.OpenParams.Builder paramsBuilder = new SQLiteDatabase.OpenParams.Builder();
paramsBuilder.setJournalMode(SQLiteDatabase.SYNC_MODE_NORMAL);

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

এই সেটিং-এ, ডেটা ডিস্কে সংরক্ষিত হওয়ার আগেই একটি কমিট সম্পন্ন হতে পারে। যদি বিদ্যুৎ চলে গেলে বা কার্নেল প্যানিকের কারণে ডিভাইসটি বন্ধ হয়ে যায়, তাহলে কমিট করা ডেটা হারিয়ে যেতে পারে। তবে, লগিং থাকার কারণে আপনার ডাটাবেসটি নষ্ট হয় না।

আপনার অ্যাপটি শুধু ক্র্যাশ করলেও আপনার ডেটা ডিস্কে পৌঁছায়। বেশিরভাগ অ্যাপের ক্ষেত্রে, এই সেটিংটি কোনো উল্লেখযোগ্য খরচ ছাড়াই পারফরম্যান্সের উন্নতি ঘটায়।

কোয়েরির পারফরম্যান্স উন্নত করুন

রেসপন্স টাইম কমিয়ে এবং প্রসেসিং দক্ষতা বাড়িয়ে SQLite-এ কোয়েরি পারফরম্যান্স উন্নত করতে এই সেরা পদ্ধতিগুলো অনুসরণ করুন।

শুধুমাত্র আপনার প্রয়োজনীয় সারিগুলো পড়ুন।

ফিল্টার আপনাকে তারিখের সীমা, অবস্থান বা নামের মতো নির্দিষ্ট মানদণ্ড উল্লেখ করে ফলাফলকে সীমিত করতে সাহায্য করে। লিমিট আপনাকে প্রদর্শিত ফলাফলের সংখ্যা নিয়ন্ত্রণ করতে দেয়:

কোটলিন

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

জাভা

try (Cursor cursor = db.rawQuery("""
    SELECT name
    FROM Customers
    LIMIT 10;
    """, null)) {
  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
    }
}

জাভা

// 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);
    // Further processing
  }
}

তবে, আপনার শুধু name কলামটিই প্রয়োজন:

কোটলিন

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

জাভা

try (Cursor cursor = db.rawQuery("""
    SELECT name
    FROM Customers;
    """, null)) {
  while (cursor.moveToNext()) {
    String 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
        }
    }
}

জাভা

@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 দিয়ে মানটি বাইন্ড করতে পারেন:

কোটলিন

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

জাভা

@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) এর বিভিন্ন আহ্বানের মধ্যে পুনরায় ব্যবহার করা হয়।

অনন্য মানগুলির জন্য 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
    }
}

জাভা

try (Cursor cursor = db.rawQuery("""
    SELECT DISTINCT name
    FROM Customers;
    """, null)) {
  while (cursor.moveToNext()) {
    // Only iterate over distinct names in Java
    // 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
}

জাভা

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

জাভা

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

জাভা

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

তবে, COUNT() ব্যবহার করলে ডাটাবেস শুধুমাত্র সংখ্যাটি ফেরত দেয়:

কোটলিন

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

জাভা

try (Cursor cursor = db.rawQuery("""
    SELECT COUNT(*)
    FROM Customers;
    """, null)) {
  cursor.moveToFirst();
  int 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
            }
        }
    }
}

জাভা

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

জাভা

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()) {
    // Process cursor data
  }
}

SQL-এ অনন্যতা যাচাই করুন

যদি টেবিলের কোনো নির্দিষ্ট কলামের মান অনন্য না হওয়া পর্যন্ত কোনো সারি যোগ করা না যায়, তাহলে সেই অনন্যতাটিকে একটি কলাম কনস্ট্রেইন্ট হিসেবে প্রয়োগ করা আরও বেশি কার্যকর হতে পারে।

নিম্নলিখিত উদাহরণে, সন্নিবেশ করার জন্য সারিটি যাচাই করতে একটি কোয়েরি এবং প্রকৃতপক্ষে সন্নিবেশ করতে আরেকটি কোয়েরি চালানো হয়:

কোটলিন

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

জাভা

// 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-কে সীমাবদ্ধতাটি পরীক্ষা করতে দিতে পারেন:

কোটলিন

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

জাভা

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 দ্রুত এবং কম ওভারহেডে কনস্ট্রেইন্ট ভ্যালিডেট করে। অ্যাপ কোডের পরিবর্তে SQLite ব্যবহার করাই উত্তম।

একটি একক লেনদেনে একাধিক সন্নিবেশ একসাথে করুন।

একটি ট্রানজ্যাকশন একাধিক অপারেশন সম্পন্ন করে, যা শুধু কার্যকারিতাই নয়, নির্ভুলতাও উন্নত করে। ডেটার সামঞ্জস্যতা উন্নত করতে এবং পারফরম্যান্স ত্বরান্বিত করতে, আপনি ব্যাচ ইনসারশন করতে পারেন:

কোটলিন

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

জাভা

db.beginTransaction();
try {
  for (customer : Customers) {
    db.execSQL(
        "INSERT INTO Customers VALUES (?, ?, ?)",
        new String[] {
          String.valueOf(customer.id),
          customer.name,
          "customerValue"
        });
  }
} finally {
  db.endTransaction()
}
{% হুবহু %} {% endverbatim %} {% হুবহু %} {% endverbatim %}