提高SQLite的每秒插入性能

3365
优化SQLite很棘手。C应用程序的批量插入性能可以从每秒85次变化到每秒超过96,000次!
背景:我们正在使用SQLite作为桌面应用程序的一部分。我们有大量的配置数据存储在XML文件中,在应用程序初始化时被解析并加载到SQLite数据库中进行进一步处理。SQLite非常适合这种情况,因为它快速,不需要专门的配置,并且数据库存储在磁盘上作为单个文件。
理由:最初我对看到的性能感到失望。事实证明,SQLite的性能可以根据如何配置数据库以及如何使用API而显着变化(对于批量插入和选择)。找出所有选项和技术并不是一件微不足道的事情,因此我认为创建这个社区wiki条目与Stack Overflow读者分享结果是明智的,以便节省其他人做同样调查的麻烦。
实验:与其仅在一般意义上谈论性能技巧(例如“使用事务!”),不如编写一些C代码并实际测量各种选项的影响。我们将从一些简单的数据开始。
  • 一个28 MB的TAB分隔文本文件(大约865,000条记录),包含多伦多市的完整交通时间表
  • 我的测试机是一台运行Windows XP的3.60 GHz P4。
  • 代码使用Visual C++ 2005编译为“Release”,使用“Full Optimization” (/Ox)和Favor Fast Code (/Ot)。
  • 我使用了SQLite“Amalgamation”,直接编译到我的测试应用程序中。我手头上的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和字符串处理操作有多快。

导入864913条记录,用时0.94秒

太棒了!我们可以每秒插入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代码和事务处理。每次插入都需要编译SQL成VDBE代码,且每个插入都在自己的事务中进行,因此速度会比较慢。 有多慢呢?

导入了864913条记录,用时9933.61秒

天哪!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),但我们的性能已经提高了一倍以上,达到每秒53000次插入

PRAGMA synchronous = OFF

默认情况下,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条记录

改进现在变得更小了,但我们已经达到了每秒69600次插入

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

导入864913条记录,耗时13.50秒

比之前的优化稍慢,每秒只能插入64000条数据。

PRAGMA synchronous = OFF 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);

导入了864913条记录,耗时12.00秒。

太棒了!我们能够每秒执行72000次插入操作。

使用内存数据库

仅出于好奇,让我们在之前所有的优化基础上构建,并重新定义数据库文件名,以便完全在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);

注意:我们已经回到使用真实的数据库文件。内存数据库虽然快,但不一定实用。

导入了864913条记录,用时8.94秒

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


总结(到目前为止)

希望您还在跟我一起!我们开始这条路的原因是SQLite的批量插入性能变化巨大,而且往往不明显需要做出哪些更改来加速操作。使用相同的编译器(和编译器选项),相同版本的SQLite和相同的数据,我们优化了我们的代码和SQLite的使用方式,使得我们从最坏情况下的每秒85次插入到每秒超过96,000次插入!


创建索引然后插入数据 vs. 插入数据然后创建索引

在我们开始衡量SELECT性能之前,我们知道我们将会创建索引。以下其中一个答案建议,在进行批量插入时,创建索引比先创建索引再插入数据更快。让我们来试试:

创建索引然后插入数据

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

导入了864913条记录,用时18.13秒

插入数据然后创建索引

...
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查询编制类似的数据。


13
好的,我们来翻译一下。在我们的情况下,我们正在处理大约150万个键/值对,这些数据从XML和CSV文本文件中读取到200,000条记录中。与像SO这样运行网站的数据库相比较小,但足够大,需要调整SQLite性能。 - Mike Willekes
56
当应用程序初始化时,我们会将存储在XML文件中的大量配置数据解析并加载到SQLite数据库中进行进一步处理。为什么不直接将所有内容存储在SQLite数据库中,而要先存储在XML文件中,然后在初始化时加载呢? - CAFxX
15
你尝试过不调用sqlite3_clear_bindings(stmt);吗?由于在每次调用时都设置了绑定,因此这应该足够了:在第一次调用sqlite3_step()之前或立即在sqlite3_reset()之后,应用程序可以调用其中一个sqlite3_bind()接口将值附加到参数。每次调用sqlite3_bind()会覆盖同一参数上先前的绑定(请参见:https://www.sqlite.org/cintro.html)。该函数的文档中没有说明必须调用它。 - ahcox
27
你做了重复测量吗?即使假设优化器混乱,为避免7个本地指针仅需要4s就能“胜利”,这显得很奇怪。 - peterchen
12
不要使用 feof() 控制输入循环的终止。使用 fgets() 返回的结果来控制。https://dev59.com/VGUp5IYBdhLWcg3wJU9J#15485689 - Keith Thompson
显示剩余23条评论
12个回答

870

几个提示:

  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. 如果您使用多个线程,可以尝试使用共享页面缓存,它将允许在线程之间共享加载的页面,从而避免昂贵的I/O调用。
  8. 不要使用!feof(file)

我也曾在这里这里提出类似的问题。


13
文档不知道 PRAGMA journal_mode NORMAL 的含义。请参考 http://www.sqlite.org/pragma.html#pragma_journal_mode。 - OneWorld
8
有一段时间了,我的建议适用于引入WAL之前的旧版本。看起来DELETE是新的正常设置,现在还有OFF和MEMORY设置。我想OFF/MEMORY会以牺牲数据库完整性为代价提高写入性能,而OFF则完全禁用回滚。 - Snazzer
6
您是否有一个示例,展示如何使用C# System.Data.SQLite包装器启用共享页面缓存 - Aaron Hudon
6
#4 唤起了古老的回忆——在以前的某个时候,曾经有一个案例,在一组添加操作之前删除索引,然后重新创建它,可以显著加速插入。在现代系统中,对于某些添加操作来说,仍然可能比直接插入更快,尤其是在你知道在一段时间内拥有表的独占访问权时。 - Bill K
@Snazzer 也许你在谈论 synchronous = NORMAL?https://www.sqlite.org/pragma.html#pragma_synchronous - Basj

175

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

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

SQLITE_STATIC告诉它您提供的内存地址将在查询执行完毕之前是有效的(在此循环中始终如此)。这将为您节省每个循环的几个分配、复制和释放操作。可能是一个很大的改进。


130

避免使用sqlite3_clear_bindings(stmt)

测试中的代码每次都设置绑定参数,这应该足够了。

来自SQLite文档的C API介绍说:

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

sqlite3_clear_bindings的文档中没有要求除简单地设置绑定参数之外还必须调用它。

更多详细信息:避免使用sqlite3_clear_bindings()


8
令人惊喜的是:"与许多人的直觉相反,sqlite3_reset()不会重置准备语句中的绑定。使用此例程将所有主机参数重置为NULL。" - https://www.sqlite.org/c3ref/clear_bindings.html - Francis Straccia

73

关于批量插入

受到这篇文章的启发以及Stack Overflow的问题,带领我来到这里--有没有可能在SQLite数据库中一次插入多行?-- 我发布了我的第一个Git仓库:

https://github.com/rdpoor/CreateOrUpdate

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

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


4
如果你点击链接,你会发现他指的是批量插入语法。 - Alix Axel
1
@afaulconbridge:可能最好也是如此:我猜想你只需将插入操作放在单个事务中进行,就可以获得相当的加速效果——而且更加安全和简便。 - fearless_fool
我同意这些猫的观点,你有28MB的数据?我有130TB的二进制数据。 - Leroy Scandal

62

如果您将INSERT / UPDATE语句分块,批量导入似乎会表现最佳。在仅有少量行的表上,10,000左右的值对我而言效果很好,但因人而异(YMMV)...


24
你需要调整 x = 10,000,以便 x = cache [= cache_size * page_size] / 插入的平均大小。 - Alix Axel

53
如果你只关心阅读,那么一种稍微快一点(但可能会读取过时数据)的方法是从多个线程的多个连接中读取(每个线程一个连接)。
首先在表中找到这些项目。
SELECT COUNT(*) FROM table

然后按页读取(LIMIT/OFFSET):
SELECT * FROM table ORDER BY _ROWID_ LIMIT <limit> OFFSET <offset>

其中<limit><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列,大约有10万行或更多)。
线程数量过多或过少都不行,你需要自己进行基准测试和分析。
对我们来说,SHAREDCACHE会使性能变慢,所以我手动设置为PRIVATECACHE(因为在我们的全局设置中启用了它)。

36

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


2
请注意,使用正值cache_size设置要缓存的页面数(而非总RAM大小)。对于默认的4kB页面大小,此设置将每个打开的文件(或每个进程,如果使用共享缓存运行)最多保存40MB的数据。 - vgru

28

阅读完本教程后,我尝试将其应用到我的程序中。

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

这就是您的建议失败的地方。您为所有记录使用单个事务和单个插入,且没有错误/失败。假设您将每个记录拆分为多个插入到不同表中。如果记录损坏会发生什么?

如果您在记录中有10个元素,并且您需要将每个元素插入到不同的表中,如果第5个元素遇到CONSTRAINT错误,则前面的4个插入也需要进行回滚。

所以这就是回滚的地方。回滚的唯一问题是您会失去所有插入并从头开始。你怎么能解决这个问题?

我的解决方案是使用多个事务。我每10,000个记录启动和结束一个事务(不要问我为什么是这个数字,这是我测试过的最快数字)。我创建了一个大小为10,000的数组,并将成功的记录插入其中。当出现错误时,我进行回滚,开始一个事务,插入来自我的数组的记录,提交,然后在损坏的记录之后开始一个新事务。

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

我创建的算法帮助我将处理时间缩短了2小时。最终加载文件的过程为1小时30分钟,虽然仍然很慢,但与最初需要4小时相比并不是很慢。我设法将插入速度从每秒10,000条提高到约14,000条/秒

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

更新

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

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

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


1
我建议采用以下方法:
  • 使用SQLITE_STATIC而不是SQLITE_TRANSIENT来避免字符串复制,您必须确保在事务执行之前该字符串不会被更改。
  • 使用批量插入INSERT INTO stop_times VALUES (NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?), (NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?), (NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?),(NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?), (NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  • mmap文件以减少系统调用次数。
- rouzier
2
通过这样做,我能够在11.51秒内导入5,582,642条记录。 - rouzier

18

你的问题的答案是,新的SQLite 3拥有更好的性能,建议使用它。

SqlAlchemy ORM作者发布的回答(为什么使用SqlAlchemy插入sqlite比直接使用sqlite3慢25倍?)表明在0.5秒内完成了10万个插入操作,并且我也曾经在python-sqlite和SqlAlchemy中看到了类似的结果。这让我相信,SQLite 3的性能已经得到了提升。


2

在我的情况下,使用 PRAGMA journal_mode = WAL 提高了 INSERT 的速度,因为内部的处理方式与批量插入 建议在此处 相同。

在我的情况下,我需要将数据导入到索引中而不仅仅是表格中。SQLite 有一个很棒的功能,即 WITHOUT ROWID,它允许将表格和索引组合在一起。默认情况下,SQLite 中的表格也是 B-Tree,并且任何索引都存储在单独的 B-Tree 页面中。使用 WITHOUT ROWID 只使用一个 B-Tree 来处理表格和索引。

我还使用了 PRAGMA auto_vacuum = 0,因为从技术上讲,它应该防止 SQLite 在牺牲数据库大小的情况下改进空间利用率,但似乎在性能方面没有任何明显的差异。

虽然我的情况与 OP 的要求略有不同,但第一条使用 WAL 的建议对他的情况应该会有所帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接