【问题标题】:Improve INSERT-per-second performance of SQLite提高 SQLite 的每秒插入性能
【发布时间】:2010-12-15 06:15:26
【问题描述】:

优化 SQLite 很棘手。 C 应用程序的批量插入性能从每秒 85 次插入到每秒超过 96,000 次插入不等!

背景:我们使用 SQLite 作为桌面应用程序的一部分。我们有大量的配置数据存储在 XML 文件中,这些数据被解析并加载到 SQLite 数据库中,以便在应用程序初始化时进行进一步处理。 SQLite 非常适合这种情况,因为它速度快,不需要专门的配置,并且数据库作为单个文件存储在磁盘上。

理由: 最初我对所看到的性能感到失望。 结果表明,SQLite 的性能可能会有很大差异(对于批量插入和选择而言) ) 取决于数据库的配置方式以及您使用 API 的方式。弄清楚所有选项和技术是什么并不是一件容易的事,因此我认为创建这个社区 wiki 条目以与 StackOverflow 读者分享结果以节省其他人进行相同调查的麻烦是明智的。

实验:与其简单地谈论一般意义上的性能技巧(即“使用事务!”),我认为最好编写一些 C 代码实际衡量各种选项的影响。我们将从一些简单的数据开始:

  • complete transit schedule for the city of Toronto 的 28 MB 制表符分隔的文本文件(大约 865,000 条记录)
  • 我的测试机器是运行 Windows XP 的 3.60 GHz P4。
  • 代码使用Visual C++ 2005 编译为具有“完全优化”(/Ox) 和Favor Fast Code (/Ot) 的“发布”。
  • 我正在使用直接编译到我的测试应用程序中的 SQLite“合并”。我碰巧拥有的 SQLite 版本有点旧(3.6.7),但我怀疑这些结果将与最新版本相当(如果您不这么认为,请发表评论)。

让我们写一些代码吧!

代码: 一个简单的 C 程序,它逐行读取文本文件,将字符串拆分为值,然后将数据插入 SQLite 数据库。在这个“基线”版本的代码中,创建了数据库,但我们不会实际插入数据:

/*************************************************************
    Baseline code to experiment with SQLite performance.

    Input data is a 28 MB TAB-delimited text file of the
    complete Toronto Transit System schedule/route info
    from http://www.toronto.ca/open/datasets/ttc-routes/

**************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include "sqlite3.h"

#define INPUTDATA "C:\\TTC_schedule_scheduleitem_10-27-2009.txt"
#define DATABASE "c:\\TTC_schedule_scheduleitem_10-27-2009.sqlite"
#define TABLE "CREATE TABLE IF NOT EXISTS TTC (id INTEGER PRIMARY KEY, Route_ID TEXT, Branch_Code TEXT, Version INTEGER, Stop INTEGER, Vehicle_Index INTEGER, Day Integer, Time TEXT)"
#define BUFFER_SIZE 256

int main(int argc, char **argv) {

    sqlite3 * db;
    sqlite3_stmt * stmt;
    char * sErrMsg = 0;
    char * tail = 0;
    int nRetCode;
    int n = 0;

    clock_t cStartClock;

    FILE * pFile;
    char sInputBuf [BUFFER_SIZE] = "\0";

    char * sRT = 0;  /* Route */
    char * sBR = 0;  /* Branch */
    char * sVR = 0;  /* Version */
    char * sST = 0;  /* Stop Number */
    char * sVI = 0;  /* Vehicle */
    char * sDT = 0;  /* Date */
    char * sTM = 0;  /* Time */

    char sSQL [BUFFER_SIZE] = "\0";

    /*********************************************/
    /* Open the Database and create the Schema */
    sqlite3_open(DATABASE, &db);
    sqlite3_exec(db, TABLE, NULL, NULL, &sErrMsg);

    /*********************************************/
    /* Open input file and import into Database*/
    cStartClock = clock();

    pFile = fopen (INPUTDATA,"r");
    while (!feof(pFile)) {

        fgets (sInputBuf, BUFFER_SIZE, pFile);

        sRT = strtok (sInputBuf, "\t");     /* Get Route */
        sBR = strtok (NULL, "\t");            /* Get Branch */
        sVR = strtok (NULL, "\t");            /* Get Version */
        sST = strtok (NULL, "\t");            /* Get Stop Number */
        sVI = strtok (NULL, "\t");            /* Get Vehicle */
        sDT = strtok (NULL, "\t");            /* Get Date */
        sTM = strtok (NULL, "\t");            /* Get Time */

        /* ACTUAL INSERT WILL GO HERE */

        n++;
    }
    fclose (pFile);

    printf("Imported %d records in %4.2f seconds\n", n, (clock() - cStartClock) / (double)CLOCKS_PER_SEC);

    sqlite3_close(db);
    return 0;
}

“控制”

按原样运行代码实际上并不执行任何数据库操作,但它会让我们了解原始 C 文件 I/O 和字符串处理操作的速度。

0.94 导入 864913 条记录 秒

太棒了!我们每秒可以进行 920,000 次插入,前提是我们实际上不进行任何插入 :-)


“最坏情况”

我们将使用从文件中读取的值生成 SQL 字符串,并使用 sqlite3_exec 调用该 SQL 操作:

sprintf(sSQL, "INSERT INTO TTC VALUES (NULL, '%s', '%s', '%s', '%s', '%s', '%s', '%s')", sRT, sBR, sVR, sST, sVI, sDT, sTM);
sqlite3_exec(db, sSQL, NULL, NULL, &sErrMsg);

这会很慢,因为每次插入的 SQL 都会被编译成 VDBE 代码,而且每次插入都会发生在自己的事务中。 有多慢?

在 9933.61 中导入了 864913 条记录 秒

哎呀! 2小时45分钟!这只是 每秒 85 次插入。

使用事务

默认情况下,SQLite 将评估唯一事务中的每个 INSERT / UPDATE 语句。如果执行大量插入,建议将您的操作包装在事务中:

sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, &sErrMsg);

pFile = fopen (INPUTDATA,"r");
while (!feof(pFile)) {

    ...

}
fclose (pFile);

sqlite3_exec(db, "END TRANSACTION", NULL, NULL, &sErrMsg);

38.03 导入 864913 条记录 秒

这样更好。只需将我们所有的插入包装在一个事务中即可将我们的性能提高到 每秒 23,000 次插入。

使用准备好的语句

使用事务是一个巨大的改进,但是如果我们一遍又一遍地使用相同的 SQL,那么为每个插入重新编译 SQL 语句就没有意义了。让我们使用sqlite3_prepare_v2 编译一次我们的SQL 语句,然后使用sqlite3_bind_text 将我们的参数绑定到该语句:

/* Open input file and import into the database */
cStartClock = clock();

sprintf(sSQL, "INSERT INTO TTC VALUES (NULL, @RT, @BR, @VR, @ST, @VI, @DT, @TM)");
sqlite3_prepare_v2(db,  sSQL, BUFFER_SIZE, &stmt, &tail);

sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, &sErrMsg);

pFile = fopen (INPUTDATA,"r");
while (!feof(pFile)) {

    fgets (sInputBuf, BUFFER_SIZE, pFile);

    sRT = strtok (sInputBuf, "\t");   /* Get Route */
    sBR = strtok (NULL, "\t");        /* Get Branch */
    sVR = strtok (NULL, "\t");        /* Get Version */
    sST = strtok (NULL, "\t");        /* Get Stop Number */
    sVI = strtok (NULL, "\t");        /* Get Vehicle */
    sDT = strtok (NULL, "\t");        /* Get Date */
    sTM = strtok (NULL, "\t");        /* Get Time */

    sqlite3_bind_text(stmt, 1, sRT, -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 2, sBR, -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 3, sVR, -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 4, sST, -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 5, sVI, -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 6, sDT, -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 7, sTM, -1, SQLITE_TRANSIENT);

    sqlite3_step(stmt);

    sqlite3_clear_bindings(stmt);
    sqlite3_reset(stmt);

    n++;
}
fclose (pFile);

sqlite3_exec(db, "END TRANSACTION", NULL, NULL, &sErrMsg);

printf("Imported %d records in %4.2f seconds\n", n, (clock() - cStartClock) / (double)CLOCKS_PER_SEC);

sqlite3_finalize(stmt);
sqlite3_close(db);

return 0;

16.27 导入 864913 条记录 秒

不错!还有一点点代码(别忘了调用sqlite3_clear_bindingssqlite3_reset),但我们的性能提高了一倍多,达到每秒53,000 次插入。

PRAGMA 同步 = 关闭

默认情况下,SQLite 会在发出操作系统级别的写入命令后暂停。这保证了数据被写入磁盘。通过设置synchronous = OFF,我们指示 SQLite 将数据简单地交给操作系统进行写入,然后继续。如果计算机在数据写入盘片之前发生灾难性崩溃(或电源故障),则数据库文件可能会损坏:

/* Open the database and create the schema */
sqlite3_open(DATABASE, &db);
sqlite3_exec(db, TABLE, NULL, NULL, &sErrMsg);
sqlite3_exec(db, "PRAGMA synchronous = OFF", NULL, NULL, &sErrMsg);

12.41 导入 864913 条记录 秒

现在改进幅度较小,但每秒插入次数高达 69,600 次。

PRAGMA journal_mode = MEMORY

考虑通过评估PRAGMA journal_mode = MEMORY 将回滚日志存储在内存中。您的事务会更快,但如果您在事务期间断电或程序崩溃,您的数据库可能会因事务部分完成而处于损坏状态:

/* Open the database and create the schema */
sqlite3_open(DATABASE, &db);
sqlite3_exec(db, TABLE, NULL, NULL, &sErrMsg);
sqlite3_exec(db, "PRAGMA journal_mode = MEMORY", NULL, NULL, &sErrMsg);

13.50 导入 864913 条记录 秒

比之前的优化慢一点,每秒 64,000 次插入。

PRAGMA 同步 = 关闭 PRAGMA journal_mode = MEMORY

让我们结合前面的两个优化。这有点风险(在崩溃的情况下),但我们只是在导入数据(不是经营银行):

/* Open the database and create the schema */
sqlite3_open(DATABASE, &db);
sqlite3_exec(db, TABLE, NULL, NULL, &sErrMsg);
sqlite3_exec(db, "PRAGMA synchronous = OFF", NULL, NULL, &sErrMsg);
sqlite3_exec(db, "PRAGMA journal_mode = MEMORY", NULL, NULL, &sErrMsg);

12.00 导入 864913 条记录 秒

太棒了!我们能够进行 每秒 72,000 次插入。

使用内存数据库

只是为了好玩,让我们在之前的所有优化的基础上重新定义数据库文件名,以便我们完全在 RAM 中工作:

#define DATABASE ":memory:"

10.94 导入 864913 条记录 秒

将我们的数据库存储在 RAM 中并不是很实用,但令人印象深刻的是,我们每秒可以执行 79,000 次插入。

重构 C 代码

虽然不是专门的 SQLite 改进,但我不喜欢 while 循环中额外的 char* 赋值操作。让我们快速重构代码,将strtok() 的输出直接传递给sqlite3_bind_text(),并让编译器为我们加快速度:

pFile = fopen (INPUTDATA,"r");
while (!feof(pFile)) {

    fgets (sInputBuf, BUFFER_SIZE, pFile);

    sqlite3_bind_text(stmt, 1, strtok (sInputBuf, "\t"), -1, SQLITE_TRANSIENT); /* Get Route */
    sqlite3_bind_text(stmt, 2, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT);    /* Get Branch */
    sqlite3_bind_text(stmt, 3, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT);    /* Get Version */
    sqlite3_bind_text(stmt, 4, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT);    /* Get Stop Number */
    sqlite3_bind_text(stmt, 5, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT);    /* Get Vehicle */
    sqlite3_bind_text(stmt, 6, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT);    /* Get Date */
    sqlite3_bind_text(stmt, 7, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT);    /* Get Time */

    sqlite3_step(stmt);        /* Execute the SQL Statement */
    sqlite3_clear_bindings(stmt);    /* Clear bindings */
    sqlite3_reset(stmt);        /* Reset VDBE */

    n++;
}
fclose (pFile);

注意:我们又回到了使用真实的数据库文件。内存数据库速度很快,但不一定实用

在 8.94 中导入了 864913 条记录 秒

对参数绑定中使用的字符串处理代码进行轻微重构后,我们可以每秒执行 96,700 次插入。我认为可以肯定地说这是 非常快。当我们开始调整其他变量(即页面大小、索引创建等)时,这将成为我们的基准。


总结(到目前为止)

我希望你还在我身边! 我们开始走这条路的原因是,SQLite 的批量插入性能变化如此之大,而且需要进行哪些更改并不总是很明显加快我们的操作。使用相同的编译器(和编译器选项)、相同版本的 SQLite 和相同的数据,我们优化了我们的代码和 SQLite 的使用,以 从每秒 85 次插入的最坏情况增加到超过 96,000 次插入每秒!


创建索引然后插入与插入然后创建索引

在我们开始测量SELECT 的性能之前,我们知道我们将创建索引。下面的答案之一建议在进行批量插入时,在插入数据后创建索引更快(而不是先创建索引然后插入数据)。让我们试试吧:

创建索引然后插入数据

sqlite3_exec(db, "CREATE  INDEX 'TTC_Stop_Index' ON 'TTC' ('Stop')", NULL, NULL, &sErrMsg);
sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, &sErrMsg);
...

18.13 导入 864913 条记录 秒

插入数据然后创建索引

...
sqlite3_exec(db, "END TRANSACTION", NULL, NULL, &sErrMsg);
sqlite3_exec(db, "CREATE  INDEX 'TTC_Stop_Index' ON 'TTC' ('Stop')", NULL, NULL, &sErrMsg);

13.66 导入 864913 条记录 秒

正如预期的那样,如果为一列建立索引,批量插入的速度会变慢,但如果在插入数据后创建索引,它确实会有所不同。我们的无索引基线是每秒 96,000 次插入。 先创建索引然后插入数据,我们每秒可以进行 47,700 次插入,而先插入数据然后创建索引,我们每秒可以进行 63,300 次插入。


我很乐意接受其他方案的建议来尝试...并且很快会为 SELECT 查询编译类似的数据。

【问题讨论】:

  • 好点!在我们的例子中,我们正在处理从 XML 和 CSV 文本文件读取到 20 万条记录中的大约 150 万个键/值对。与运行诸如 SO 之类的网站的数据库相比较小 - 但足够大以至于调整 SQLite 性能变得很重要。
  • “我们有大量的配置数据存储在 XML 文件中,这些数据在应用程序初始化时被解析并加载到 SQLite 数据库中以供进一步处理。”为什么不首先将所有内容都保存在 sqlite 数据库中,而不是存储在 XML 中,然后在初始化时加载所有内容?
  • 你试过不打电话给sqlite3_clear_bindings(stmt);吗?每次都设置绑定就足够了: 在第一次调用 sqlite3_step() 之前或在 sqlite3_reset() 之后立即调用 sqlite3_bind() 接口之一以将值附加到参数。对 sqlite3_bind() 的每次调用都会覆盖对同一参数的先前绑定(请参阅:sqlite.org/cintro.html)。 docs for that function 中没有任何内容说您必须调用它。
  • 您是否进行了重复测量?避免 7 个本地指针的 4s “胜利”很奇怪,即使假设优化器很混乱。
  • 不要使用feof() 来控制输入循环的终止。使用fgets() 返回的结果。 stackoverflow.com/a/15485689/827263

标签: c performance sqlite optimization


【解决方案1】:

像@Jimmy_A 那样将任务拆分为多个事务是可行的方法。否则,您的 RAM 可能会因庞大的事务和繁重的 COMMIT 任务而饱和。

如果您使用某种电池供电系统(笔记本电脑、UPS、带电池的 RAID 控制器...),您还可以在硬盘驱动器上启用回写式缓存,以进一步调整性能。

【讨论】:

    【解决方案2】:

    您的问题的答案是,较新的 SQLite 3 提高了性能,使用它。

    SqlAlchemy Orm 作者的这个答案 Why is SQLAlchemy insert with sqlite 25 times slower than using sqlite3 directly? 在 0.5 秒内插入了 10 万次,我在 python-sqlite 和 SqlAlchemy 中看到了类似的结果。这让我相信 SQLite 3 提高了性能。

    【讨论】:

      【解决方案3】:

      避免sqlite3_clear_bindings(stmt)

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

      SQLite 文档中的C API intro 说:

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

      sqlite3_clear_bindings 的文档中没有说明除了简单地设置绑定之外您还必须调用它。

      更多详情:Avoid_sqlite3_clear_bindings()

      【讨论】:

      • 非常正确:“与许多人的直觉相反,sqlite3_reset() 不会重置准备好的语句上的绑定。使用此例程将所有主机参数重置为 NULL。” - sqlite.org/c3ref/clear_bindings.html
      【解决方案4】:

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

      首先在表格中找到项目:

      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(因为它是为我们全局启用的)

      【讨论】:

        【解决方案5】:

        使用 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 部分了解更多详情

        【讨论】:

          【解决方案6】:

          几个提示:

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

          我也问过类似的问题herehere

          【讨论】:

          • Docs 不知道 PRAGMA journal_mode NORMAL sqlite.org/pragma.html#pragma_journal_mode
          • 已经有一段时间了,我的建议在引入 WAL 之前适用于旧版本。看起来 DELETE 是新的正常设置,现在还有 OFF 和 MEMORY 设置。我想 OFF/MEMORY 会以牺牲数据库完整性为代价来提高写入性能,而 OFF 会完全禁用回滚。
          • 对于#7,你有关于如何使用 c# system.data.sqlite 包装器启用 共享页面缓存 的示例吗?
          • #4 带回了古老的记忆——在以前至少有一个案例,在一组添加之前删除索引并在之后重新创建它显着加快插入。对于某些您知道在此期间您可以单独访问该表的添加,在现代系统上可能仍然可以更快地工作。
          • @Snazzer 也许你说的是synchronous = NORMALsqlite.org/pragma.html#pragma_synchronous
          【解决方案7】:

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

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

          这是您的建议失败的地方。您对所有记录使用单个事务和没有错误/失败的单个插入。假设您将每条记录拆分为不同表上的多个插入。如果记录被打破会怎样?

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

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

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

          这个解决方案帮助我绕过了在处理包含错误/重复记录的文件时遇到的问题(我有几乎 4% 的错误记录)。

          我创建的算法帮助我将流程缩短了 2 小时。文件 1hr 30m 的最终加载过程仍然很慢,但与最初花费的 4hrs 相比没有。我设法将插入速度从 10.000/s 提高到 ~14.000/s

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

          更新

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

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

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

          【讨论】:

          • 我会提出以下建议。 * 使用 SQLITE_STATIC 与 SQLITE_TRANSIENT 避免字符串复制,您必须确保在执行事务之前不会更改字符串 * 使用批量插入 INSERT INTO stop_times VALUES (NULL, ?, ?, ?, ?, ?, ?, ?, ? , ?), (NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?), (NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?),(NULL , ?, ?, ?, ?, ?, ?, ?, ?, ?), (NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?) * mmap 文件减少的数量系统调用。
          • 这样做我可以在 11.51 秒内导入 5,582,642 条记录
          【解决方案8】:

          尝试使用SQLITE_STATIC 而不是SQLITE_TRANSIENT 进行这些插入。

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

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

          【讨论】:

            【解决方案9】:

            在我将 cache_size 提高到更高的值(即PRAGMA cache_size=10000;)之前,我无法从事务中获得任何收益

            【讨论】:

            • 请注意,对cache_size 使用正值会设置number of pages to cache,而不是总RAM 大小。在默认页面大小为 4kB 的情况下,此设置将为每个打开的文件(或每个进程,如果使用 shared cache 运行)最多保存 40MB 的数据。
            【解决方案10】:

            关于批量插入

            受这篇文章和引发我来到这里的 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。

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

            【讨论】:

            • @Jess:如果你点击链接,你会发现他的意思是批量插入语法。
            • @afaulconbridge:可能也一样:我怀疑您将获得相当的加速 - 但更安全、更容易 - 只需将插入包装在单个事务中。
            【解决方案11】:

            如果您可以对 INSERT/UPDATE 语句进行分块,批量导入的效果似乎最好。在只有几行的表上,10,000 左右的值对我来说效果很好,YMMV...

            【讨论】:

            • 您需要调整 x = 10,000,以便 x = cache [= cache_size * page_size] / 插入的平均大小。
            猜你喜欢
            • 2010-12-15
            • 2017-06-21
            • 2019-02-08
            • 1970-01-01
            • 2013-02-12
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多