小巧。快速。可靠。
三者选其二。

通过网络访问 SQLite,
注意事项与考量

引言

SQLite 库的用户,特别是希望从网络连接的不同系统访问 SQLite 数据库的应用程序开发人员,通常会倾向于简单地通过指定网络文件系统中某个位置的数据库文件的文件名来打开 数据库连接。(此处“远程数据库”)然后,通过允许模拟来自/到本地文件的 I/O 的操作系统 API 来访问此“文件”。这种模拟效果不错,但在某些重要方面并不完美。

这种简单的“远程数据库”方法通常不是从多个系统使用单个 SQLite 数据库的最佳方法(即使它看起来“可以工作”),因为它经常导致各种问题和麻烦。由于这些问题在某些使用情况下是不可避免的,但并不频繁或可重复,因此应用程序开发人员不应依赖早期测试的成功来判断他们的远程数据库使用是否会按预期工作。

远程数据库文件引起的问题

此图显示了组件及其链接,以便在后续讨论中参考

Client Application SQLite Database Engine Database File(s) SQLite API Calls DB Engine File I/O

问题源于上述三个块之间两个数据/控制通道的属性和利用。

通道流量

“API 调用”通道承载的信息少于“文件 I/O”通道。提交查询或指定数据修改的 API 调用通常需要来回传递的信息比特数远少于存储或查找数据而传输到/从数据库文件的比特数。查询结果检索通常需要比 API 流量更多的文件流量,因为要返回的数据很少能在不读取未请求的数据的情况下找到。

通道带宽

API 调用通道以处理器主内存速度(千兆字/秒)运行,数据通常通过引用传递(因此不会复制)。相比之下,即使是最快的文件 I/O 通道也较慢。它们需要复制数据,通常通过需要比特串行化的介质。对于旋转磁介质,传输需要等待盘片旋转和磁头移动,然后受旋转速度限制。

当文件 I/O 通道包含网络连接时(除了其远端的一些真实文件 I/O),会强加额外的缓慢。即使原始传输速率不限制带宽,流量也必须在两端进行分组和缓冲。额外的 I/O 处理程序层会增加调度延迟。但是,传输速度下降是网络文件系统中影响最小的一个问题。

通道可靠性

“API 调用”通道非常可靠,以至于错误率未说明且被忽略为可忽略不计。该通道仅在系统断电时才会发生故障(流星等情况除外)。

“文件 I/O”通道在直接到达本地存储设备时也高度可靠。(旋转存储 MTBF 超过 100 万小时,NVRAM 持续时间更长。)本地设备还具有一项特性,这对使数据库管理软件能够被设计为确保 ACID 行为至关重要:当所有进程对设备的写入都完成后(当 POSIX fsync() 或 Windows FlushFileBuffers() 调用返回时),文件系统要么已存储“写入”的数据,要么将在存储任何后续写入的数据之前存储该数据。

当网络文件系统设备和软件层置于文件系统客户端和实际存储设备上的文件系统之间时,会引入重要的故障和错误行为来源。虽然网络数据传输经过了良好的错误检查,但传输数据包并非在发送后都能可靠地到达目的地。一些数据包会被其他数据包覆盖,必须重新发送。在数据包覆盖的情况下,重复重试可能会导致延迟超过类似数据到达本地存储所需的延迟。客户端写入的部分内容最终可能会相对于写入的其他部分以不同顺序存储。

由于网络文件系统写入中发生的无序和数据丢失,因此至关重要的是,必须准确地知道一组文件写入何时完成,然后才能开始后续一组文件写入。通过使用稳健设计和正确实现的 fsync()(或等效)操作系统功能来获得此保证。不幸的是,对于某些应用程序而言,网络文件系统同步操作可能不如本地文件系统同步可靠。在网络数据包传输错误的情况下实现稳健的同步很困难,并且有时会为了性能而放松安全措施。

网络文件系统中的文件锁定也存在类似的风险。SQLite 依赖于写操作的独占锁,并且已知某些网络文件系统中的独占锁操作不正确。这导致了数据库损坏。随着此类锁的设计者更改其实现以适应更常见的用例,这种情况可能会再次发生。

最重要的是,网络文件系统同步和锁定的可靠性在不同的实现和安装之间有所不同。它所依赖的设计假设在应用程序测试所在位置可能比其依赖所在位置更真实。**以您(以及您的客户)的风险为代价依赖它。** 请参阅 如何损坏您的数据库文件

性能和可靠性问题

从上图和讨论可以明显看出,在两个通道之一中插入网络链接会降低性能(又称“速度”)。考虑 API 调用通道和文件 I/O 通道之间的相对流量,可以发现这种插入对 API 调用通道的影响较小。

可靠性影响的考虑更容易,结果也更清晰:在 API 调用通道中插入网络链接也可能导致有时调用失败。但是,如果客户端应用程序已费心正确使用 SQL/SQLite 事务,则此类故障只会导致事务失败并回滚,而不会损害数据完整性。相反,如果将网络链接插入文件 I/O 通道,则事务可能会失败(如 API 调用插入一样),但还会导致远程数据库损坏。

这些网络不可靠性问题可以通过在回滚模式下使用 SQLite 来完全或在可接受的程度上缓解。但是,SQLite 库没有在跨网络场景中进行测试,这也不合理。因此,使用远程数据库需要**用户自担风险**。

建议

通常,如果您的数据与应用程序通过网络分离,则您需要使用客户端/服务器数据库。这是因为数据库引擎充当数据库流量的带宽减少过滤器。

如果您的数据与应用程序通过网络分离,则您希望低流量链接跨越网络,而不是高流量链接。这意味着数据库引擎需要与数据库本身位于同一台机器上。客户端/服务器数据库(如 PostgreSQL)就是这样。SQLite 的不同之处在于数据库引擎与应用程序在同一台机器上运行,这在远程数据库场景中会导致高流量链接遍历网络。这通常会导致性能降低。

网络文件系统不支持在同时保持数据库一致性的同时进行同时读写。因此,如果您在多台不同的机器上有多个客户端需要同时进行数据库读写,则可以选择以下方法

1. 使用客户端/服务器数据库引擎。PostgreSQL 是一个极佳的选择。其变体为

2. 在 WAL 模式下托管 SQLite 数据库,但所有读写操作都来自存储数据库文件的同一台机器上的进程。实现一个在数据库机器上运行的代理,该代理中继来自远程机器的读/写请求。

3. 在 回滚模式下使用 SQLite。这意味着您可以有多个同时读取器或一个写入器,但不能同时读取器和写入器。

应用程序程序员应该意识到用户可能选择使用远程数据库的可能性,如果他们可以这样做的话。除非已执行上述选择之一,或一次一个,使用独占访问,否则程序员应考虑阻止该选择,除非可靠性不重要。

总结

选择适合您和您的客户的技术。如果您的数据位于与应用程序不同的机器上,则应考虑使用客户端/服务器数据库。SQLite 适用于数据和应用程序共存于同一台机器的情况。SQLite 仍然可以在许多远程数据库情况下使用,但在这种情况下,客户端/服务器解决方案通常会更好地工作。

此页面上次修改于 2022-06-22 21:14:29 UTC