Android SQLite veritabanı: yavaş ekleme


92

Kullanmakta olduğum oldukça büyük bir XML dosyasını (yaklaşık yüz kilobayt ile birkaç yüz kilobayt arasında değişen) ayrıştırmam gerekiyor Xml#parse(String, ContentHandler). Şu anda bunu 152KB'lik bir dosya ile test ediyorum.

Ayrıştırma sırasında, ben de aşağıdakine benzer çağrıları kullanan bir SQLite veritabanında veri ekleme: getWritableDatabase().insert(TABLE_NAME, "_id", values). Bunların tümü, 152KB test dosyası için yaklaşık 80 saniye sürer (yaklaşık 200 satır eklemeye kadar iner).

Tüm insert ifadelerini yorumladığımda (ancak diğer her şeyi, örneğin oluşturma ContentValuesvb. İçinde bıraktığımda ) aynı dosya yalnızca 23 saniye sürüyor.

Veritabanı işlemlerinin bu kadar büyük bir ek yükü olması normal mi? Bununla ilgili bir şey yapabilir miyim?

Yanıtlar:


192

Toplu ekler yapmalısınız.

Sözde kod:

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

Bu, uygulamalarımdaki eklerin hızını son derece artırdı.

Güncelleme:
@ Yuku çok ilginç bir blog yazısı sağladı: Android, sqlite veritabanına daha hızlı eklemeler için inserthelper kullanıyor


4
Tüm İçerik Değerlerini bu şekilde eklemek yalnızca bir veya iki saniye sürer. Çok teşekkürler!
benvd

XML ayrıştırma işleminin tüm süresini kapsayan uzun süreli bir işlemin açık olması ve ardından bunu sonunda taahhüt etmesi güvenli midir? Veya eklemeler listesi XML ayrıştırıcı içinde yerel olarak önbelleğe alınmalı ve ardından ayrıştırma tamamlandıktan sonra kısa ömürlü bir işlem açılıp yürütülmeli mi?
Graham Borland

2
60 ekimi bir işlemle sarmak performansı 10 kat artırdı. bunu bir işlemle sarmalamak ve hazırlanmış bir ifade (SQLiteStatement) kullanmak bunu 20 kat artırdı!
stefs

2
benvd yorum için teşekkürler. 20 bin kayıt ekliyordum, yaklaşık 8 dakika sürdü ama bir işlemi kullandıktan sonra sadece 20 saniye sürüyor :-)
ayı

2
Bu blog girişi, neredeyse gizli olan InsertHelper outofwhatbox.com/blog/2010/12/…
Randy Sugianto 'Yuku'

68

Yuku ve Brett tarafından bahsedilen InsertHelper artık kullanımdan kaldırıldığından (API seviyesi 17), Google tarafından önerilen doğru alternatifin SQLiteStatement kullandığı görülüyor .

Veritabanı ekleme yöntemini şu şekilde kullandım:

database.insert(table, null, values);

Bazı ciddi performans sorunları yaşadıktan sonra, aşağıdaki kod 500 eklememi 14,5 saniyeden yalnızca 270 ms'ye kadar hızlandırdı , harika!

İşte SQLiteStatement'ı nasıl kullandım:

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
Burada kaçınılması gereken bir nokta: bindString'deki dizin 1 tabanlı ve 0 tabanlı değil
Görünen ad

@qefzec Bu çözüm için teşekkürler..Bu, uygulamamdaki ekleme süresini 900 satır için 78 saniyeden 4'e
düşürdü

1
Teşekkürler bayım. 4 dakikadan 8 saniyeye kadar VARCHAR (80) dahil olmak üzere her biri 6 veri alanlı 20000 kayıt. Aslında bu en iyi cevap olarak işaretlenmeli, IMHO.
TomeeNS

1
Harika! Cihaza ve dahili / harici belleğe bağlı olarak% 4100 ile% 10400 arasında iyileştirme oluşturarak uç başına 15 sütunla bir seferde 200 test eki üzerinde kıyaslamamız. Önceki performans, projemizi yerden kalkmadan mahvedebilirdi.
Frank

13600 satır için 15
dakikadan

13

Sql insert deyimini derlemek işleri hızlandırmaya yardımcı olur. Ayrıca, artık her şey omuzlarınızda olduğundan, her şeyi desteklemek ve olası enjeksiyonu önlemek için daha fazla çaba gerektirebilir.

İşleri hızlandırabilecek başka bir yaklaşım, eksik belgelenmiş android.database.DatabaseUtils.InsertHelper sınıfıdır. Anladığım kadarıyla, derlenmiş insert ifadelerini aslında sarmalıyor. Derlenmemiş işlemden geçirilmiş eklemelerden derlenmiş işlem görmüş eklere geçiş, büyük (200K + girişler) ancak basit SQLite eklemelerim için hızda yaklaşık 3 kat artış (ekleme başına 2 ms ila .6 ms) oldu.

Basit kod:

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

iHelp.bind'de ContentValue nasıl eklenir (first_index, thing_1); ?
Vasil Valchev

3

Tablonun üzerinde bir dizin varsa, kayıtları eklemeden önce kaldırmayı ve ardından kayıtlarınızı kaydettikten sonra tekrar eklemeyi düşünün.


1

Bir ContentProvider kullanılıyorsa:

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

}

Ardından, eklemeyi gerçekleştirmek için özel işlev (hala içerik sağlayıcınızın içinde):

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


    }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.