提高SQLite的每秒INSERT性能?

时间:2009-11-10 22:16:43

标签: c performance sqlite optimization

10 个答案:

答案 0 :(得分:723)

一些提示:

  1. 在事务中放置插入/更新。
  2. 对于旧版本的SQLite - 考虑一种不那么偏执的日记模式(pragma journal_mode)。有NORMAL,然后有OFF,如果您不太担心如果操作系统崩溃可能会损坏数据库,则可以显着提高插入速度。如果您的应用程序崩溃,数据应该没问题。请注意,在较新版本中,OFF/MEMORY设置对于应用程序级别崩溃是不安全的。
  3. 使用页面大小也会产生影响(PRAGMA page_size)。具有较大的页面大小可以使读取和写入更快,因为较大的页面保存在内存中。请注意,您的数据库将使用更多内存。
  4. 如果您有索引,请考虑在完成所有插入后调用CREATE INDEX。这比创建索引然后进行插入要快得多。
  5. 如果您同时访问SQLite,则必须非常小心,因为在完成写入时整个数据库都被锁定,尽管可能有多个读取器,但写入将被锁定。通过在较新的SQLite版本中添加WAL,这有所改善。
  6. 利用节省空间......较小的数据库更快。例如,如果您有键值对,请尽可能将键设为INTEGER PRIMARY KEY,这将替换表中隐含的唯一行号列。
  7. 如果您使用多个线程,可以尝试使用shared page cache,这将允许在线程之间共享已加载的页面,这可以避免昂贵的I / O调用。
  8. Don't use !feof(file)!
  9. 我也问了类似的问题herehere

答案 1 :(得分:120)

尝试使用SQLITE_STATIC代替SQLITE_TRANSIENT进行这些插入。

SQLITE_TRANSIENT将导致SQLite在返回之前复制字符串数据。

SQLITE_STATIC告诉它你给它的内存地址在查询执行之前是有效的(在这个循环中总是如此)。这将为每个循环节省几次分配,复制和取消分配操作。可能是一个很大的进步。

答案 2 :(得分:92)

避免使用sqlite3_clear_bindings(stmt);

测试中的代码每次都应该设置绑定就足够了。

SQLite文档中的C API简介说

  

第一次或立即调用sqlite3_step()之前   在sqlite3_reset()之后,应用程序可以调用其中一个   sqlite3_bind()接口将值附加到参数。每   调用sqlite3_bind()会覆盖对同一参数

的先前绑定

(见:sqlite.org/cintro.html)。 that function文档中没有任何内容表示除了简单地设置绑定外,还必须调用它。

更多细节:http://www.hoogli.com/blogs/micro/index.html#Avoid_sqlite3_clear_bindings()

答案 3 :(得分:53)

批量插入

受到这篇文章的启发以及导致我在这里的Stack Overflow问题的启发 - Is it possible to insert multiple rows at a time in an SQLite database? - 我发布了我的第一个Git存储库:

https://github.com/rdpoor/CreateOrUpdate

批量加载ActiveRecords数组到MySQL,SQLite或PostgreSQL数据库。它包括一个忽略现有记录,覆盖它们或引发错误的选项。我的基本基准测试显示,与顺序写入相比,速度提高了10倍 - YMMV。

我在生产代码中使用它,我经常需要导入大型数据集,我对它非常满意。

答案 4 :(得分:45)

如果您可以对 INSERT / UPDATE 语句进行分块,批量导入似乎表现最佳。在一张只有几行YMMV ......

的桌子上,10,000左右的值对我来说效果很好

答案 5 :(得分:37)

如果您只关心阅读,可能会更快(但可能会读取陈旧数据)版本是从多个线程的多个连接中读取(每个线程的连接)。

首先找到表中的项目:

 SELECT COUNT(*) FROM table

然后读入页面(LIMIT / OFFSET)

  SELECT * FROM table ORDER BY _ROWID_ LIMIT <limit> OFFSET <offset>

其中和每个线程计算,如下所示:

int limit = (count + n_threads - 1)/n_threads;
每个帖子

int offset = thread_index * limit

对于我们的小型(200mb)数据库,这使得速度提高了50-75%(Windows 7上为3.8.0.2 64位)。我们的表非常规范化(1000-1500列,大约100,000行或更多行)。

太多或太少的线程都没有赢得,你需要自己进行基准测试和分析。

同样对我们来说,SHAREDCACHE使性能变慢,所以我手动放置PRIVATECACHE(因为它是全局启用的)

答案 6 :(得分:27)

我从事务中获得任何收益,直到我将cache_size提升到更高的值,即PRAGMA cache_size=10000;

答案 7 :(得分:18)

阅读本教程后,我尝试将其实现到我的程序中。

我有4-5个包含地址的文件。每个文件有大约3000万条记录。我使用的是你建议的相同配置,但我每秒的INSERT数量很低(每秒约10.000条记录)。

这是您的建议失败的地方。您对所有记录使用单个事务,并且没有错误/失败的单个插入。让我们假设您将每条记录拆分为不同表上的多个插入。如果记录被破坏会发生什么?

ON CONFLICT命令不适用,因为如果记录中有10个元素,并且需要将每个元素插入到不同的表中,如果元素5出现CONSTRAINT错误,那么之前的所有4个插入也需要。

所以这里是回滚的来源。回滚的唯一问题是您丢失了所有插入并从顶部开始。你怎么解决这个问题?

我的解决方案是使用多个交易。我每隔10.000条记录开始和结束一次交易(不要问为什么这个数字,这是我测试过的最快的数字)。我创建了一个10.000的数组,并在那里插入成功的记录。发生错误时,我会进行回滚,开始事务,从我的数组中插入记录,提交,然后在损坏的记录之后开始新的事务。

此解决方案帮助我绕过了处理包含错误/重复记录的文件时遇到的问题(我的记录差不多有4%)。

我创建的算法帮助我减少了2个小时的过程。文件1小时30米的最终加载过程仍然很慢,但与最初的4小时相比没有。我设法将插入速度从10.000 / s加速到~14.000 / s

如果有人对如何加快速度有任何其他想法,我愿意接受建议。

<强>更新

除了上面的回答之外,您还应该记住,每秒插入次数取决于您使用的硬盘驱动器。我在具有不同硬盘驱动器的3台不同PC上进行了测试,并且在时间上有很大差异。 PC1(1小时30分钟),PC2(6小时)PC3(14小时),所以我开始想知道为什么会这样。

经过两周的研究和检查多个资源:硬盘,Ram,缓存,我发现硬盘上的某些设置会影响I / O速率。通过单击所需输出驱动器上的属性,您可以在常规选项卡中看到两个选项。选项1:压缩此驱动器,选项2:允许此驱动器的文件将内容编入索引。

通过禁用这两个选项,现在所有3台PC都需要大约相同的时间才能完成(1小时和20到40分钟)。如果遇到慢速插入,请检查您的硬盘驱动器是否配置了这些选项。它将为您节省大量时间和头痛,试图找到解决方案

答案 8 :(得分:10)

你的问题的答案是较新的sqlite3提高了性能,使用它。

SqlAlchemy Orm作者的回答Why is SQLAlchemy insert with sqlite 25 times slower than using sqlite3 directly?在0.5秒内有100k次插入,我在python-sqlite和SqlAlchemy中看到了类似的结果。这让我相信使用sqlite3

可以提高性能

答案 9 :(得分:-1)

使用ContentProvider在db中插入批量数据。 以下用于将批量数据插入数据库的方法。这样可以提高SQLite每秒INSERT的性能。

private SQLiteDatabase database;
database = dbHelper.getWritableDatabase();

public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) {

database.beginTransaction();

for (ContentValues value : values)
 db.insert("TABLE_NAME", null, value);

database.setTransactionSuccessful();
database.endTransaction();

}

调用bulkInsert方法:

App.getAppContext().getContentResolver().bulkInsert(contentUriTable,
            contentValuesArray);

链接:https://www.vogella.com/tutorials/AndroidSQLite/article.html 检查“使用ContentProvider”部分以获取更多详细信息