Android SQLite数据库:插入缓慢


91

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

在解析期间,我还使用类似于以下内容的调用将数据插入SQLite数据库中getWritableDatabase().insert(TABLE_NAME, "_id", values)。对于152KB的测试文件,所有这些总共花费了80秒左右的时间(大约可插入200行)。

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

数据库操作有这么大的开销是否正常?我能做些什么吗?

Answers:


190

您应该进行批量插入。

伪代码:

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

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

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


4
以这种方式插入所有ContentValues只需要一两秒钟。非常感谢!
benvd 2010年

打开一个长时间运行的事务(覆盖XML解析操作的整个持续时间),然后在最后提交该事务是否安全?还是应该将插入列表本地缓存在XML解析器中,然后在解析完成后打开并提交一个短暂的事务?
Graham Borland

2
用事务处理包裹我的60个插入内容可将性能提高10倍。用事务包装它,并使用准备好的语句(SQLiteStatement)将它增加了20倍!
stefs 2011年

2
benvd感谢您的评论。我插入了2万条记录,耗时约8分钟,但使用事务处理后仅需20秒:-)

2
此博客文章讨论了使用几乎隐藏的InsertHelperoutofwhatbox.com/blog/2010/12/…进行的另一项优化
Randy Sugianto'Yuku'

68

由于Yuku和Brett提到的InsertHelper 弃用(API级别17),因此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();
}

14
这里需要避免的一个问题:bindString中的索引基于1而不是基于0
显示名称

@qefzec感谢你为这个solution..This刚刚落下插入时间在我的应用程序78秒至4加入900行..
csanonymus

1
谢谢你,先生。20000条记录,每条记录具有6个数据字段,其中包括4分钟到8秒之间的VARCHAR(80)。实际上,应将此标记为最佳答案,恕我直言。
TomeeNS 2015年

1
大!我们一次测试200个测试插入物的基准,每个插入物有15列,根据设备和内部/外部存储器的不同,可提高4100%至10400%。先前的性能会在我们的项目启动之前注定要失败。
弗兰克

从15分钟下降到45s(共13600行)
DoruChidean '16

13

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

以及如何在iHelp.bind(first_index,Thing_1)中添加ContentValue;?
Vasil Valchev

3

如果表上有索引,请考虑在插入记录之前将其删除,然后在提交记录后将其重新添加。


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函数执行插入操作(仍在您的内容提供程序内部):

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


    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.