在 sqlite3 中更快的批量插入?

Posted

技术标签:

【中文标题】在 sqlite3 中更快的批量插入?【英文标题】:Faster bulk inserts in sqlite3? 【发布时间】:2010-09-26 17:08:15 【问题描述】:

我有一个包含大约 30000 行数据的文件,我想加载到 sqlite3 数据库中。有没有比为每行数据生成插入语句更快的方法?

数据以空格分隔并直接映射到 sqlite3 表。是否有任何类型的批量插入方法可以将卷数据添加到数据库?

如果不是内置的,有没有人设计了一些非常棒的方法来做到这一点?

我应该先问一下,是否有 C++ 方法可以从 API 中做到这一点?

【问题讨论】:

非常相关:***.com/questions/1711631/… 【参考方案1】: 将所有 INSERT 包装在一个事务中,即使只有一个用户,它也快得多。 使用准备好的语句。

【讨论】:

适用于大多数(所有?)SQL 数据库。 PRAGMA journal_mode = MEMORY;可能对某些人有帮助 ***.com/questions/43511725/… Nodejs 开发的事务示例【参考方案2】:

您也可以尝试tweaking a few parameters 以获得额外的速度。具体来说,您可能想要PRAGMA synchronous = OFF;

【讨论】:

pragma synchronous = OFF 是个坏主意 - 它几乎不会影响批量插入的性能,并且您的数据库将在电源故障时损坏。一个更好的主意是将插入包装在事务中。 将 INSERTS 包装在 TRANSACTION 中并使用 PRAGMA journal_mode = MEMORY;将阻止 INSERT 访问磁盘,直到事务结束。 当心内存会在断电时损坏数据库 PRAGMA journal_mode = WAL;一次将允许许多写入者,并且您最终可以使用线程来写入数据。请注意,激活预写日志后,断电后数据库不会损坏。【参考方案3】:

增加PRAGMA cache_size 到一个更大的数字。这会 增加缓存的页数 在记忆中。注意:cache_size 是每个连接的设置。

将所有插入封装到一个事务中,而不是每行一个事务。

使用已编译的 SQL 语句进行插入。 最后,如前所述,如果您愿意放弃完全的 ACID 合规性,请设置PRAGMA synchronous = OFF;

【讨论】:

PRAGMA default_cache_size 现在是 deprecated cache_size 可以用来代替已弃用的default_cache_size。但是,cache_size 用于单个连接。【参考方案4】:

根据数据的大小和可用的 RAM 量,将 sqlite 设置为使用全内存数据库而不是写入磁盘将获得最佳性能提升之一。

对于内存数据库,将 NULL 作为文件名参数传递给 sqlite3_open 和 make sure that TEMP_STORE is defined appropriately

(以上所有文字均摘自我自己对separate sqlite-related question的回答)

【讨论】:

链接指向一个不完整的文档。信息比人们希望的要少,【参考方案5】:

您想使用.import 命令。例如:

$ cat demotab.txt
44      92
35      94
43      94
195     49
66      28
135     93
135     91
67      84
135     94

$ echo "create table mytable (col1 int, col2 int);" | sqlite3 foo.sqlite
$ echo ".import demotab.txt mytable"  | sqlite3 foo.sqlite

$ sqlite3 foo.sqlite
-- Loading resources from /Users/ramanujan/.sqliterc
SQLite version 3.6.6.2
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> select * from mytable;
col1    col2
44      92
35      94
43      94
195     49
66      28
135     93
135     91
67      84
135     94

请注意,此批量加载命令不是 SQL,而是 SQLite 的自定义功能。因此它的语法很奇怪,因为我们通过echo 将它传递给交互式命令行解释器sqlite3

在 PostgreSQL 中等价于COPY FROM: http://www.postgresql.org/docs/8.1/static/sql-copy.html

mysql 中是LOAD DATA LOCAL INFILE: http://dev.mysql.com/doc/refman/5.1/en/load-data.html

最后一件事:记住要小心.separator 的值。在进行批量插入时,这是一个非常常见的问题。

sqlite> .show .separator
     echo: off
  explain: off
  headers: on
     mode: list
nullvalue: ""
   output: stdout
separator: "\t"
    width:

在执行.import 之前,您应该明确地将分隔符设置为空格、制表符或逗号。

【讨论】:

这很棒,而且速度非常快。 20 分钟减少到 3 秒。 这是否适用于具有自动递增主键的表?我尝试在文件中使用 NULL 作为自动增量列,但它会引发错误。 查看 SQLite 的 shell.c 的代码,.import 只是在事务中使用准备好的语句。 sqlite 在命令行中使用 \t 有一个奇怪的行为,您必须为 -separator 选项提供一个 REAL 选项卡。在命令行中,使用 Control-v 插入一个真正的 TAB。【参考方案6】:

如果你只是插入一次,我可能对你有一个肮脏的把戏。

思路很简单,先插入内存数据库,然后备份,最后恢复到原来的数据库文件。

详细步骤我写在my blog。 :)

【讨论】:

【参考方案7】:

没有办法批量插入,但是 有一种方法可以写大块 记忆,然后将它们提交给 数据库。对于 C/C++ API,只需:

sqlite3_exec(db, "开始交易", 空,空,空);

...(插入语句)

sqlite3_exec(db, "COMMIT TRANSACTION", NULL, NULL, NULL);

假设 db 是您的数据库指针。

【讨论】:

【参考方案8】:

RE:“有没有更快的方法为每行数据生成插入语句?”

首先:通过使用 Sqlite3 的Virtual table API 将其减少到 2 个 SQL 语句,例如

create virtual table vtYourDataset using yourModule;
-- Bulk insert
insert into yourTargetTable (x, y, z)
select x, y, z from vtYourDataset;

这里的想法是,您实现一个 C 接口,该接口读取您的源数据集并将其作为虚拟表呈现给 SQlite,然后您一次完成从源表到目标表的 SQL 复制。这听起来比实际上更难,而且我已经通过这种方式测量了巨大的速度提升。

第二:利用此处提供的其他建议,即编译指示设置和使用事务。

第三:也许看看你是否可以取消目标表上的一些索引。这样,sqlite 将为插入的每一行更新更少的索引

【讨论】:

+1 这实际上是一种从 API 执行的“c”方式(根据要求),很好【参考方案9】:

一个很好的折衷办法是在 BEGIN; 之间包装你的 INSERTS;和结束;关键字即:

BEGIN;
INSERT INTO table VALUES ();
INSERT INTO table VALUES ();
...
END;

【讨论】:

还有INSERT INTO table VALUES (),(),();【参考方案10】:

我发现这是一次性导入的好组合。

.echo ON

.read create_table_without_pk.sql

PRAGMA cache_size = 400000; PRAGMA synchronous = OFF; PRAGMA journal_mode = OFF; PRAGMA locking_mode = EXCLUSIVE; PRAGMA count_changes = OFF; PRAGMA temp_store = MEMORY; PRAGMA auto_vacuum = NONE;

.separator "\t" .import a_tab_seprated_table.txt mytable

BEGIN; .read add_indexes.sql COMMIT;

.exit

来源:http://erictheturtle.blogspot.be/2009/05/fastest-bulk-import-into-sqlite.html

一些附加信息:http://blog.quibb.org/2010/08/fast-bulk-inserts-into-sqlite/

【讨论】:

【参考方案11】:

我已经测试了一些pragmas 在此处的答案中提出的建议:

synchronous = OFF journal_mode = WAL journal_mode = OFF locking_mode = EXCLUSIVE synchronous = OFF + locking_mode = EXCLUSIVE + journal_mode = OFF

这是我在事务中插入不同数量的数字:

增加批量大小可以真正提升性能,而关闭日志、同步、获取独占锁只会带来微不足道的收益。大约 110k 的点显示了随机后台负载如何影响您的数据库性能。

另外,值得一提的是,journal_mode=WAL 是默认值的一个很好的替代方案。它会带来一些好处,但不会降低可靠性。

C# Code.

【讨论】:

我在我的一个项目中注意到的一件事是,如果可能的话,应该将批处理限制为单个表。如果您在一个事务中并在循环中更新表 a 然后表 b,这将比循环两次运行慢得多,一次用于表 a,然后再次用于表 b。【参考方案12】:

我使用这种方法进行批量插入:

colnames = ['col1', 'col2', 'col3'] 
nrcols = len(colnames) 
qmarks = ",".join(["?" for i in range(nrcols)]) 
stmt = "INSERT INTO tablename VALUES(" + qmarks + ")" 
vals = [[val11, val12, val13], [val21, val22, val23], ..., [valn1, valn2, valn3]] 
conn.executemany(stmt, vals)

colnames must be in the order of the column names in the table 
vals is a list of db rows
each row must have the same length, and
contain the values in the correct order 
Note that we use executemany, not execute

【讨论】:

以上是关于在 sqlite3 中更快的批量插入?的主要内容,如果未能解决你的问题,请参考以下文章

为啥批量插入/更新更快?批量更新如何工作?

批量MySQL插入比PHP慢2倍

哪种方法最适合批量插入?

SQLite数据库如何批量插入数据?

C#_批量插入数据到Sqlserver中的四种方式

如何在 APEX 应用程序的 csv 文件的批量数据插入中使用 forall 语句