6

我使用Delphi XE2DISQLite v3(基本上是SQLite3的一个端口)。我喜欢 SQLite3的一切,除了缺乏并发编写,尤其是我在这个项目中广泛依赖多线程 :(

我的分析器明确表示我需要对此做一些事情,所以我决定使用这种方法:

  1. 每当我需要在 DB 中插入一条记录时,我不是执行 INSERT,而是write在一个特殊的文件夹中进行 SQL 查询,即。

    WriteToFile_Inline(SPECIAL_FOLDER_PATH + '\' + GUID, FileName + '|' + IntToStr(ID) + '|' + Hash + '|' + FloatToStr(ModifDate) + '|' + ...);

  2. 我添加了一个timer(在主应用程序线程中)每分钟触发一次,解析这些文件,然后使用事务插入查询。

  3. 最后删除那些临时文件。

结果是我获得了500% 的性能增益另外,这种技术是ACID,因为我总是可以SPECIAL_FOLDER_PATH在电源故障后扫描并执行我找到的 INSERT。

尽管结果很好,但我对使用的方法不是很满意(至少可以说是骇人听闻的),我一直在想,如果我可以拥有一个泛型——比如具有快速查找访问、线程安全、ACID 列表,这将是更清洁(可能更快?)

所以我的问题是:你知道 Delphi XE2 的类似内容吗?


PS。我相信许多阅读上面代码的人都会感到震惊,并且会开始侮辱我!请成为我的客人,但如果您知道更好(即更快)的 ACID 方法,请分享您的想法

4

3 回答 3

5

您将插入发送到队列的想法非常好,这将重新排列插入,并通过准备好的语句加入它们。在主线程或单独的线程中使用计时器取决于您。它将避免任何锁定。

不要忘记使用事务,然后例如每 100/1000 次插入提交一次。

关于使用 SQLite3 的高性能,请参阅这篇博客文章(以及下图)

速度比较

在此图中,最佳性能(文件关闭)来自:

  • PRAGMA synchronous = OFF
  • 使用准备好的语句
  • 交易内部
  • 在 WAL 模式下(尤其是在并发模式下)

您也可以更改页面大小或期刊大小,但上面的设置是最好的。见https://stackoverflow.com/search?q=sqlite3+performance

如果您不想使用后台线程,请确保 WAL 处于开启状态,准备您的语句,使用批处理,并重新组合您的进程以尽快释放 SQLite3 锁。

就像我们对mORMot所做的那样,通过添加客户端-服务器层可以实现最佳性能。

于 2013-01-06T09:54:46.883 回答
3

使用文件,您组织了一个具有持久性的异步作业队列。它允许您避免one-by-one和使用batch(记录组)方法来插入记录。比较one-by-onebatch

  • 首先对每条记录以自动提交模式(可能)工作,其次将批处理包装到单个事务中并提供最大的性能增益。
  • 每次需要插入记录(可能)时,首先准备一个 INSERT 命令,然后每批准备一次,然后按值增益提供第二个。

我不认为,SQLite 并发在您的情况下是一个问题(至少不是主要问题)。因为在 SQLite 中,单次插入的速度相当快,而且您会在高工作负载下遇到并发性能问题。使用其他 DBMS(如 Oracle)可能会得到类似的结果。

要改进您的batch方法,请考虑以下事项:

  • 考虑将journal_mode设置为WAL并禁用shared cache mode
  • 使用后台线程来处理您的队列。不是固定的时间间隔(1 分钟),而是SPECIAL_FOLDER_PATH更频繁地检查。如果队列有超过 X Kb 的数据,则开始处理。或者使用排队记录和事件的计数来通知线程,队列应该开始处理。
  • 使用准备好的多记录INSERT而不是单记录INSERT。您可以为 100 条记录构建一个 INSERT 并在一个批次中处理您的队列数据,但需要 100 条记录。
  • 考虑写入/读取二进制字段值而不是文本值。
  • 考虑使用一组具有预分配大小的文件。
  • ETC
于 2013-01-06T05:36:46.327 回答
1

sqlite3_busy_timeout效率很低,因为当它等待的表被解锁时它不会立即返回。

我会尝试创建一个关键部分(TCriticalSection?)来保护每个表。如果您在插入行之前进入临界区并在之后立即退出,您将创建比 SQLite 提供的更好的表锁。

但是,在不了解您的访问模式的情况下,很难说这是否比将一分钟的插入批量处理到单个事务中更快。

于 2013-01-06T04:37:22.613 回答