[译]榨取SQLite性能:插入
原文链接:https://medium.com/@JasonWyatt/squeezing-performance-from-sqlite-insertions-971aff98eef2 有时我们需要在应用程序中管理大量的数据。将所有数据导入Android应用程序的SQLite数据库中最快的方式是什么? 在这篇文章中,我将调查向SQLite数据库中插入大量数据的可行方法,评估每种方法的性能,并发现一些最佳实现。 测试方法每个实验涉及两种或多种将1000,10000和100000个随机生成的记录插入到两种不同类型的表中方法的比较:
我在16GB Nexus 5X上执行所有测试,运行API为25级(牛轧糖)。 通过跟踪当前迭代的所有插入运行时所使用的时间来计算结果。每次迭代时不包含连接到数据库和擦除表的时间。 可以从GitHub下载源代码,并自行构建/运行测试应用程序,如果你需要! 勘探db.insert()带或不带显式事务Android的SQLite绑定的官方培训材料提供了使用
ContentValues values = new ContentValues(1);
for (int i = 0; i < numIterations; i++) {
values.put('val',random.nextInt());
db.insert("inserts_1",null,values);
}
我认为可以通过在事务中执行 db.beginTransaction();
ContentValues values = new ContentValues(1);
for (int i = 0; i < numIterations; i++) {
values.put('val',values);
}
db.setTransactionSuccessful();
db.endTransaction();
看一下图表,很明显,与不用事务包装比,将一堆 但为什么这么快呢? 事实证明,除非显式的在
重要的是要注意,一旦事务提交后,SQLite才会将插入数据写入磁盘。因此,如果可以最小化事务数量(无论是显式还是隐式启动),都将最大限度地减少磁盘访问并最大限度地提高性能。将图表中的数据转换为每秒插入的记录数:
只需3行代码,带来了10倍提升! 现在我们知道使用事务是一个巨大的优势,从这里开始当我们用其他方式插入数据时,我们都将使用事务。 db.execSQL()SQLiteDatabase暴露的另一个将数据插入到表的方法是 db.beginTransaction();
Object[] values = new Object[1];
for (int i = 0; i < numIterations; i++) {
values[0] = random.nextInt();
db.execSQL("INSERT INTO inserts_1 (val) VALUES (?)",values);
}
db.setTransactionSuccessful();
db.endTransaction();
在本实验中,通过使用
如果你考虑它,这是完美的; 使用db.execSQL()进行批量插入我们使用原始语句和 我最初认为,因为SQLite是一个进程内数据库引擎,我们不一定会通过批量插入来保存任何东西(与数据库服务器不同,每个语句会引起网络延迟)。 我第一次尝试将所有插入放人一个批量操作: db.beginTransaction();
Object[] values = new Object[numIterations];
StringBuilder valuesBuilder = new StringBuilder();
for (int i = 0; i < numIterations; i++) {
if (i != 0) {
valuesBuilder.append(",");
}
values[i] = mRandom.nextInt();
valuesBuilder.append("(?)");
}
db.execSQL(
"INSERT INTO inserts_1 (val) VALUES "+valuesBuilder.toString(),values
);
db.setTransactionSuccessful();
db.endTransaction();
太多的SQL变量 事实证明,在SQLite源代码中,它们对准备语句中允许的变量数量设置了硬编码限制。 从sqlite3.c: /* ** The maximum value of a ?nnn wildcard that the parser will accept. */
#ifndef SQLITE_MAX_VARIABLE_NUMBER
# define SQLITE_MAX_VARIABLE_NUMBER 999
#endif
如果你不介意狡猾点,用这个方法来解决这个问题并不难: db.beginTransaction();
doInsertions(db,numIterations);
db.setTransactionSuccessful();
db.endTransaction();
// ... elsewhere in the class ...
void doInsertions(SQLiteDatabase db,int numInsertions) {
if (total > 999) {
doInsertions(db,numInsertions - 999);
numInsertions = 999;
}
Object[] values = new Object[numInsertions];
StringBuilder valuesBuilder = new StringBuilder();
for (int i = 0; i < numInsertions; i++) {
if (i != 0) {
valuesBuilder.append(",");
}
values[i] = mRandom.nextInt();
valuesBuilder.append("(?)");
}
db.execSQL(
"INSERT INTO inserts_1 (val) VALUES "
+valuesBuilder.toString(),values
);
}
使用递归让它超级容易,但如果这不是你的风格或不适合你的情况,循环也很酷。我猜。 哇!事实证明,即使我们不通过批量插入来节省任何网络延迟,也不需要做这么多单独的语句,就获得了一些额外的性能提升。 然而,需要注意的一点是:表的列数越多,插入的批处理操作就越少。这是因为每个语句可以插入的记录数等于 再次运行吞吐量数据:
直接使用SQLiteStatment在我发布了这篇博客的第一稿之后,我发现了#AndroidChat的一个朋友,他还指出了一个我应该运行的实验: 不使用 这个想法与从 另外,如果可以重复使用 我使用
SQLiteStatement stmt = db.compileStatement(
"INSERT INTO inserts_1 (val) VALUES (?)"
);
db.beginTransaction();
for (int i = 0; i < numIterations; i++) {
stmt.bindLong(1,random.nextInt());
stmt.executeInsert();
stmt.clearBindings();
}
db.setTransactionSuccessful();
db.endTransaction();
批处理插入的代码,使用递归技巧,从之前存储在基于HashMap的大小索引缓存中的语句: Map<Integer,SQLiteStatement> statementCache = new HashMap<>();
db.beginTransaction();
doInsertions(db,numInsertions,statementCache);
db.setTransactionSuccessful();
db.endTransaction();
// ... elsewhere in the class ...
void doInsertions(SQLiteDatabase db,int numInsertions,Map<Integer,SQLiteStatement> statementCache) {
if (numInsertions > 999) {
doInsertions(db,numInsertions - 999,statementCache);
total = 999;
}
SQLiteStatement stmt;
if (statementCache.containsKey(numInsertions)) {
stmt = statementCache.get(numInsertions);
} else {
StringBuilder valuesBuilder = new StringBuilder();
for (int i = 0; i < numInsertions; i++) {
if (i != 0) {
valuesBuilder.append(",");
}
valuesBuilder.append("(?)");
}
stmt = db.compileStatement(
"INSERT INTO inserts_1 (val) VALUES "
+ valuesBuilder.toString()
);
statementCache.put(numInsertions,stmt);
}
for (int i = 0; i < numInsertions; i++) {
stmt.bindLong(i+1,random.nextInt());
}
stmt.executeInsert();
stmt.clearBindings();
}
从图表中可以清楚的看出,重用 结论从这篇文章中脱颖而出的第一件最重要的事情是将插入操作明确地封装在一个事务中,使得性能得到巨大而明确的改进。 之后,您可以通过使用 最后,如果确实希望在插入数据时最大限度地压榨SQLite:请考虑直接重 延伸阅读:压榨SQLite性能:解释虚拟数据库引擎 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |