ฐานข้อมูล Android SQLite: การแทรกช้า


92

ฉันต้องการแยกวิเคราะห์ไฟล์ XML ที่ค่อนข้างใหญ่ (แตกต่างกันไปประมาณหนึ่งร้อยกิโลไบต์และหลายร้อยกิโลไบต์) ซึ่งฉันใช้Xml#parse(String, ContentHandler)อยู่ ฉันกำลังทดสอบกับไฟล์ 152KB

ในระหว่างการแยกฉันยังใส่ข้อมูลในฐานข้อมูล SQLite getWritableDatabase().insert(TABLE_NAME, "_id", values)ใช้สายคล้ายกับต่อไปนี้: ทั้งหมดนี้ใช้เวลาประมาณ 80 วินาทีสำหรับไฟล์ทดสอบ 152KB (ซึ่งมาพร้อมกับการแทรกแถวประมาณ 200 แถว)

เมื่อฉันแสดงความคิดเห็นในคำสั่งแทรกทั้งหมด (แต่ปล่อยไว้อย่างอื่นเช่นสร้างContentValuesเป็นต้น) ไฟล์เดียวกันจะใช้เวลาเพียง 23 วินาที

เป็นเรื่องปกติหรือไม่ที่การดำเนินการฐานข้อมูลจะมีค่าใช้จ่ายสูงขนาดนี้? ฉันสามารถทำอะไรเกี่ยวกับเรื่องนั้นได้หรือไม่?

คำตอบ:


192

คุณควรทำการแทรกแบทช์

รหัสเทียม:

db.beginTransaction();
for (entry : listOfEntries) {
    db.insert(entry);
}
db.setTransactionSuccessful();
db.endTransaction();

นั่นเพิ่มความเร็วในการแทรกในแอพของฉันอย่างมาก

อัปเดต:
@Yuku ให้บล็อกโพสต์ที่น่าสนใจมาก: Android ใช้ตัวช่วยแทรกเพื่อการแทรกที่เร็วขึ้นในฐานข้อมูล sqlite


4
การแทรก ContentValues ​​ทั้งหมดด้วยวิธีนี้ใช้เวลาเพียงหนึ่งหรือสองวินาที ขอบคุณมาก!
benvd

ปลอดภัยหรือไม่ที่จะเปิดธุรกรรมที่รันเป็นเวลานานโดยครอบคลุมระยะเวลาทั้งหมดของการดำเนินการแยกวิเคราะห์ XML จากนั้นจึงดำเนินการในตอนท้าย หรือรายการแทรกควรถูกแคชไว้ภายในตัวแยกวิเคราะห์ XML จากนั้นจึงเปิดธุรกรรมที่มีอายุสั้นและตกลงเมื่อการแยกวิเคราะห์เสร็จสมบูรณ์แล้ว?
Graham Borland

2
การห่อเม็ดมีด 60 เม็ดของฉันด้วยธุรกรรมช่วยเพิ่มประสิทธิภาพ 10x ห่อด้วยธุรกรรมและใช้คำสั่งที่เตรียมไว้ (SQLiteStatement) เพิ่มขึ้น 20 เท่า!
stefs

2
Benvd ขอบคุณสำหรับความคิดเห็น ฉันใส่บันทึก 20k ใช้เวลาประมาณ 8 นาที แต่หลังจากใช้ธุรกรรมใช้เวลาเพียง 20 วินาที :-)
หมี

2
รายการบล็อกนี้กล่าวถึงการเพิ่มประสิทธิภาพอื่นโดยใช้ InsertHelper ที่ซ่อนไว้เกือบทั้งหมดoutofwhatbox.com/blog/2010/12/…
Randy Sugianto 'Yuku'

68

เนื่องจาก InsertHelper ที่กล่าวถึงโดย Yuku และ Brett เลิกใช้งานแล้วในขณะนี้ (API ระดับ 17) ดูเหมือนว่าทางเลือกที่เหมาะสมที่แนะนำโดย Google จะใช้SQLiteStatement SQLiteStatement

ฉันใช้วิธีการแทรกฐานข้อมูลดังนี้:

database.insert(table, null, values);

หลังจากที่ฉันประสบปัญหาด้านประสิทธิภาพที่ร้ายแรงรหัสต่อไปนี้ทำให้เม็ดมีด 500 ของฉันเร็วขึ้นจาก14.5 วินาทีเหลือเพียง270 มิลลิวินาทีน่าทึ่งมาก!

นี่คือวิธีที่ฉันใช้ SQLiteStatement:

private void insertTestData() {
    String sql = "insert into producttable (name, description, price, stock_available) values (?, ?, ?, ?);";

    dbHandler.getWritableDatabase();
    database.beginTransaction();
    SQLiteStatement stmt = database.compileStatement(sql);

    for (int i = 0; i < NUMBER_OF_ROWS; i++) {
        //generate some values

        stmt.bindString(1, randomName);
        stmt.bindString(2, randomDescription);
        stmt.bindDouble(3, randomPrice);
        stmt.bindLong(4, randomNumber);

        long entryID = stmt.executeInsert();
        stmt.clearBindings();
    }

    database.setTransactionSuccessful();
    database.endTransaction();

    dbHandler.close();
}

14
สิ่งที่ควรหลีกเลี่ยงที่นี่: ดัชนีใน bindString เป็นแบบ 1 และไม่อิงตาม 0
ชื่อที่แสดง

@qefzec ขอบคุณสำหรับการแก้ปัญหานี้.. เพียงแค่ลดเวลาการแทรกในแอปของฉันจาก 78 วินาทีเหลือ 4 สำหรับ 900 แถวที่เพิ่ม ..
csanonymus

1
ขอบคุณครับ. 20000 บันทึกโดยมีข้อมูล 6 ฟิลด์แต่ละฟิลด์รวมถึง VARCHAR (80) จาก 4 นาทีถึง 8 วินาที อันที่จริงควรระบุว่าเป็นคำตอบที่ดีที่สุด IMHO
TomeeNS

1
เยี่ยมมาก! เกณฑ์มาตรฐานของเราเกี่ยวกับเม็ดมีดทดสอบ 200 ชิ้นในแต่ละครั้งโดยมี 15 คอลัมน์ต่อเม็ดมีดสร้างขึ้นระหว่างการปรับปรุง 4100% ถึง 10400% ขึ้นอยู่กับอุปกรณ์และหน่วยความจำภายใน / ภายนอก การแสดงก่อนหน้านี้จะทำให้โครงการของเราถึงวาระก่อนที่มันจะตกลงจากพื้น
Frank

จาก 15 นาทีลงไป 45 วินาทีสำหรับ 13600 แถว
DoruChidean

13

การรวบรวมคำสั่งแทรก sql ช่วยให้สิ่งต่างๆเร็วขึ้น นอกจากนี้ยังอาจต้องใช้ความพยายามมากขึ้นในการขึ้นฝั่งและป้องกันการฉีดยาเนื่องจากตอนนี้ทั้งหมดอยู่บนไหล่ของคุณ

อีกแนวทางหนึ่งที่สามารถเร่งความเร็วได้เช่นกันคือคลาส android.database.DatabaseUtils.InsertHelper ที่อยู่ภายใต้เอกสาร ความเข้าใจของฉันคือมันรวมประโยคแทรกที่คอมไพล์แล้ว การเปลี่ยนจากเม็ดมีดทรานแซคชันที่ไม่ได้คอมไพล์ไปเป็นเม็ดมีดทรานแซคชันที่คอมไพล์แล้วจะได้รับความเร็วเพิ่มขึ้นประมาณ 3 เท่า (2ms ต่อการแทรกเป็น. 6 มิลลิวินาทีต่อการแทรก) สำหรับรายการขนาดใหญ่ (200K +) ของฉัน แต่การแทรก SQLite แบบธรรมดา

โค้ดตัวอย่าง:

SQLiteDatabse db = getWriteableDatabase();

//use the db you would normally use for db.insert, and the "table_name"
//is the same one you would use in db.insert()
InsertHelper iHelp = new InsertHelper(db, "table_name");

//Get the indices you need to bind data to
//Similar to Cursor.getColumnIndex("col_name");                 
int first_index = iHelp.getColumnIndex("first");
int last_index = iHelp.getColumnIndex("last");

try
{
   db.beginTransaction();
   for(int i=0 ; i<num_things ; ++i)
   {
       //need to tell the helper you are inserting (rather than replacing)
       iHelp.prepareForInsert();

       //do the equivalent of ContentValues.put("field","value") here
       iHelp.bind(first_index, thing_1);
       iHelp.bind(last_index, thing_2);

       //the db.insert() equilvalent
       iHelp.execute();
   }
   db.setTransactionSuccessful();
}
finally
{
    db.endTransaction();
}
db.close();

และวิธีเพิ่ม ContentValue ใน iHelp.bind (first_index, thing_1); เหรอ?
Vasil Valchev

3

หากตารางมีดัชนีอยู่ให้ลองวางก่อนที่จะแทรกระเบียนแล้วเพิ่มกลับเข้าไปหลังจากที่คุณได้ยืนยันระเบียนของคุณแล้ว


1

หากใช้ ContentProvider:

@Override
public int bulkInsert(Uri uri, ContentValues[] bulkinsertvalues) {

    int QueryType = sUriMatcher.match(uri);
    int returnValue=0;
    SQLiteDatabase db = mOpenHelper.getWritableDatabase();

     switch (QueryType) {

         case SOME_URI_IM_LOOKING_FOR: //replace this with your real URI

            db.beginTransaction();

            for (int i = 0; i < bulkinsertvalues.length; i++) {
                //get an individual result from the array of ContentValues
                ContentValues values = bulkinsertvalues[i];
                //insert this record into the local SQLite database using a private function you create, "insertIndividualRecord" (replace with a better function name)
                insertIndividualRecord(uri, values);    
            }

            db.setTransactionSuccessful();
            db.endTransaction();                 

            break;  

         default:
             throw new IllegalArgumentException("Unknown URI " + uri);

     }    

    return returnValue;

}

จากนั้นฟังก์ชันส่วนตัวเพื่อทำการแทรก (ยังอยู่ในผู้ให้บริการเนื้อหาของคุณ):

       private Uri insertIndividualRecord(Uri uri, ContentValues values){

            //see content provider documentation if this is confusing
            if (sUriMatcher.match(uri) != THE_CONSTANT_IM_LOOKING_FOR) {
                throw new IllegalArgumentException("Unknown URI " + uri);
            }

            //example validation if you have a field called "name" in your database
            if (values.containsKey(YOUR_CONSTANT_FOR_NAME) == false) {
                values.put(YOUR_CONSTANT_FOR_NAME, "");
            }

            //******add all your other validations

            //**********

           //time to insert records into your local SQLite database
           SQLiteDatabase db = mOpenHelper.getWritableDatabase();
           long rowId = db.insert(YOUR_TABLE_NAME, null, values);           

           if (rowId > 0) {
               Uri myUri = ContentUris.withAppendedId(MY_INSERT_URI, rowId);
               getContext().getContentResolver().notifyChange(myUri, null);

               return myUri;
           }


           throw new SQLException("Failed to insert row into " + uri);


    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.