提高 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 秒

我的结论是,transactionstime 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 = NORMALPRAGMA 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 的每秒更新性能?的主要内容,如果未能解决你的问题,请参考以下文章

Sqlite3写性能优化-每秒百万条写入

提高 SQLite 性能

如何提高 Python 3.6 中的 SQLite 插入性能?

如何提高 SQLite 数据库的性能?

Sqlite3插入性能测试

将大型 python 列表传递到 SQLite SELECT 语句时如何提高性能?