安卓SQLite数据库:插入速度缓慢

93

我需要解析一个相当大的XML文件(大小在几百千字节之间),我使用Xml#parse(String, ContentHandler)进行解析。目前我正在测试一个152KB的文件。

在解析期间,我还使用类似以下代码的语句将数据插入SQLite数据库:getWritableDatabase().insert(TABLE_NAME, "_id", values)。所有这些操作加起来大约需要80秒来处理152KB的测试文件(大约插入了200行)。

当我注释掉所有插入语句(但保留其他所有内容,如创建ContentValues等)时,同样的文件只需23秒。

数据库操作有这么大的开销正常吗?我能做些什么来改善它吗?

5个回答

193
你应该进行批量插入。
伪代码:
db.beginTransaction();
for (entry : listOfEntries) {
    db.insert(entry);
}
db.setTransactionSuccessful();
db.endTransaction();

那极大地提高了我的应用程序中插入操作的速度。

更新:
@Yuku 提供了一篇非常有趣的博客文章:Android 使用 InsertHelper 更快地向 SQLite 数据库插入数据


4
用这种方式插入所有ContentValues只需要一两秒钟。非常感谢! - benvd
2
将我的60个插入操作用事务包装起来,性能提高了10倍。将其用事务包装并使用预处理语句(SQLiteStatement),性能提高了20倍! - stefs
2
benvd,感谢您的评论。我正在插入20k条记录,需要大约8分钟,但是使用事务后只需要20秒:-) - Bear
2
本博客文章讨论了另一种优化方法,使用几乎隐藏的InsertHelper http://www.outofwhatbox.com/blog/2010/12/android-using-databaseutils-inserthelper-for-faster-insertions-into-sqlite-database/。 - Randy Sugianto 'Yuku'
存储2790条记录需要24307毫秒,使用此方法只需1071毫秒。谢谢:D - Mohamd Ali
显示剩余7条评论

72

由于Yuku和Brett提到的InsertHelper现在已经弃用 (API级别为17),因此谷歌推荐的正确替代方法似乎是使用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();
}

14
这里需要注意一个问题:bindString中的索引是基于1而不是0的。 - Display name
@qefzec 感谢您提供的解决方案。这使得我应用程序中插入时间从78秒降至4秒,对于添加了900行数据。 - Daniel Bejan
1
感谢您先生。20000条记录,每条记录包含6个数据字段,其中包括VARCHAR(80),从4分钟缩短到8秒。实际上,我认为这应该被标记为最佳答案。 - TomeeNS
1
太好了!我们在每次进行200个测试插入时,每个插入有15列,并且根据设备和内/外部存储生成了4100%至10400%的性能改进。如果之前的性能没有得到改善,我们的项目就注定要失败了。 - Frank
从15分钟缩短到45秒,处理13600行。 - DoruChidean
显示剩余2条评论

14

编译SQL插入语句可以加快速度。但是,它也需要更多的工作来确保安全,并防止可能的注入攻击,因为这一切现在都由您来承担。

另一种可以提高速度的方法是使用文档不足的android.database.DatabaseUtils.InsertHelper类。我的理解是,它实际上包装了已编译的插入语句。将未编译的事务插入转换为已编译的事务插入后,对于我大型(200K+条目)但简单的SQLite插入,速度提高了约3倍(每个插入从2ms到.6ms)。

示例代码:

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(first_index, thing_1); 中添加 ContentValue? - Vasil Valchev

3
如果表上有索引,请考虑在插入记录之前删除它,然后在提交记录后再添加回来。

2
如果使用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);


    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接