2010-08-17 235 views
83

我需要解析一個相當大的XML文件(大約在一百千字節和幾百千字節之間變化),我使用的是Xml#parse(String, ContentHandler)。我目前正在用一個152KB的文件進行測試。Android SQLite數據庫:緩慢插入

在解析過程中,我還使用類似於以下的調用將數據插入到SQLite數據庫中:getWritableDatabase().insert(TABLE_NAME, "_id", values)。所有這些在一起大約需要80秒,用於152KB的測試文件(可以插入大約200行)。

當我註釋掉所有插入語句(但是在其他所有內容中,例如創建ContentValues等)時,同一個文件只需要23秒。

數據庫操作有這麼大的開銷是否正常?我能做些什麼嗎?

回答

179

你應該做批量插入。

僞代碼:

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

這極大增加插入的速度在我的應用程序。

更新:
@Yuku提供了一個非常有趣的博客文章:Android using inserthelper for faster insertions into sqlite database

+4

插入這樣所有ContentValues只需要一兩秒鐘。非常感謝! – benvd 2010-08-17 13:00:10

+0

打開一個長時間運行的事務是否安全,覆蓋整個XML解析操作的持續時間,然後在最後提交它?或者應該將插入列表本地緩存到XML解析器中,然後在解析完成後打開並提交一個短期事務? – 2011-01-24 11:43:04

+2

用交易包裝我的60插入增加了10倍的性能。用一個事務包裝它,並使用準備好的語句(SQLiteStatement)將其增加20倍! – stefs 2011-04-07 13:57:53

3

如果表上有一個指標,考慮之前,插入記錄,然後將它放回放棄它你COMMITED後您的記錄。

12

編譯sql insert語句有助於加快速度。它還需要更多的努力來支撐一切,並防止可能的注射,因爲它現在全部放在你的肩上。

另一種可以加快速度的方法是缺少記錄的android.database.DatabaseUtils.InsertHelper類。我的理解是它實際上包裝了編譯的插入語句。從非編譯的事務性插入到編譯的事務性插入對於我的大型(200K +條目),但是簡單的SQLite插入,速度增加了3倍(每插入2毫秒,每插入0.6毫秒)。

示例代碼:

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

的速度以及如何在iHelp.bind(first_index,thing_1)中添加ContentValue; ? – 2015-04-08 18:16:57

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


    } 
61

由於Yuku和Brett提到的InsertHelper現在是deprecated(API等級17),似乎r Google推薦使用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(); 
} 
+13

在這裏要避免的一個問題:bindString中的索引是基於1的,而不是0基於 – 2014-07-09 11:59:03

+0

@qefzec感謝您的解決方案..這只是將插入時間從78秒減少到了4個900行添加了.. – csanonymus 2014-08-14 10:14:47

+1

謝謝你,先生。 20000個記錄,每個數據包含6個字段,其中VARCHAR(80)從4分鐘到8秒。其實這應該被標記爲最佳答案,恕我直言。 – TomeeNS 2015-11-19 04:05:33