ב-Android יש תמיכה מובנית ב-SQLite, מסד נתונים יעיל של SQL. מומלץ לפעול לפי השיטות המומלצות האלה כדי לבצע אופטימיזציה של הביצועים של האפליקציה, וכך להבטיח שהיא תמשיך לפעול במהירות ובאופן צפוי ככל שהנתונים יתרבים. השימוש בשיטות המומלצות האלה גם מפחית את הסיכוי להיתקל בבעיות בביצועים שקשה לשחזר ולפתור.
כדי לשפר את הביצועים, כדאי לפעול לפי עקרונות הביצועים הבאים:
קריאה של פחות שורות ועמודות: כדאי לבצע אופטימיזציה של השאילתות כדי לאחזר רק את הנתונים הנחוצים. כדאי לצמצם את כמות הנתונים שנקרא ממסד הנתונים, כי אחזור נתונים מיותר יכול להשפיע על הביצועים.
העברת עבודה למנוע SQLite: ביצוע פעולות חישוב, סינון ומיון בתוך שאילתות ה-SQL. שימוש במנוע השאילתות של SQLite יכול לשפר משמעותית את הביצועים.
שינוי הסכימה של מסד הנתונים: תכנון הסכימה של מסד הנתונים כדי לעזור ל-SQLite ליצור תוכניות שאילתות ופריסות נתונים יעילות. הוספת טבלאות לאינדקס בצורה נכונה וביצוע אופטימיזציה של מבני הטבלאות כדי לשפר את הביצועים.
בנוסף, אפשר להשתמש בכלי פתרון הבעיות הזמינים כדי למדוד את הביצועים של מסד הנתונים של SQLite ולזהות אזורים שדורשים אופטימיזציה.
מומלץ להשתמש בספריית Jetpack Room.
הגדרת מסד הנתונים לשיפור הביצועים
כדי להגדיר את מסד הנתונים לביצועים אופטימליים ב-SQLite, פועלים לפי השלבים שמפורטים בקטע הזה.
הפעלת רישום ביומן מראש
SQLite מטמיע מוטציות על ידי צירוף שלהן ליומן, שמאוחד מדי פעם עם מסד הנתונים. התהליך הזה נקרא Write-Ahead Logging (WAL).
מפעילים את WAL, אלא אם משתמשים ב-ATTACH
DATABASE
.
להקל על מצב הסנכרון
כשמשתמשים ב-WAL, כברירת מחדל כל השמירה יוצרת fsync
כדי לוודא שהנתונים מגיעים לדיסק. כך אפשר לשפר את עמידות הנתונים, אבל ההתחייבויות (commits) יתבצעו לאט יותר.
ב-SQLite יש אפשרות לשלוט במצב הסינכרוני. אם מפעילים את WAL, צריך להגדיר את המצב הסינכרוני ל-NORMAL
:
Kotlin
db.execSQL("PRAGMA synchronous = NORMAL")
Java
db.execSQL("PRAGMA synchronous = NORMAL");
בהגדרה הזו, השמירה יכולה לחזור לפני שהנתונים מאוחסנים בדיסק. אם המכשיר יכבה, למשל בגלל הפסקת חשמל או חרדה בליבה (kernel panic), יכול להיות שהנתונים שהתחייבו יאבדו. עם זאת, בזכות הרישום ביומן, מסד הנתונים לא פגום.
אם רק האפליקציה קורסה, הנתונים עדיין מגיעים לדיסק. ברוב האפליקציות, ההגדרה הזו מובילה לשיפור בביצועים ללא עלות משמעותית.
הגדרת סכימות יעילות של טבלאות
כדי לשפר את הביצועים ולצמצם את צריכת הנתונים, צריך להגדיר סכימה יעילה של טבלה. 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-tree, אבל שאילתות שמסננות לפי 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
כפולה, כי היא נמצאת עכשיו גם בטבלה המקורית וגם באינדקס. מכיוון שאתם משתמשים ב-index, העלות של נפח האחסון הנוסף שווה את התועלת של שאילתות מהירות יותר.
עם זאת, אל תנהלו אינדקס שאתם לא משתמשים בו כדי להימנע מתשלום על עלות האחסון בלי שיפור בביצועי השאילתות.
יצירת אינדקסים עם כמה עמודות
אם השאילתות שלכם משלבות כמה עמודות, תוכלו ליצור אינדקסים של כמה עמודות כדי להאיץ את השאילתה באופן מלא. אפשר גם להשתמש באינדקס בעמודה חיצונית ולאפשר לחיפוש הפנימי להתבצע כסריקה לינארית.
לדוגמה, בהתאם לשאילתה הבאה:
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
או בקובץ, ואז לאחסן את נתיב הקובץ בעמודה.
בדרך כלל, הקבצים מרובעים למעלה ל-4KB. בקובצי נתונים קטנים מאוד, שבהם שגיאת העיגול משמעותית, יעיל יותר לאחסן אותם במסד הנתונים כ-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 Analyzer
ב-SQLite יש ממשק שורת פקודה (CLI) בשם sqlite3_analyzer
, שמאפשר לדפוק מידע נוסף שאפשר להשתמש בו כדי לפתור בעיות בביצועים. כדי להתקין את הספרייה, אפשר להיכנס אל דף ההורדה של SQLite.
אפשר להשתמש ב-adb pull
כדי להוריד קובץ מסד נתונים ממכשיר יעד למחשב העבודה לצורך ניתוח:
adb pull /data/data/<app_package_name>/databases/<db_name>.db
SQLite Browser
אפשר גם להתקין את הכלי עם ממשק המשתמש 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 tracing
כשמגדירים את Perfetto, אפשר להוסיף את הקוד הבא כדי לכלול מסלולים לשאילתות ספציפיות:
data_sources {
config {
name: "linux.ftrace"
ftrace_config {
atrace_categories: "database"
}
}
}
מומלץ עבורך
- הערה: טקסט הקישור מוצג כש-JavaScript מושבת
- הרצת מדדי ביצועים באינטגרציה רציפה
- פריימים קפואים
- יצירה ומדידה של פרופילים בסיסיים ללא Macrobenchmark