أفضل الممارسات لتحسين أداء SQLite

يتوافق Android تلقائيًا مع SQLite، وهي قاعدة بيانات SQL فعّالة. اتّبِع أفضل الممارسات التالية لتحسين أداء تطبيقك، ما يضمن بقاءه سريعًا وبسرعة متوقّعة مع زيادة بياناتك. من خلال استخدام أفضل الممارسات هذه، يمكنك أيضًا تقليل احتمالية مواجهة مشاكل في الأداء يصعب إعادة إنتاجها وتحديد المشاكل وحلّها.

لتحقيق أداء أسرع، اتّبِع مبادئ الأداء التالية:

  • قراءة عدد أقل من الصفوف والأعمدة: يمكنك تحسين طلبات البحث لاسترداد البيانات الضرورية فقط. قلِّل حجم البيانات التي تتم قراءتها من قاعدة البيانات، لأنّ استرداد البيانات الزائدة يمكن أن يؤثر في الأداء.

  • نقل العمل إلى محرّك SQLite: يمكنك إجراء العمليات الحسابية وعمليات الفلترة والفرز ضمن طلبات بحث SQL. يمكن أن يؤدي استخدام محرّك طلبات البحث في SQLite إلى تحسين الأداء بشكلٍ كبير.

  • تعديل مخطط قاعدة البيانات: يمكنك تصميم مخطط قاعدة البيانات لمساعدة SQLite في إنشاء خطط طلبات بحث وعروض بيانات فعّالة. يمكنك فهرسة الجداول بشكلٍ صحيح وتحسين هياكل الجداول لتحسين الأداء.

بالإضافة إلى ذلك، يمكنك استخدام أدوات تحديد المشاكل وحلّها المتاحة لقياس أداء قاعدة بيانات SQLite للمساعدة في تحديد المجالات التي تتطلّب التحسين.

ننصحك باستخدام مكتبة Jetpack Room.

إعداد قاعدة البيانات لتحسين الأداء

اتّبِع الخطوات الواردة في هذا القسم لإعداد قاعدة البيانات لتحقيق أفضل أداء في SQLite.

تفعيل ميزة Write-Ahead Logging

تنفّذ SQLite عمليات التعديل من خلال إلحاقها بسجلّ، ثم تضغطها بشكلٍ دوري في قاعدة البيانات. يُعرف ذلك باسم Write-Ahead Logging (WAL).

فعِّل ميزة WAL ما لم تكن تستخدم ATTACH DATABASE.

تخفيف وضع المزامنة

عند استخدام ميزة WAL، يؤدي كل إجراء `commit` تلقائيًا إلى إصدار أمر fsync للمساعدة في ضمان وصول البيانات إلى القرص. يؤدي ذلك إلى تحسين متانة البيانات، ولكنّه يبطئ عمليات `commit`.

يتضمّن 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 id الاسم مدينة
1 456 John Lennon Liverpool, England
2 123 مايكل جاكسون غاري، إنديانا
3 789 Dolly Parton Sevier County, TN

العمود rowid هو فهرس يحافظ على ترتيب الإدراج. يتم تنفيذ طلبات البحث التي يتم فلترتها حسب rowid كبحث سريع في شجرة B، ولكن طلبات البحث التي يتم فلترتها حسب id هي عملية فحص بطيئة للجدول.

إذا كنت تخطط لإجراء عمليات بحث حسب id، يمكنك تجنُّب تخزين الـ rowid عمود للحصول على بيانات أقل في مساحة التخزين وقاعدة بيانات أسرع بشكلٍ عام:

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

يبدو جدولك الآن على النحو التالي:

id الاسم مدينة
123 مايكل جاكسون غاري، إنديانا
456 John Lennon Liverpool, England
789 Dolly Parton Sevier County, TN

بما أنّك لست بحاجة إلى تخزين العمود 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
Liverpool, England 1
Sevier County, TN 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، وليس في الرمز

استخدِم طلب بحث واحدًا يعرض جميع النتائج المستهدَفة، بدلاً من حلقة برمجية تتكرّر في طلبات بحث SQL لعرض النتائج الفردية. تكون الحلقة البرمجية أبطأ بنحو 1000 مرة من طلب بحث 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: تربط السلاسل باستخدام فاصل اختياري.

استخدِم COUNT() بدلاً من Cursor.getCount()

في المثال التالي، تقرأ الدالة 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 من القيود بشكلٍ أسرع وبأقل قدر من النفقات العامة من رمز 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 Browser من صفحة تنزيلات 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"
    }
  }
}

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: مسار ملف قاعدة البيانات. في مثالنا، تم إلحاق (1) أو رقم آخر بأسماء بعض قواعد البيانات للإشارة إلى وجود أكثر من اتصال بقاعدة البيانات الأساسية نفسها. يتم تتبُّع الإحصاءات لكل اتصال.

ضمن POOL STATS، ستجد ما يلي:

  • cache hits: يخزّن SQLite العبارات المُعدّة مؤقتًا ويحاول إعادة استخدامها عند تشغيل طلبات البحث، لتوفير بعض الجهد والذاكرة في تجميع عبارات SQL. هذا هو عدد عمليات الوصول إلى ذاكرة التخزين المؤقت للعبارات (العدد).
  • cache misses: عدد عمليات عدم الوصول إلى ذاكرة التخزين المؤقت للعبارات (العدد).
  • cache size: بدءًا من Android 17، يعرض هذا الخيار العدد الإجمالي للعبارات المُعدّة في ذاكرة التخزين المؤقت. في الإصدارات السابقة، تكون هذه القيمة مساوية لمجموع عمليات الوصول وعدم الوصول المدرَجة في العمودَين الآخرَين، ولا تمثّل حجم ذاكرة التخزين المؤقت.

مراجع إضافية

عرض المحتوى