يوفر Android دعمًا مضمّنًا لـ SQLite، وهي قاعدة بيانات SQL فعالة. اتّبِع أفضل الممارسات التالية لتحسين أداء التطبيق، والتأكّد من الحفاظ على سرعته وسهولة توقّعه مع نمو بياناتك. من خلال اتّباع أفضل الممارسات هذه، يمكنك أيضًا الحدّ من احتمال مواجهة مشاكل في الأداء يصعب إعادة إنتاجها وتحديد المشاكل وحلّها.
لتحقيق أداء أسرع، اتّبِع مبادئ الأداء التالية:
قراءة عدد أقل من الصفوف والأعمدة: يمكنك تحسين طلبات البحث لاسترداد البيانات الضرورية فقط. قم بتقليل كمية البيانات المقروءة من قاعدة البيانات، لأن استرداد البيانات الزائد يمكن أن يؤثر على الأداء.
دفع العمل إلى محرك SQLite: يمكنك إجراء عمليات العمليات الحسابية والفلترة والترتيب ضمن طلبات بحث SQL. يمكن أن يؤدي استخدام محرك استعلام SQLite إلى تحسين الأداء بشكل كبير.
تعديل مخطط قاعدة البيانات: صمِّم مخطط قاعدة البيانات لمساعدة SQLite في إنشاء خطط طلبات بحث وتمثيلات فعّالة للبيانات. يمكنك فهرسة الجداول بشكل صحيح وتحسين هياكل الجداول لتحسين الأداء.
بالإضافة إلى ذلك، يمكنك استخدام الأدوات المتاحة لتحديد المشاكل وحلّها لقياس أداء قاعدة بيانات SQLite للمساعدة في تحديد المجالات التي تحتاج إلى تحسين.
ننصحك باستخدام مكتبة غرفة Jetpack.
ضبط قاعدة البيانات للأداء
اتبع الخطوات الواردة في هذا القسم لإعداد قاعدة البيانات لتحقيق الأداء الأمثل في 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');
نتيجة الجدول هي على النحو التالي:
معرّف الصف | 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
:
مدينة | معرّف الصف |
---|---|
غاري، إنديانا | 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); ... } }
استخدِم 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 على صفحة "عمليات التنزيل" في 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 tracing
### Perfetto tracing {:#perfetto-tracing}
When [configuring Perfetto](https://perfetto.dev/docs/concepts/config), you may
add the following to include tracks for individual queries:
```protobuf
data_sources {
config {
name: "linux.ftrace"
ftrace_config {
atrace_categories: "database"
}
}
}
أفلام مُقترَحة لك
- ملاحظة: يتم عرض نص الرابط عند إيقاف JavaScript.
- تنفيذ مقاييس الأداء في عملية الدمج المستمر
- إطارات ثابتة
- إنشاء الملفات التجارية المرجعية وقياسها بدون استخدام مقاييس الأداء الكلية