2010-08-17 177 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