提高 SQLite 的每秒更新性能?
Posted
技术标签:
【中文标题】提高 SQLite 的每秒更新性能?【英文标题】:Improve UPDATE-per-second performance of SQLite? 【发布时间】:2017-06-21 05:03:57 【问题描述】:我的问题直接来自this 一个,虽然我只对UPDATE 感兴趣。
我有一个用C/C++
编写的应用程序,它以非常频繁的间隔大量使用SQLite
,主要是SELECT/UPDATE
(每0.5 到1 秒大约有20 个查询) p>
我的数据库不大,目前大概2500条记录,表结构如下:
CREATE TABLE player (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(64) UNIQUE,
stats VARBINARY,
rules VARBINARY
);
到目前为止,我还没有使用transactions
,因为我正在改进代码并且想要稳定性而不是性能。
然后我仅通过执行10 update
查询来测量我的数据库性能,如下(在不同值的循环中):
// 10 times execution of this
UPDATE player SET stats = ? WHERE (name = ?)
其中stats
是正好有 150 个字符的 JSON,name
是 5-10 个字符。
没有事务,结果是不可接受的:-大约 1 整秒(每个 0.096)
对于事务,时间减少 x7.5 倍:- 大约 0.11 - 0.16 秒(每个 0.013)
我尝试删除大部分数据库和/或重新排序/删除列以查看是否有任何更改,但没有。即使数据库只包含 100 条记录(经过测试),我也会得到上述数字。
然后我尝试使用PRAGMA
选项:
PRAGMA synchronous = NORMAL
PRAGMA journal_mode = MEMORY
给我更短的时间,但并非总是如此,更像是大约 0.08 - 0.14 秒
PRAGMA synchronous = OFF
PRAGMA journal_mode = MEMORY
最后给了我非常小的时间大约 0.002 - 0.003 秒,但我不想使用它,因为我的应用程序每秒都会保存数据库,并且很有可能在操作系统/电源上损坏数据库失败。
我的C SQLite
查询代码是:(cmets/错误处理/无关部分省略)
// start transaction
sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, NULL);
// query
sqlite3_stmt *statement = NULL;
int out = sqlite3_prepare_v2(query.c_str(), -1, &statement, NULL);
// bindings
for(size_t x = 0, sz = bindings.size(); x < sz; x++)
out = sqlite3_bind_text(statement, x+1, bindings[x].text_value.c_str(), bindings[x].text_value.size(), SQLITE_TRANSIENT);
...
// execute
out = sqlite3_step(statement);
if (out != SQLITE_OK)
// should finalize the query no mind the error
if (statement != NULL)
sqlite3_finalize(statement);
// end the transaction
sqlite3_exec(db, "END TRANSACTION", NULL, NULL, NULL);
如您所见,这是一个非常典型的TABLE
,记录数量很少,而我正在做一个简单的UPDATE
正好10 次。我还能做些什么来减少我的UPDATE
次吗?我正在使用最新的SQLite 3.16.2
。
注意:上述时间直接来自单个
END TRANSACTION
查询。查询完成了一个简单的交易,我 使用准备好的语句。
更新:
我在启用和禁用事务以及各种更新计数的情况下执行了一些测试。我使用以下设置进行了测试:
VACUUM;
PRAGMA synchronous = NORMAL; -- def: FULL
PRAGMA journal_mode = WAL; -- def: DELETE
PRAGMA page_size = 4096; -- def: 1024
结果如下:
无交易(10 次更新)
0.30800 秒(每次更新 0.0308) 0.30200 秒 0.36200 秒 0.28600 秒无交易(100 次更新)
2.64400 秒(每次更新 0.02644) 2.61200 秒 2.76400 秒 2.68700 秒无交易(1000 次更新)
28.02800 秒(每次更新 0.028) 27.73700 秒 ..有交易(10 次更新)
0.12800 秒(每次更新 0.0128) 0.08100 秒 0.16400 秒 0.10400 秒有交易(100 次更新)
0.088 秒(每次更新 0.00088) 0.091 秒 0.052 秒 0.101 秒有交易(1000 次更新)
0.08900 秒(每次更新 0.000089) 0.15000 秒 0.11000 秒 0.09100 秒我的结论是,transactions
在time cost per query
中毫无意义。也许随着更新数量的增加,时间会变得更大,但我对这些数字不感兴趣。 单个事务的 10 和 1000 次更新之间几乎没有时间成本差异。但是我想知道这是否是我机器上的硬件限制并且不能做太多。即使使用 WAL,我似乎也无法使用单个事务和 10-1000 次更新低于 ~100
毫秒。
在没有事务的情况下,固定时间成本约为0.025
秒。
【问题讨论】:
@Olaf,唯一的C++
是std::string
;其余的是C
。我在上面特别强调了这一点。其次,我不希望有人审查我的代码,我想要一种更好的 SQLite 方法来解决我的问题
请停止编辑错误的标签!要么提供 C minimal reproducible example,要么留下 C++ 标签!请注意,您的问题可能会有不同的答案,具体取决于语言。
你已经知道最快的方法是使用单个事务。
@CL。是的,交易减少了x10
的时间,但我的时间不是仍然很慢吗?我的意思是,通常10
简单更新以处理100ms
的交易?还是我做错了什么?
@user6096479:你为什么要为准备好的语句的创建计时?对sqlite3_prepare_v2
的调用不应是正在计时的代码的一部分。
【参考方案1】:
您可能仍会受到提交事务所需时间的限制。在您的第一个示例中,每个事务大约需要 0.10 才能完成,这非常接近插入 10 条记录的事务时间。如果您在单个事务中批处理 100 或 1000 次更新,您会得到什么样的结果?
此外,SQLite 预计平均硬盘驱动器每秒大约有 60 个事务,而您只得到大约 10 个。您的磁盘性能可能是这里的问题吗?
https://sqlite.org/faq.html#q19
【讨论】:
我是否因为硬盘速度和同步模式等待 SQLite 验证写入的数据而受到限制?因此,如果我选择安全而不是性能,那么对于使用典型 7200 磁盘的更新查询,我是否仅限于~10 ms
?我没有测试 100 或 1000 次更新,因为我的应用程序每个事务最多只能处理 15-20 个查询(中间有一些 SELECT),所以我模拟了这个问题的场景。一周前我整理了我的磁盘,我会再做一次并回复:)
不,时间仍然在0.10 to 0.14
secs 10 个查询的事务。【参考方案2】:
数据量这么少,数据库操作本身的时间是微不足道的;您测量的是事务开销(强制写入磁盘所需的时间),这取决于操作系统、文件系统和硬件。
如果您可以忍受它的限制(主要是没有网络),您可以通过启用 WAL mode 来使用异步写入。
【讨论】:
我通过设置PRAGMA synchronous = NORMAL
和PRAGMA journal_mode = WAL
尝试了WAL
,但我没有得到任何改进,我的意思是。我根本不需要网络,我想从 WAL 中受益,我只是没有任何收获(也许需要一些额外的选项?!?)。另一方面,我只需要update
每最多 1 秒最多 20 个查询。它们(查询)可能更少但不能更多。在我看来,使用synchronous = NORMAL or FULL
无法打破10 ms / update query
的障碍,除非我选择将句柄交给操作系统,从而降低安全性。
检查WAL是否开启,执行PRAGMA journal_mode;
。
我不知道为什么,但我的PRAGMA journal_mode = WAL
在我的 C 调用中根本没有被查询,所以模式仍然是 DELETE
。当我在phpLiteAdmin
上使用查询时,正常执行。但是我的时间略微减少到大约0.07 - 0.11
,这似乎还可以。【参考方案3】:
尝试将 INDEXE 添加到您的数据库中:
CREATE INDEX IDXname ON player (name)
【讨论】:
使用索引会对大表产生巨大影响以上是关于提高 SQLite 的每秒更新性能?的主要内容,如果未能解决你的问题,请参考以下文章