Android的SQLite数据库:插入缓慢
我需要parsing一个相当大的XML文件(大约一百千字节到几百千字节之间),我正在使用Xml#parse(String, ContentHandler)
。 我目前正在用一个152KB的文件进行testing。
在parsing过程中,我还使用类似于以下的调用将数据插入到SQLite数据库中: getWritableDatabase().insert(TABLE_NAME, "_id", values)
_ getWritableDatabase().insert(TABLE_NAME, "_id", values)
。 所有这一切大约需要80秒的152KBtesting文件(这可以下来插入大约200行)。
当我注释掉所有的插入语句(但是留下一切,如创buildContentValues
等),同一个文件只需要23秒。
数据库操作有这么大的开销是否正常? 我能做些什么吗?
你应该做批量插入。
伪代码:
db.beginTransaction(); for (entry : listOfEntries) { db.insert(entry); } db.setTransactionSuccessful(); db.endTransaction();
这增加了插入我的应用程序的速度。
更新:
@Yuku提供了一个非常有趣的博客文章: Android使用inserthelper更快地插入到sqlite数据库
由于Yuku和Brett提到的InsertHelper现在已经被弃用 (API级别17),所以Google推荐的正确select似乎是使用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(); }
编译sql插入语句有助于加快速度。 它也可能需要更多的努力来支撑一切,防止可能的注射,因为它现在都在你的肩上。
另一种也可以加快速度的方法是缺lesslogging的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();
如果表上有索引,请考虑在插入logging之前将其删除,然后在提交logging之后将其添加回来。
如果使用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); }