Android มีการรองรับ SQLite ซึ่งเป็นฐานข้อมูล SQL ที่มีประสิทธิภาพในตัว ทำตามแนวทางปฏิบัติแนะนำเหล่านี้เพื่อเพิ่มประสิทธิภาพของแอปให้สูงสุด เพื่อให้แอปยังคงทำงานได้อย่างรวดเร็วและรวดเร็วอย่างสม่ำเสมอเมื่อข้อมูลเพิ่มขึ้น การใช้แนวทางปฏิบัติแนะนำเหล่านี้ยังช่วยลดโอกาสที่จะพบปัญหาด้านประสิทธิภาพที่แก้ไขและแก้ปัญหาได้ยาก
หากต้องการให้แอปมีประสิทธิภาพเร็วขึ้น ให้ทำตามหลักการด้านประสิทธิภาพต่อไปนี้
อ่านแถวและคอลัมน์ให้น้อยลง: เพิ่มประสิทธิภาพการค้นหาเพื่อดึงข้อมูลที่จำเป็นเท่านั้น ลดปริมาณข้อมูลที่อ่านจากฐานข้อมูล เนื่องจากดึงข้อมูลมากเกินไปอาจส่งผลต่อประสิทธิภาพ
ส่งงานไปยังเครื่องมือ SQLite: ดำเนินการคำนวณ การกรอง และการจัดเรียง ภายในคำค้นหา SQL การใช้เครื่องมือค้นหาของ SQLite สามารถปรับปรุงประสิทธิภาพได้อย่างมาก
แก้ไขสคีมาฐานข้อมูล: ออกแบบสคีมาฐานข้อมูลเพื่อช่วยให้ SQLite สร้างแผนการค้นหาและการแสดงข้อมูลที่มีประสิทธิภาพ จัดทำดัชนีตารางและเพิ่มประสิทธิภาพโครงสร้างตารางอย่างเหมาะสมเพื่อเพิ่มประสิทธิภาพ
นอกจากนี้ คุณยังใช้เครื่องมือแก้ปัญหาที่มีอยู่เพื่อวัดประสิทธิภาพของฐานข้อมูล SQLite เพื่อช่วยระบุส่วนที่ต้องเพิ่มประสิทธิภาพได้ด้วย
เราขอแนะนำให้ใช้ไลบรารี Jetpack Room
กำหนดค่าฐานข้อมูลเพื่อประสิทธิภาพ
ทำตามขั้นตอนในส่วนนี้เพื่อกำหนดค่าฐานข้อมูลให้มีประสิทธิภาพสูงสุดใน SQLite
เปิดใช้การบันทึกแบบเขียนล่วงหน้า
SQLite ใช้การเปลี่ยนแปลงโดยการผนวกการเปลี่ยนแปลงเหล่านั้นเข้ากับบันทึก ซึ่งจะบีบอัดลงในฐานข้อมูลเป็นครั้งคราว ซึ่งเรียกว่าการบันทึกแบบเขียนล่วงหน้า (Write-Ahead Logging หรือ 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");
ในการตั้งค่านี้ การคอมมิตสามารถแสดงผลก่อนที่จะจัดเก็บข้อมูลในดิสก์ หากเกิดการปิดเครื่อง เช่น เมื่อไฟดับหรือเกิด 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 | name | city |
|---|---|---|---|
| 1 | 456 | John Lennon | Liverpool, England |
| 2 | 123 | Michael Jackson | Gary, IN |
| 3 | 789 | Dolly Parton | Sevier County, TN |
คอลัมน์ rowid เป็น
ดัชนีที่รักษาลำดับการแทรก การค้นหาที่
กรองตาม rowid จะดำเนินการเป็นการค้นหา B-tree อย่างรวดเร็ว แต่การค้นหาที่
กรองตาม id จะเป็นการสแกนตารางอย่างช้าๆ
หากวางแผนที่จะค้นหาตาม id คุณสามารถหลีกเลี่ยงการจัดเก็บคอลัมน์
rowid เพื่อให้มีข้อมูลน้อยลงในพื้นที่เก็บข้อมูลและฐานข้อมูลโดยรวม
เร็วขึ้นได้ดังนี้
CREATE TABLE Customers(
id INTEGER PRIMARY KEY,
name TEXT,
city TEXT
);
ตอนนี้ตารางของคุณจะมีลักษณะดังนี้
| id | name | city |
|---|---|---|
| 123 | Michael Jackson | Gary, IN |
| 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 ดังนี้
| city | rowid |
|---|---|
| Gary, IN | 2 |
| Liverpool, England | 1 |
| Sevier County, TN | 3 |
โปรดทราบว่าค่าใช้จ่ายในการจัดเก็บคอลัมน์ city จะเพิ่มขึ้นเป็น 2 เท่า เนื่องจากตอนนี้คอลัมน์นี้อยู่ในทั้งตารางเดิมและดัชนี เนื่องจากคุณใช้ดัชนี ค่าใช้จ่ายในการจัดเก็บที่เพิ่มขึ้นจึงคุ้มค่ากับประโยชน์ที่ได้รับจากการค้นหาที่เร็วขึ้น
อย่างไรก็ตาม อย่าเก็บดัชนีที่คุณไม่ได้ใช้เพื่อหลีกเลี่ยงการจ่ายค่าใช้จ่ายในการจัดเก็บโดยไม่ได้รับประสิทธิภาพการค้นหาเพิ่มขึ้น
สร้างดัชนีหลายคอลัมน์
หากการค้นหาของคุณรวมคอลัมน์หลายคอลัมน์ คุณสามารถสร้าง ดัชนีหลายคอลัมน์ เพื่อเร่งการค้นหาได้อย่างเต็มที่ นอกจากนี้ คุณยังใช้ดัชนีในคอลัมน์ภายนอกและอนุญาตให้การค้นหาภายในดำเนินการเป็นการสแกนเชิงเส้นได้ด้วย
ตัวอย่างเช่น หากมีการค้นหาต่อไปนี้
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 KB สำหรับไฟล์ขนาดเล็กมาก ซึ่งข้อผิดพลาดในการปัดเศษมีนัยสำคัญ การจัดเก็บไฟล์เหล่านั้นในฐานข้อมูลเป็น 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 ครั้งเดียวประมาณ 1,000 เท่า
ใช้ 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
}
}
ใช้ฟังก์ชันการรวมข้อมูลเมื่อเป็นไปได้
ใช้ฟังก์ชันการรวมข้อมูลสำหรับผลลัพธ์รวมโดยไม่มีข้อมูลแถว ตัวอย่างเช่น โค้ดต่อไปนี้จะตรวจสอบว่ามีแถวที่ตรงกันอย่างน้อย 1 แถวหรือไม่
// 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 หากมีแถวที่ตรงกันอย่างน้อย 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 Analyzer
SQLite มีอินเทอร์เฟซบรรทัดคำสั่ง (CLI) sqlite3_analyzer เพื่อดัมพ์ข้อมูลเพิ่มเติมที่ใช้แก้ปัญหาด้านประสิทธิภาพได้ หากต้องการติดตั้ง ให้ไปที่ หน้าดาวน์โหลด SQLite
คุณสามารถใช้ adb pull เพื่อดาวน์โหลดไฟล์ฐานข้อมูลจากอุปกรณ์เป้าหมายไปยังเวิร์กสเตชันเพื่อทำการวิเคราะห์ได้ดังนี้
adb pull /data/data/<app_package_name>/databases/<db_name>.db
SQLite Browser
นอกจากนี้ คุณยังติดตั้งเครื่องมือ GUI 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: ขนาดของหน้าฐานข้อมูล 1 หน้าในหน่วย KBdbsz: ขนาดของฐานข้อมูลทั้งหมดในหน่วยหน้า หากต้องการดูขนาดในหน่วย KB ให้คูณpgszด้วยdbszLookaside(b): หน่วยความจำที่จัดสรรให้กับบัฟเฟอร์ Lookaside ของ SQLite ต่อการเชื่อมต่อในหน่วยไบต์ โดยปกติจะมีขนาดเล็กมากcache hits: SQLite จะเก็บแคชของหน้าฐานข้อมูล ซึ่งเป็นจำนวนการเข้าถึงแคชหน้า (จำนวน)cache misses: จำนวนการเข้าถึงแคชหน้าไม่สำเร็จ (จำนวน)cache size: จำนวนหน้าในแคช (จำนวน) หากต้องการดูขนาดในหน่วย KB ให้คูณจำนวนนี้ด้วยpgszDbname: เส้นทางไปยังไฟล์ DB ในตัวอย่างของเรา DB บางรายการมี(1)หรือตัวเลขอื่นต่อท้ายชื่อ เพื่อระบุว่ามีการเชื่อมต่อกับฐานข้อมูลพื้นฐานเดียวกันมากกว่า 1 รายการ ระบบจะติดตามสถิติต่อการเชื่อมต่อ
ในส่วน POOL STATS คุณจะเห็นข้อมูลต่อไปนี้
cache hits: SQLite จะแคชคำสั่งที่เตรียมไว้และพยายามนำคำสั่งเหล่านั้นกลับมาใช้ซ้ำเมื่อเรียกใช้การค้นหา เพื่อประหยัดความพยายามและหน่วยความจำในการคอมไพล์คำสั่ง SQL ซึ่งเป็นจำนวนการเข้าถึงแคชคำสั่ง (จำนวน)cache misses: จำนวนการเข้าถึงแคชคำสั่งไม่สำเร็จ (จำนวน)cache size: ตั้งแต่ Android 17 เป็นต้นไป รายการนี้จะแสดงจำนวนคำสั่งที่เตรียมไว้ทั้งหมดในแคช ในเวอร์ชันก่อนหน้า ค่านี้จะ เท่ากับผลรวมของการเข้าถึงและการเข้าถึงไม่สำเร็จที่แสดงในคอลัมน์อื่นๆ 2 คอลัมน์ และไม่ได้แสดงถึง ขนาดแคช
แหล่งข้อมูลเพิ่มเติม
ดูเนื้อหา
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- เรียกใช้การทดสอบประสิทธิภาพในการผสานรวมอย่างต่อเนื่อง
- เฟรมที่ค้าง
- สร้างและวัดโปรไฟล์พื้นฐานโดยไม่ต้องใช้ Macrobenchmark