我在我的一个项目中使用SQLite3,我需要确保插入到表中的行对于某些列的组合是唯一的。在大多数情况下,插入的行在这方面会有所不同,但如果匹配,新行必须更新/替换现有行。
显而易见的解决方案是使用复合主键,并使用conflict子句来处理冲突。在此之前:
CREATE TABLE Event (Id INTEGER, Fld0 TEXT, Fld1 INTEGER, Fld2 TEXT, Fld3 TEXT, Fld4 TEXT, Fld5 TEXT, Fld6 TEXT);
成了这个:
CREATE TABLE Event (Id INTEGER, Fld0 TEXT, Fld1 INTEGER, Fld2 TEXT, Fld3 TEXT, Fld4 TEXT, Fld5 TEXT, Fld6 TEXT, PRIMARY KEY (Fld0, Fld2, Fld3) ON CONFLICT REPLACE);
这确实强制执行我需要的唯一性约束。不幸的是,这种变化也会导致性能损失超出我的预期。我做到了
使用sqlite3
命令行实用程序进行的一些测试,以确保我的其余代码中没有错误。测试涉及在单个中输入100,000行
交易或100个交易,每个交易1,000行。我得到了以下结果:
| 1 * 100,000 | 10 * 10,000 | 100 * 1,000 |
|---------------|---------------|---------------|
| Time | CPU | Time | CPU | Time | CPU |
| (sec) | (%) | (sec) | (%) | (sec) | (%) |
--------------------------------|-------|-------|-------|-------|-------|-------|
No primary key | 2.33 | 80 | 3.73 | 50 | 15.1 | 15 |
--------------------------------|-------|-------|-------|-------|-------|-------|
Primary key: Fld3 | 5.19 | 84 | 23.6 | 21 | 226.2 | 3 |
--------------------------------|-------|-------|-------|-------|-------|-------|
Primary key: Fld2, Fld3 | 5.11 | 88 | 24.6 | 22 | 258.8 | 3 |
--------------------------------|-------|-------|-------|-------|-------|-------|
Primary key: Fld0, Fld2, Fld3 | 5.38 | 87 | 23.8 | 23 | 232.3 | 3 |
我的应用程序目前执行的行数最多为1,000行,我对性能下降15倍感到惊讶。我预计吞吐量下降最多3倍,CPU使用率也会上升,如100k交易案例所示。我想维护主键约束所涉及的索引需要大量的同步数据库操作,因此在这种情况下我的硬盘成为瓶颈。
使用WAL mode会产生一些影响 - 性能提升约15%。不幸的是,这本身还不够。 PRAGMA synchronous = NORMAL
似乎没有任何效果。
我可能能够通过增加事务大小来恢复某些性能,但由于内存使用量的增加以及对响应能力的关注,我宁愿不这样做。 可靠性。
每行中的文本字段的可变长度平均约为250字节。查询性能无关紧要,但插入性能非常重要。我的应用程序代码在C中,并且(应该是)可移植到至少Linux和Windows。
有没有办法在不增加事务大小的情况下提高插入性能? SQLite中的一些设置(除了永久强制DB进入异步操作之外的任何东西,或者是在我的应用程序代码中以编程方式)?例如,有没有办法确保行唯一性而不使用索引?
BOUNTY:
通过使用我自己的答案中描述的散列/索引方法,我设法将性能降低到一定程度,以至于我的应用程序可能接受它。 但是,似乎随着表中行数的增加,索引的存在会使插入越来越慢。
我对任何可以提高此特定用例性能的技术或微调设置感兴趣,只要它不涉及破解SQLite3代码或导致项目无法维护。
答案 0 :(得分:15)
我已经使用sqlite在运行时插入了数百万行,这就是我用来提高性能的方法:
如果您尝试这些,请发布您的测试结果。我相信每个人都会感兴趣。
答案 1 :(得分:8)
ON CONFLICT REPLACE
子句将使SQLite删除现有行,然后插入新行。这意味着SQLite可能会花费一些时间
这是我对它的看法,基于SQLite文档和阅读其他数据库管理系统。我没看过源代码。
SQLite有两种表达唯一性约束的方法:PRIMARY KEY
和UNIQUE
。但是,它们都创建了一个索引。
现在真正重要的东西。 。
你做测试真是太好了。大多数开发人员不这样做。但我认为你的测试结果非常误导。
在您的情况下,将行插入到没有主键的表中的速度并不重要。没有主键的表不满足您对数据完整性的基本要求。这意味着你不能依靠你的数据库来给你正确的答案。
如果没有给出正确的答案,我可以让它真的非常快。
要获得插入没有密钥的表的有意义的时间,您需要
当然,这些流程所需的时间也必须考虑在内。
FWIW,我通过在1000个语句的事务中将100K SQL插入语句运行到您的模式中进行了测试,并且只花了30秒。 1000个插入语句的单个事务,似乎是您在生产中所期望的,花了149毫秒。
也许你可以通过插入一个无键的临时表来加快速度,然后从中更新键控表。
答案 2 :(得分:4)
(我通常不回答我自己的问题,但我想为此记录一些想法/部分解决方案。)
复合主键的主要问题是索引的处理方式。复合键意味着复合值的索引,在我的情况下意味着索引字符串。虽然比较字符串值并不那么慢,但索引长度为500字节的值意味着索引中的B树节点可以比索引64-的B树更少的行/节点指针。位整数值。这意味着每个索引搜索都会加载更多的DB页面,因为B树的高度会增加。
为了解决这个问题,我修改了我的代码,以便:
它使用WAL mode。性能提升肯定值得这么小的变化,因为我没有任何问题,DB文件不是自包含的。
我使用MurmurHash3哈希函数 - 在用C重新编写它并调整它之后 - 从形成密钥的字段的值产生一个32位哈希值。我将此哈希存储在新的索引列中。由于这是一个整数值,因此索引非常快。这是此表的唯一索引。由于表中最多有10,000,000行,因此哈希冲突不会成为性能问题 - 虽然我不能真正认为哈希值为UNIQUE
,但索引只返回一般的一行情况下。
此时我已编码并正在进行测试有两种选择:
DELETE FROM Event WHERE Hash=? AND Fld0=? AND Fld2=? AND Fld3=?
,然后是INSERT
。
UPDATE Event SET Fld1=?,... WHERE Hash=? AND Fld0=? AND Fld2=? AND Fld3=?
,如果没有更新的行,则后跟INSERT
。
我希望第二种选择更快,但我必须先完成测试。在任何情况下,似乎通过这些更改,性能下降(与原始无索引表相比)已减少到5左右,这更容易管理。
编辑:
此时我已经决定使用第二种变体,这确实稍快一些。但是,似乎随着索引表变大,任何类型的索引都会显着减慢SQLite3的速度。将数据库页面大小增加到8192字节似乎有所帮助,但并不像我想的那样大幅提升。
答案 3 :(得分:3)
Case When Exists((Select ID From Table Where Fld0 = value0 and Fld2 = value1 and Fld3 = value 2)) Then
--Insert Statement
End
我不是100%认为插件的工作方式与SQLite相同,但我认为应该如此。这对Where
字段的正确索引应该相当快。然而,这是需要考虑的两个交易。
答案 4 :(得分:3)
除了所有其他好的答案之外,您可以做的一件事是将数据分成几个表。
随着行数的增加,SQLite INSERT变得越来越慢,但是如果你可以将一个表拆分成几个那些效果减弱的表(例如:“names” - >“names_a”,“names_b”,......对于以字母x
开头的名称。稍后,您可以CREATE VIEW "names" AS SELECT * FROM "names_a" UNION SELECT * FROM "names_b" UNION ...
。