SQLite কর্মক্ষমতা জন্য সেরা অনুশীলন

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

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

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

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

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

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

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

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

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

রাইট-অহেড লগিং সক্ষম করুন

SQLite পরিবর্তনগুলিকে একটি লগে যুক্ত করার মাধ্যমে প্রয়োগ করে, যা এটি মাঝে মাঝে ডেটাবেসে সংকুচিত করে। একে রাইট-অহেড লগিং (WAL) বলা হয়।

আপনি যদি 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 কলামটি একটি ইনডেক্স যা ইনসারশন অর্ডার বজায় রাখে। rowid দ্বারা ফিল্টার করা কোয়েরিগুলো একটি দ্রুত B-tree সার্চ হিসাবে বাস্তবায়িত হয়, কিন্তু id দ্বারা ফিল্টার করা কোয়েরিগুলো একটি ধীরগতির টেবিল স্ক্যান।

আপনি যদি id দ্বারা অনুসন্ধান করার পরিকল্পনা করেন, তাহলে স্টোরেজে কম ডেটা ব্যবহার করতে এবং সার্বিকভাবে একটি দ্রুততর ডেটাবেস পেতে rowid কলামটি সংরক্ষণ করা এড়িয়ে যেতে পারেন।

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

আপনার টেবিলটি এখন দেখতে নিম্নরূপ:

আইডি নাম শহর
১২৩ মাইকেল জ্যাকসন গ্যারি, ইন্ডিয়ানা
৪৫৬ জন লেনন লিভারপুল, ইংল্যান্ড
৭৮৯ ডলি পার্টন সেভিয়ার কাউন্টি, টিএন

যেহেতু আপনার 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 এর সাথে ম্যাপ করা হয়:

শহর রোইড
গ্যারি, ইন্ডিয়ানা
লিভারপুল, ইংল্যান্ড
সেভিয়ার কাউন্টি, টিএন

লক্ষ্য করুন যে, 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 এর একটি উপনাম (alias) হয়ে যায়।

যেসব টেবিলের প্রাইমারি কী INTEGER ছাড়া অন্য কোনো সংখ্যা বা একাধিক কলামের সমন্বয়ে গঠিত, সেগুলোর ক্ষেত্রে WITHOUT ROWID ব্যবহার করার কথা বিবেচনা করুন।

অল্প ডেটা BLOB হিসেবে এবং বেশি ডেটা ফাইল হিসেবে সংরক্ষণ করুন।

যদি আপনি কোনো সারির সাথে বড় আকারের ডেটা, যেমন কোনো ছবির থাম্বনেইল বা কোনো কন্ট্যাক্টের ছবি যুক্ত করতে চান, তাহলে আপনি ডেটাটি একটি BLOB কলামে বা কোনো ফাইলে সংরক্ষণ করতে পারেন এবং তারপর কলামটিতে তার পাথটি সংরক্ষণ করতে পারেন।

ফাইলগুলি সাধারণত ৪ কিলোবাইট পর্যন্ত রাউন্ড আপ করা হয়। খুব ছোট ফাইলের ক্ষেত্রে, যেখানে রাউন্ডিং ত্রুটি উল্লেখযোগ্য, সেগুলিকে ডাটাবেসে 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-এ পুনরাবৃত্তি করুন।

পৃথক পৃথক ফলাফল পাওয়ার জন্য SQL কোয়েরিগুলোর ওপর প্রোগ্রাম্যাটিক লুপ চালানোর পরিবর্তে, এমন একটিমাত্র কোয়েরি ব্যবহার করুন যা সমস্ত কাঙ্ক্ষিত ফলাফল ফেরত দেয়। প্রোগ্রাম্যাটিক লুপটি একটিমাত্র SQL কোয়েরির চেয়ে প্রায় ১০০০ গুণ ধীরগতির।

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

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

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 দ্রুত এবং কম ওভারহেডে কনস্ট্রেইন্ট ভ্যালিডেট করে। অ্যাপ কোডের পরিবর্তে 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 চালু করুন। অ্যান্ড্রয়েড প্ল্যাটফর্মের বিভিন্ন সংস্করণে SQLite-এর ভিন্ন ভিন্ন সংস্করণ ব্যবহৃত হয়। অ্যান্ড্রয়েড চালিত ডিভাইসে থাকা ইঞ্জিনটি ব্যবহার করতে, আপনার টার্গেট ডিভাইসে 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 অ্যানালাইজার

SQLite পারফরম্যান্স ট্রাবলশুটিং-এর জন্য অতিরিক্ত তথ্য ডাম্প করতে sqlite3_analyzer কমান্ড-লাইন ইন্টারফেস (CLI) প্রদান করে। এটি ইনস্টল করতে, SQLite ডাউনলোড পেজ-এ যান।

বিশ্লেষণের জন্য আপনি টার্গেট ডিভাইস থেকে আপনার ওয়ার্কস্টেশনে একটি ডাটাবেস ফাইল ডাউনলোড করতে adb pull ব্যবহার করতে পারেন:

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

SQLite ব্রাউজার

এছাড়াও আপনি SQLite ডাউনলোড পেজ থেকে GUI টুল SQLite ব্রাউজারটি ইনস্টল করতে পারেন।

অ্যান্ড্রয়েড লগিং

অ্যান্ড্রয়েড আপনার জন্য SQLite কোয়েরিগুলোর সময় পরিমাপ করে এবং সেগুলো লগ করে রাখে:

# Enable query time logging
$ adb shell setprop log.tag.SQLiteTime VERBOSE
# Disable query time logging
$ adb shell setprop log.tag.SQLiteTime ERROR

পারফেট্টো ট্রেসিং

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 : একটি ডাটাবেস পেজের আকার, কিলোবাইটে (KB)।
  • dbsz : সম্পূর্ণ ডেটাবেসের আকার, পেজ এককে। কিলোবাইটে (KB) আকার পেতে, pgsz কে dbsz দিয়ে গুণ করুন।
  • Lookaside(b) : প্রতি সংযোগের জন্য SQLite লুকাসাইড বাফারে বরাদ্দকৃত মেমরি, বাইটে। এগুলি সাধারণত খুব ছোট হয়।
  • cache hits : SQLite ডাটাবেস পেজগুলির একটি ক্যাশ বজায় রাখে। এটি হলো পেজ ক্যাশ হিটের সংখ্যা (কাউন্ট)।
  • cache misses : পেজ ক্যাশ মিসের সংখ্যা (গণনা)।
  • cache size : ক্যাশে থাকা পেজের সংখ্যা (কাউন্ট)। কিলোবাইটে (KB) সাইজ পেতে, এই সংখ্যাটিকে pgsz দিয়ে গুণ করুন।
  • Dbname : DB ফাইলের পাথ। আমাদের উদাহরণে কিছু DB-এর নামের শেষে (1) বা অন্য কোনো সংখ্যা যুক্ত করা আছে, যা নির্দেশ করে যে একই ডাটাবেসে একাধিক সংযোগ রয়েছে। পরিসংখ্যান প্রতিটি সংযোগের জন্য ট্র্যাক করা হয়।

POOL STATS এর অধীনে আপনি পাবেন:

  • cache hits : SQLite প্রিপেয়ার্ড স্টেটমেন্টগুলোকে ক্যাশ করে রাখে এবং কোয়েরি চালানোর সময় সেগুলোকে পুনরায় ব্যবহার করার চেষ্টা করে, যাতে SQL স্টেটমেন্ট কম্পাইল করার ক্ষেত্রে কিছু শ্রম ও মেমরি সাশ্রয় হয়। এটিই হলো স্টেটমেন্ট ক্যাশ হিটের সংখ্যা (কাউন্ট)।
  • cache misses : স্টেটমেন্ট ক্যাশ মিসের সংখ্যা (গণনা)।
  • cache size : অ্যান্ড্রয়েড ১৭ থেকে শুরু করে, এটি ক্যাশে থাকা মোট প্রিপেয়ার্ড স্টেটমেন্টের সংখ্যা দেখায়। পূর্ববর্তী সংস্করণগুলিতে, এই মানটি অন্য দুটি কলামে তালিকাভুক্ত হিট এবং মিসের যোগফলের সমান ছিল এবং এটি ক্যাশ সাইজকে বোঝাতো না।

অতিরিক্ত সম্পদ

বিষয়বস্তু দেখুন

{% হুবহু %} {% endverbatim %} {% হুবহু %} {% endverbatim %}