数百万行的插入性能

4
我正在尝试使用Python脚本解析维基百科档案。(是的,我知道。)当然:
  • 维基百科XML文件:45.95 GB
  • 可用内存:16 GB
这排除了将文件加载到内存中,而进入虚拟内存也不会好得多。因此,为了处理数据,我决定将必要的信息解析成SQLite数据库。对于XML解析,我使用了ElementTree库,它的性能非常好。我确认仅运行XML解析(只需注释掉数据库调用),它会线性运行,并且在遍历文件时不会减慢速度。
问题出现在尝试将数百万行插入SQLite数据库(每个维基百科文章一个)。我用于测试的简单版本表如下:
CREATE TABLE articles(
    id INTEGER NOT NULL PRIMARY KEY,
    title TEXT NOT NULL UNIQUE ON CONFLICT IGNORE);

在这个初始阶段,我只有ID和文本字段。当我通过以下方式开始添加行时:
INSERT OR IGNORE INTO articles(title) VALUES(?1);

它一开始表现良好。但是在大约800万行左右,它开始急剧减速,降低一个数量级或更多。
当然需要一些细节。我正在使用带有插入语句之前创建的单个游标的cur.executemany()。每次调用此函数都有约10万行的批处理。在所有100万多行都被插入之前,我不会调用db.commit()。根据我所读到的,只要有INSERT语句,executemany()就不应该提交事务,直到db.commit()。
正在读取的源XML和正在写入的数据库位于两个独立的磁盘上,我还尝试在内存中创建数据库,但无论如何我都看到了减速。我还尝试了isolation_level=None选项,在开头和结尾添加了BEGIN TRANSACTIONCOMMIT TRANSACTION调用(因此整个解析序列是一个事务),但仍然没有帮助。

有一些本网站上的其他问题表明索引是问题所在。我没有在表上创建任何索引。我尝试删除UNIQUE约束,只限制于id INTEGER PRIMARY KEYtitle TEXT NOT NULL,但也没有效果。

在SQLite中处理大型数据集的这些类型插入的最佳方法是什么?当然,这个简单的查询只是众多查询中的第一个;还有其他更复杂的查询,涉及外键(此表中的文章ID)以及嵌入的select语句的插入语句(在插入期间从文章表中选择ID)。这些问题必然会出现,但随着行数的增加,性能问题会更加严重-当文章表少于1500万行时,其他表可能会有超过10亿行。因此,这些性能问题变得更加令人担忧。


很可能是因为SQLite无法在内存中保存更改,所以溢出到磁盘上。请参阅http://www.sqlite.org/tempfiles.html和http://www.sqlite.org/atomiccommit.html,了解SQLite如何与非易失性存储交互。 - Martijn Pieters
你可以尝试使用WAL模式来提高性能。我没有使用过它,但是乍一看,我肯定会调查这个选项。 - Martijn Pieters
说实话,我的评论最多只是猜测。如果每个事务需要修改很多已经写好的页面,那么也会出现“内存无法容纳太多页面”的相同行为。但是我没有仔细研究过SQLite的内存模型。 - Martijn Pieters
3
尝试增加缓存大小 - CL.
1
CL. - 你的评论有点被隐藏了。这是有效的解决方法(你应该将其添加为答案,以便我可以接受它)- 结果发现SQLite的缓存太小了,无法满足我的需求。当它增长到表格无法完全适合缓存的大小时,它似乎会不断地交换表格的部分来检查主键。我认为主键会比那聪明一些(因为它很常见,看起来很简单,你会认为它只是i++的情况),但显然不是这样。;) - jstm88
显示剩余3条评论
1个回答

3
在插入数据时,一个“不可见”的操作是更新表的索引(以及检查诸如 UNIQUE 的索引相关约束)。由于您无论如何都会忽略 UNIQUE 违规,因此在加载表时禁用表上的索引可能会对您有所帮助。如果您确实需要它们,在加载完成后构建索引即可。
但请注意,SQLite 在处理小型数据时的闪电般速度来自某些隐含的假设,当您处理大型数据时,这些假设越来越不成立。在当前硬件上,它可能不是您当前问题的适当工具。

我曾经认为在事务期间主键索引被禁用,并且只在每个事务结束时更新,因此我尝试在单个事务中完成。我还禁用了“UNIQUE”约束,但这并没有改善性能。我现在正在运行一个测试,使用没有id列(只有文本)来看看它的效果如何。 - jstm88
它不应该有所帮助,因为在内部,SQLite 无论如何都使用一个不可见的“整数主键”。但是,如果瓶颈不是索引,则页面缓存翻转(无论是在SQLite级别、操作系统内存管理器级别还是甚至处理器缓存级别)很可能是性能急剧下降的原因。 - dig
主键列是罪魁祸首,至少部分原因在于此...没有主键时它的工作速度非常快。不幸的是,这迫使我创建表,然后创建一个新表将数据移动到其中,以再次赋予其主键...我将尝试调整缓存大小,看看是否可以在不删除主键的情况下使其正常工作。 - jstm88

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