بهترین روش ها برای عملکرد SQLite

اندروید پشتیبانی داخلی از SQLite ، یک پایگاه داده SQL کارآمد، ارائه می‌دهد. برای بهینه‌سازی عملکرد برنامه خود، از این بهترین شیوه‌ها پیروی کنید و اطمینان حاصل کنید که با افزایش داده‌های شما، سرعت آن همچنان سریع و قابل پیش‌بینی باقی می‌ماند. با استفاده از این بهترین شیوه‌ها، احتمال مواجهه با مشکلات عملکردی که ایجاد و عیب‌یابی آنها دشوار است را نیز کاهش می‌دهید.

برای دستیابی به عملکرد سریع‌تر، این اصول عملکرد را دنبال کنید:

  • خواندن سطرها و ستون‌های کمتر : کوئری‌های خود را بهینه کنید تا فقط داده‌های ضروری بازیابی شوند. میزان داده‌های خوانده شده از پایگاه داده را به حداقل برسانید، زیرا بازیابی داده‌های اضافی می‌تواند بر عملکرد تأثیر بگذارد.

  • ارسال کار به موتور SQLite : انجام محاسبات، فیلتر کردن و مرتب‌سازی عملیات درون کوئری‌های SQL. استفاده از موتور کوئری SQLite می‌تواند عملکرد را به میزان قابل توجهی بهبود بخشد.

  • اصلاح طرحواره پایگاه داده : طرحواره پایگاه داده خود را طوری طراحی کنید که به SQLite در ساخت طرح‌های پرس‌وجو و نمایش داده‌های کارآمد کمک کند. جداول را به درستی فهرست‌بندی کنید و ساختارهای جدول را برای افزایش عملکرد بهینه کنید.

علاوه بر این، می‌توانید از ابزارهای عیب‌یابی موجود برای اندازه‌گیری عملکرد پایگاه داده SQLite خود استفاده کنید تا به شناسایی مناطقی که نیاز به بهینه‌سازی دارند، کمک کنید.

توصیه می‌کنیم از کتابخانه Jetpack Room استفاده کنید.

پیکربندی پایگاه داده برای عملکرد بهتر

برای پیکربندی پایگاه داده خود برای عملکرد بهینه در SQLite، مراحل این بخش را دنبال کنید.

فعال کردن ثبت وقایع پیش از نوشتن

SQLite جهش‌ها را با افزودن آنها به یک گزارش (log) پیاده‌سازی می‌کند، که گاهی اوقات آن را در پایگاه داده فشرده می‌کند. به این عمل، گزارش‌نویسی پیش از وقوع (WAL) می‌گویند.

WAL را فعال کنید، مگر اینکه از ATTACH DATABASE استفاده می‌کنید.

حالت همگام‌سازی را آرام کنید

هنگام استفاده از 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");

در این تنظیم، یک commit می‌تواند قبل از ذخیره شدن داده‌ها در دیسک بازگردد. اگر دستگاه خاموش شود، مانند قطع برق یا اختلال در هسته، داده‌های commit شده ممکن است از بین بروند. با این حال، به دلیل ثبت وقایع، پایگاه داده شما خراب نمی‌شود.

اگر فقط برنامه شما از کار بیفتد، داده‌های شما همچنان به دیسک می‌رسند. برای اکثر برنامه‌ها، این تنظیم بدون هیچ هزینه مادی، بهبود عملکرد را به همراه دارد.

تعریف طرحواره‌های جدول کارآمد

برای بهینه‌سازی عملکرد و به حداقل رساندن مصرف داده‌ها، یک طرح جدول کارآمد تعریف کنید. 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 پیاده‌سازی می‌شوند، اما پرس‌وجوهایی که بر 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 تبدیل می‌شود.

برای جداولی که کلید اصلی غیر از 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
    )
)

به جای بررسی محدودیت منحصر به فرد در کاتلین، می‌توانید آن را در 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

می‌توانید از 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 رابط خط فرمان (CLI) sqlite3_analyzer را برای ارائه اطلاعات اضافی که می‌توانند برای عیب‌یابی عملکرد استفاده شوند، ارائه می‌دهد. برای نصب، به صفحه دانلود SQLite مراجعه کنید.

شما می‌توانید adb pull برای دانلود یک فایل پایگاه داده از دستگاه هدف به ایستگاه کاری خود برای تجزیه و تحلیل استفاده کنید:

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

مرورگر SQLite

همچنین می‌توانید ابزار گرافیکی SQLite Browser را در صفحه دانلودهای 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 : اندازه یک صفحه پایگاه داده، بر حسب کیلوبایت.
  • dbsz : اندازه کل پایگاه داده، بر حسب صفحات. برای بدست آوردن اندازه بر حسب کیلوبایت، pgsz را در dbsz ضرب کنید.
  • Lookaside(b) : حافظه‌ای که به بافر lookaside در SQLite برای هر اتصال اختصاص داده می‌شود، بر حسب بایت. این حافظه‌ها معمولاً بسیار کوچک هستند.
  • cache hits : SQLite صفحات پایگاه داده را در حافظه پنهان (cache) نگهداری می‌کند. این تعداد بازدیدهای کش صفحه (count) است.
  • cache misses : تعداد خطاهای کش صفحه (تعداد).
  • cache size : تعداد صفحات موجود در حافظه پنهان (تعداد). برای بدست آوردن اندازه بر حسب کیلوبایت، این عدد را در pgsz ضرب کنید.
  • Dbname : مسیر فایل پایگاه داده. در مثال ما، برخی از پایگاه‌های داده عدد (1) یا عدد دیگری به نام خود اضافه کرده‌اند تا نشان دهند که بیش از یک اتصال به همان پایگاه داده اصلی وجود دارد. آمارها به ازای هر اتصال پیگیری می‌شوند.

در بخش POOL STATS را خواهید یافت:

  • cache hits : SQLite دستورات آماده را کش می‌کند و سعی می‌کند هنگام اجرای کوئری‌ها از آنها دوباره استفاده کند تا در تلاش و حافظه کامپایل دستورات SQL صرفه‌جویی شود. این تعداد بازدیدهای کش دستورات (تعداد) است.
  • cache misses : تعداد خطاهای حافظه پنهان دستور (تعداد).
  • cache size : از اندروید ۱۷ به بعد، این مقدار تعداد کل دستورات آماده‌شده در حافظه پنهان را فهرست می‌کند. در نسخه‌های قبلی، این مقدار معادل مجموع موفقیت‌ها و شکست‌های ذکر شده در دو ستون دیگر است و اندازه حافظه پنهان را نشان نمی‌دهد.

منابع اضافی

محتوا را مشاهده می‌کند

{% کلمه به کلمه %} {% فعل کمکی %} {% کلمه به کلمه %} {% فعل کمکی %}