具有并发写入功能的 SQLite 替代品 (Delphi)

8

我正在使用DISQLite(SQLite的Delphi端口)与我的多线程客户端应用程序(尚未发布,因此我可以更改DB引擎,如果我确实需要)。

我的分析器清楚地表明这是一个愚蠢的决定,我将其追溯到2-3个非常简单的SQL语句,当在单线程应用程序中执行时,它们可以很快完成,但由于线程锁定/等待(SQLite在多个线程同时尝试写入时确实表现不佳)

我尽力优化了我的代码/避免瓶颈,但经过几周的努力工作后,我现在想知道是否更容易放弃SQLite并选择其他DB引擎(?)

我的要求是:

  1. ACID
  2. 非常好的同时读/写(记录级别)支持
  3. (非常)快速和稳定的DB引擎
  4. B树
  5. Delphi 2010支持

我只使用基本的INSERT / UPDATE / DELETE命令与索引,没有什么花哨的东西。因此,我的SQL要求相对基本(我不需要连接或其他“更高级”的SQL内容)。

我也开放NQL解决方案,只要它支持上述要求即可。

我的研究导致了Berkley DB,如果我理解正确,它是SQLite的修改版本,支持并发写入,但问题是它不适用于Delphi。

我还阅读了有关Kyoto Cabinet的文章,但再次没有Delphi支持:(

任何建议都将不胜感激,

谢谢!


我对Delphi绑定感到不确定,但您可以看看http://en.wikipedia.org/wiki/Berkeley_DB(拥有大多数其他绑定)。 - Joachim Isaksson
1
@JoachimIsaksson:谢谢,是的,我确实在维基百科上查看了BDB,我找到了这个链接:http://www.demonak.com/delphi/berkeleydb.en.shtml和“AnyDAC for Delphi Berkeley DB支持”,但我不确定这是否是我想要的(更不用提许可证成本了...:( ) - TheDude
我用它来做开源项目,但是在你的问题中没有看到你是否指定了开源或非开源,但是我确实可以看到Oracle许可成本可能会成为一个问题。不过对于处于相同情况下的开源项目来说还是很不错的 :) - Joachim Isaksson
@JoachimIsaksson:很抱歉,我的应用程序是闭源/商业化的。 - TheDude
6个回答

3

如果:

  • 您为所有线程仅使用一个数据库连接;
  • 您使用全局临界区保护您的数据库连接访问。

那么您的应用程序速度是多少?然后您可以尝试我们的 Sqlite3静态绑定,它是在未编译线程互斥锁的情况下编译的:

#define SQLITE_THREADSAFE 2
//  assuming multi-thread safety is made by caller - in our framework, there is
// only one thread using the database connection at the same time, but there could
// be multiple database connection at the same time (previous was 0 could be unsafe)
#define SQLITE_OMIT_SHARED_CACHE 1
// no need of shared cache in a threadsafe calling model

我们在mORMot ORM框架中使用这样的模型,并且与四个级别的缓存相关:
  • 语句缓存用于重复使用SQL语句,并动态绑定参数;
  • 全局JSON结果缓存位于数据库层,会在任何INSERT / UPDATE时全局刷新;
  • 调整过的记录缓存位于服务器端指定表格或记录的CRUD / RESTful级别;
  • 调整过的记录缓存位于客户端指定表格或记录的CRUD / RESTful级别。
结果性能并不差 - 它可以很好地扩展到多线程访问中,即使存在全局关键部分。当然,SQlite3的设计并不像Oracle那样良好扩展!但我已经在实际应用中使用了SQlite,有很多客户端。您可以考虑使用具有更复杂(和调整)的客户端 - 服务器体系结构的FireBird
关于加快写作速度,您可以将您的文章分组为一个事务,这样会更快。这就是我使用 加速写作 的方法,您可以通过多个客户端扩展这个概念:在服务器端,您将您的写入重新分组到一个共享事务中,在超时时间(例如一秒钟)后提交该事务。
对于这种添加操作,SQLite3非常快(甚至使用绑定参数的预准备INSERT语句更快),但对于单个添加操作则很慢,因为它必须使用低级API锁定整个文件,这非常慢。为了使其符合ACID标准,确保提交总是被处理。实际上,其他数据库引擎通过类似的在后台隐藏的过程实现良好的并发速度。SQLite3默认的写入方法被期望是这样的,以确保多个进程可以访问同一个文件 - 但在您的客户端-服务器应用程序中,您只需依赖于您将是唯一访问SQLite3数据库文件的人,因此它将是安全的。

谢谢,使用WAL时读取性能很好,但我的问题实际上是并发写入,可悲的是SQLite不支持:( - TheDude
关于我的应用程序性能,单线程非常慢。我尝试仅在必要时使用临界区(仅在每个线程的开头和结尾各一次 - 仅用于写入数据库)。你的帖子中有很多好东西,我会研究一下,非常感谢! - TheDude
关于加快写入速度,您可以将您的写作分组成一个事务,这样它会快得多。这就是我在http://blog.synopse.info/post/2011/06/03/BATCH-sequences-for-adding/updating/deleting-records中使用的方法,您可以通过多个客户端扩展此概念:在服务器端,您将您的写入重新分组到共享事务中,在超时期后提交(例如一秒钟)。SQLite3非常适合这种添加操作(甚至使用绑定参数的准备好的INSERT语句更快),但对于单个添加操作则较慢。这就是其他客户端-服务器数据库所做的事情。 - Arnaud Bouchez
1
我还发现定义独占访问模式能够显著提高读写速度的性能,但是你的进程应该是唯一访问文件的。请参见http://blog.synopse.info/post/2013/06/14/SQLite3-performance-in-Exclusive-file-locking-mode和http://www.sqlite.org/pragma.html#pragma_locking_mode。 - Arnaud Bouchez

2

有趣...Firebird 确实似乎是一个不错的选择。我会研究一下(不知道在速度方面和 SQLite 相比如何...)。 - TheDude
@Gdhami:我都用过,虽然嵌入式FB版本稍微重一些比SQLite,但它具有完整版FB的所有功能。当然,除了单用户与多用户之外。一旦引擎加载到内存中,我预计速度差异将是很小的。 - Vivian Mills

1

顺便说一下,我最终决定坚持使用DISQLite以及这个“丑陋”的hackish解决方案:

  • 进行了一些(不太小的)更改,尽可能地减少在线程内写入数据库的次数(每个线程需要两个数据库插入操作)

  • 当我必须在线程内工作时写入数据库时,我将SQL查询参数取出并将它们写入一个特殊的文件夹中(写入文件非常快),即。

C:\my-project\pending-sql\insert_SOME-GUID.txt

每个文件看起来都像这样:
Param1|Param2|Param3|Param4|
一旦我完成了线程(或者如果我的应用程序崩溃),我调用了一个例程,扫描了这个文件夹,提取了SQL参数,并使用准备好的语句运行它们(包装在事务中)。
任何包含少于4个参数的文件都将被视为损坏并将被跳过。
这是一个非常丑陋的算法(对不起!),但它有效,快速,(有点)ACID,并且我不必花费数月时间学习可能(或可能不)适合的另一个DB引擎。
我只想感谢大家的帮助,时间压力使我无法切换到另一个DB引擎,至少对于这个项目来说。

1

只需将您的表(可以同时编写)拆分为单独的SQLite数据库文件,并使用主连接将它们附加在一起即可。


谢谢,是的,我确实做了类似的事情(没有附加它们:这意味着失去ACID性,因为我正在使用WAL)...它确实有所帮助 - 很多,但我的分析器显示一些非常简单的SQL查询在多线程模式下需要20秒以上才能执行,而在单线程模式下只需要不到0.01毫秒!(这不是典型情况,但足以拖慢整个操作) - TheDude
你确定你没有做错什么吗?同样的查询之间有如此大的差异非常奇怪和不太可能。另外一个解决方案是停止使用WAL。 - Linas
是的。我已经向DISQLite的作者发送了一条关于这些缓慢查询的消息,我们会看看情况如何。 - TheDude

0

谢谢!实际上去年夏天我从Absolute转向了DISQLite(已经使用了6年)。据我所知,使用AbsoluteDB时,您要么具有并发写入支持,要么具有ACID性,但两者都不能兼备。 - TheDude
我同时使用了v5和v6(都用于D2010和D7)。 - TheDude

0

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