يقدّم نظام التشغيل Android دعمًا مضمّنًا لـ SQLite، وهي قاعدة بيانات SQL فعالة. اتّبِع أفضل الممارسات التالية لتحسين أداء تطبيقك، ما يضمن استمرار سرعته العالية بشكل متوقّع مع زيادة حجم بياناتك. ومن خلال اتّباع أفضل الممارسات هذه، يمكنك أيضًا تقليل احتمال مواجهة مشاكل في الأداء يصعب تكرارها و تحديد المشاكل وحلّها.
لتحقيق أداء أسرع، اتّبِع مبادئ الأداء التالية:
قراءة عدد أقل من الصفوف والأعمدة: يمكنك تحسين طلبات البحث لاسترداد البيانات الضرورية فقط. قلِّل من كمية البيانات التي تتم قراءتها من قاعدة البيانات، لأنّه يمكن أن يؤثّر استرداد البيانات الزائدة في الأداء.
إرسال العمل إلى محرّك SQLite: يمكنك إجراء العمليات الحسابية والفلترة والفرز ضمن طلبات بحث SQL. يمكن أن يؤدي استخدام محرّك طلبات البحث في SQLite إلى تحسين الأداء بشكل كبير.
تعديل مخطّط قاعدة البيانات: يمكنك تصميم مخطّط قاعدة البيانات لمساعدة SQLite في إنشاء خطط استعلامات وتمثيلات بيانات فعّالة. فهرِس الجداول بشكلٍ سليم وتحسين هياكل الجداول لتحسين الأداء
بالإضافة إلى ذلك، يمكنك استخدام أدوات تحديد المشاكل وحلّها المتاحة لقياس أداء قاعدة بيانات SQLite للمساعدة في تحديد الجوانب التي تتطلّب التحسين.
ننصحك باستخدام مكتبة Jetpack Room.
ضبط قاعدة البيانات لتحسين الأداء
اتّبِع الخطوات الواردة في هذا القسم لضبط قاعدة بياناتك لتحقيق أفضل أداء في SQLite.
تفعيل ميزة "التسجيل المُسبَق للكتابة"
تنفِّذ SQLite عمليات التحويل عن طريق إلحاقها بسجلّ، يتم أحيانًا تكثيفه في قاعدة البيانات. ويُعرف هذا باسم تسجيل الكتابة مسبقًا (WAL).
فعِّل ملف "السجلّ الإداري للبيانات" (WAL)
ما لم تكن تستخدم ATTACH
DATABASE
.
تخفيف قيود وضع المزامنة
عند استخدام 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
كبحث سريع في شجرة B، ولكن طلبات البحث التي تتم تصفيتها حسب 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
أو في
ملف، ثم تخزين مسار الملف في العمود.
يتم تقريب حجم الملفات بشكل عام إلى أقرب عدد صحيح بزيادة قدرها 4 كيلوبايت. بالنسبة إلى الملفات الصغيرة جدًا التي يكون فيها
خطأ التقريب كبيرًا، يكون من الأفضل تخزينها في
قاعدة البيانات بتنسيق 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)، وليس في الرمز البرمجي
استخدِم طلب بحث واحدًا يعرض جميع النتائج المستهدَفة، بدلاً من حلقة برمجية تتكرّر في استعلامات SQL لعرض نتائج فردية. تكون دورة التحسين المبرمَج أبطأ بحوالي 1,000 مرة من طلب بحث SQL واحد.
استخدام 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
: تسلسل سلاسل مع فاصل اختياري
استخدِم COUNT()
بدلاً من Cursor.getCount()
.
في المثال التالي، تقرأ الدالة
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); ... }
استخدام طلبات البحث في Nest بدلاً من الرموز البرمجية
يمكن دمج لغة الاستعلامات البنيوية (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()) { ... } }
التحقّق من التفرد في SQL
إذا كان لا يجب إدراج صف ما إلا إذا كانت قيمة عمود معيّنة فريدة في جدول، قد يكون من الأفضل فرض هذه الفريدة كقيد عمود.
في المثال التالي، يتم تنفيذ طلب بحث واحد للتحقّق من صحة الصف الذي سيتم إدراجه وآخر لإدراجه فعليًا:
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 واجهة سطر الأوامر (CLI) في 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"
}
}
}
أفلام مُقترَحة لك
- ملاحظة: يتم عرض نص الرابط عندما تكون لغة JavaScript غير مفعّلة.
- تشغيل مقاييس الأداء في عملية الدمج المستمر
- الإطارات المجمّدة
- إنشاء الملفات الشخصية الأساسية وقياسها بدون استخدام أداة Macrobenchmark