Database Android SQLite: penyisipan lambat

92

Saya perlu mengurai file XML yang cukup besar (bervariasi antara sekitar seratus kilobyte dan beberapa ratus kilobyte), yang sedang saya gunakan Xml#parse(String, ContentHandler). Saya sedang menguji ini dengan file 152KB.

Selama parsing, saya juga memasukkan data dalam database SQLite menggunakan panggilan mirip dengan berikut ini: getWritableDatabase().insert(TABLE_NAME, "_id", values). Semua ini bersama-sama membutuhkan waktu sekitar 80 detik untuk file pengujian 152KB (yang berarti menyisipkan sekitar 200 baris).

Ketika saya mengomentari semua pernyataan sisipan (tetapi tinggalkan yang lainnya, seperti membuat ContentValuesdll.), File yang sama hanya membutuhkan 23 detik.

Apakah normal untuk operasi database memiliki overhead sebesar itu? Bisakah saya melakukan sesuatu tentang itu?

benvd
sumber

Jawaban:

192

Anda harus melakukan penyisipan batch.

Pseudocode:

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

Itu sangat meningkatkan kecepatan penyisipan di aplikasi saya.

Pembaruan:
@Yuku memberikan posting blog yang sangat menarik: Android menggunakan inserthelper untuk penyisipan yang lebih cepat ke dalam database sqlite

WarrenFaith
sumber
4
Memasukkan semua ContentValues ​​dengan cara ini hanya membutuhkan satu atau dua detik. Terima kasih banyak!
benvd
Apakah aman untuk membuka transaksi yang berjalan lama, yang mencakup seluruh durasi operasi penguraian XML, dan kemudian melakukannya di akhir? Atau haruskah daftar penyisipan disimpan dalam cache secara lokal di dalam parser XML dan kemudian memiliki transaksi berumur pendek yang dibuka dan dilakukan setelah penguraian selesai?
Graham Borland
2
membungkus 60 sisipan saya dengan transaksi meningkatkan kinerja 10x. membungkusnya dengan transaksi dan menggunakan pernyataan yang disiapkan (SQLiteStatement) meningkatkannya 20x!
Stefs
2
benvd terima kasih atas komentarnya. Saya memasukkan 20 ribu catatan, butuh sekitar 8 menit tetapi setelah menggunakan transaksi hanya butuh 20 detik :-)
Beruang
2
Entri blog ini membahas pengoptimalan lain menggunakan outofwhatbox.com/blog/2010/12/… InsertHelper yang
Randy Sugianto 'Yuku'
68

Karena InsertHelper yang disebutkan oleh Yuku dan Brett sekarang sudah tidak digunakan lagi (API level 17), tampaknya alternatif yang tepat yang direkomendasikan oleh Google adalah menggunakan SQLiteStatement .

Saya menggunakan metode penyisipan database seperti ini:

database.insert(table, null, values);

Setelah saya juga mengalami beberapa masalah kinerja yang serius, kode berikut mempercepat 500 penyisipan saya dari 14,5 detik menjadi hanya 270 md , luar biasa!

Berikut adalah cara saya menggunakan 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();
}
qefzec.dll
sumber
14
Satu tangkapan yang harus dihindari di sini: indeks dalam bindString adalah berbasis 1 dan bukan berbasis 0
Nama tampilan
@qefzec Terima kasih atas solusi ini..Ini baru saja menurunkan waktu penyisipan di aplikasi saya dari 78 detik menjadi 4 untuk 900 baris ditambahkan ..
csanonymus
1
Terima kasih Pak. 20000 catatan dengan masing-masing 6 bidang data, termasuk VARCHAR (80) dari 4 menit hingga 8 detik. Sebenarnya ini harus ditandai sebagai jawaban terbaik, IMHO.
TomeeNS
1
Bagus! Tolok ukur kami pada 200 sisipan uji sekaligus dengan 15 kolom per sisipan menghasilkan peningkatan antara 4100% dan 10400% tergantung pada perangkat dan memori internal / eksternal. Kinerja sebelumnya akan menghancurkan proyek kami sebelum diluncurkan.
Frank
dari 15 menit hingga 45
detik
13

Mengompilasi pernyataan insert sql membantu mempercepat pekerjaan. Ini juga membutuhkan lebih banyak usaha untuk menopang semuanya dan mencegah kemungkinan injeksi karena sekarang semuanya ada di pundak Anda.

Pendekatan lain yang juga dapat mempercepat adalah kelas android.database.DatabaseUtils.InsertHelper yang kurang terdokumentasi. Pemahaman saya adalah bahwa itu sebenarnya membungkus pernyataan sisipan yang dikompilasi. Beralih dari sisipan yang ditransaksikan yang tidak dikompilasi ke sisipan yang ditransaksikan terkompilasi adalah tentang peningkatan kecepatan 3x (2ms per sisipan hingga .6ms per sisipan) untuk sisipan SQLite saya yang besar (200K +) tetapi sederhana.

Kode sampel:

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();
Brett
sumber
dan cara menambahkan ContentValue di iHelp.bind (first_index, thing_1); ?
Vasil Valchev
3

Jika tabel memiliki indeks di atasnya, pertimbangkan untuk melepaskannya sebelum memasukkan catatan dan kemudian menambahkannya kembali setelah Anda memasukkan catatan Anda.

kaD'argo
sumber
1

Jika menggunakan 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;

}

Kemudian fungsi privat untuk melakukan penyisipan (masih di dalam penyedia konten Anda):

       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);


    }
CircuitBreaker716
sumber