Android SQLite 数据库:插入速度慢
Posted
技术标签:
【中文标题】Android SQLite 数据库:插入速度慢【英文标题】:Android SQLite database: slow insertion 【发布时间】:2011-03-30 21:55:39 【问题描述】:我需要使用Xml#parse(String, ContentHandler)
解析一个相当大的XML 文件(大约在100 KB 到几百KB 之间变化)。我目前正在用一个 152KB 的文件对此进行测试。
在解析过程中,我还使用类似于以下内容的调用将数据插入 SQLite 数据库:getWritableDatabase().insert(TABLE_NAME, "_id", values)
。对于 152KB 的测试文件,所有这些加起来大约需要 80 秒(归结为插入大约 200 行)。
当我注释掉所有插入语句(但保留其他所有内容,例如创建 ContentValues
等)时,同一个文件只需要 23 秒。
数据库操作有这么大的开销是正常的吗?我能做点什么吗?
【问题讨论】:
【参考方案1】:你应该做批量插入。
伪代码:
db.beginTransaction();
for (entry : listOfEntries)
db.insert(entry);
db.setTransactionSuccessful();
db.endTransaction();
这极大地提高了我的应用程序中的插入速度。
更新: @Yuku 提供了一篇非常有趣的博文:android using inserthelper for faster insertions into sqlite database
【讨论】:
以这种方式插入所有 ContentValues 只需要一两秒钟。非常感谢! 打开一个长时间运行的事务是否安全,覆盖 XML 解析操作的整个持续时间,然后在最后提交它?还是应该在 XML 解析器中本地缓存插入列表,然后在解析完成后打开并提交一个短暂的事务? 用事务包装我的 60 个插入使性能提高了 10 倍。用事务包装它并使用准备好的语句(SQLiteStatement)将它增加了 20 倍! benvd 感谢您的评论。我插入了 20k 条记录,大约需要 8 分钟,但使用事务后只需要 20 秒 :-) 这篇博文讨论了另一个使用几乎隐藏的 InsertHelper outofwhatbox.com/blog/2010/12/… 的优化【参考方案2】:由于 Yuku 和 Brett 提到的 InsertHelper 现在是 deprecated(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();
【讨论】:
这里要避免的一个问题:bindString 中的索引是基于 1 而不是基于 0 @qefzec 感谢您的解决方案。这只是将我的应用程序中的插入时间从 78 秒降至 4 秒,添加了 900 行。 谢谢先生。 20000 条记录,每条记录有 6 个数据字段,包括 VARCHAR(80) 从 4 分钟到 8 秒。实际上,这应该被标记为最佳答案,恕我直言。 太棒了!我们一次对 200 个测试插入进行基准测试,每个插入 15 列,根据设备和内部/外部存储器的不同,产生了 4100% 到 10400% 的改进。之前的表现会在我们的项目开始之前就注定要失败。 13600 行从 15 分钟缩短到 45 秒【参考方案3】:编译 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; ?【参考方案4】:如果表上有索引,请考虑在插入记录之前将其删除,然后在提交记录后将其添加回来。
【讨论】:
【参考方案5】:如果使用 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);
【讨论】:
以上是关于Android SQLite 数据库:插入速度慢的主要内容,如果未能解决你的问题,请参考以下文章
Python语言 SQLite怎么用内存数据库解决插入数据时速度慢的问题?
如何将 SQLite 数据库(SQLite Firefox 扩展)连接到 Android 应用程序,以便 android 2.1 查询并插入数据库