17

考虑到以下情况,什么主键策略最适合用于关系数据库模型?

  • 数以万计的用户
  • 每个用户有多个客户端(手机、平板电脑、台式机)
  • 每张表数百万行(不断增长)

Azure SQL 将是中央数据存储,将通过 Web API 公开。客户端将包括一个 Web 应用程序和许多本机应用程序,包括 iOS、Android、Mac、Windows 8 等。Web 应用程序将需要“始终在线”连接,并且没有本地数据存储,而是检索和更新通过 api - 通过 RESTful API 思考 CRUD。

所有其他客户端(手机、平板电脑、台式机)都将具有本地数据库(SQLite)。首次使用此类客户端时,用户必须进行身份验证和同步。一旦经过身份验证和同步,这些客户端就可以在离线模式下运行(在本地 SQLite 数据库中创建、删除和更新记录)。这些更改最终将与 Azure 后端同步。

数据库的分布式特性给我们留下了一个主键问题以及提出这个问题的原因。

以下是我们迄今为止所考虑的:

图形用户界面

每个客户端都创建自己的密钥。在同步时,重复密钥的可能性很小,但我们需要通过将功能写入每个客户端来使用新密钥更新所有关系来解决这个问题。GUID 很大,当考虑到每个表有多个外键时,随着时间的推移,存储可能会成为一个问题。可能最大的问题是 GUID 的随机性,这意味着由于碎片,它们不能(或不应该)用作聚集索引。这意味着我们需要为每个表创建一个聚集索引(可能是任意的)。

身份

每个客户端都创建自己的主键。同步时,这些密钥将替换为服务器生成的密钥。这给同步过程增加了额外的复杂性,并迫使每个客户端“修复”他们的键,包括相关表上的所有外键。

合成的

在第一次同步时,每个客户端都会分配一个客户端 ID。此客户端 ID 与本地自动递增 ID 结合使用,作为每个表的复合主键。此复合键将是唯一的,因此同步时不应该有冲突,但这确实意味着大多数表都需要复合主键。性能和查询复杂性是这里关注的问题。

HiLo(合并复合)

与复合方法一样,每个客户端在第一次同步时都被分配一个客户端 ID (int32)。客户端 ID 与唯一的本地 ID (int32) 合并到一个列中,以形成应用程序范围内的唯一 ID (int64)。这应该不会导致同步期间发生冲突。由于每个客户端生成的 id 是连续的,因此这些键与 GUID 相比有更多的顺序,但会有数千个唯一的客户端 ID,那么我们是否仍然冒着聚集索引碎片的风险?

我们是否忽略了什么?还有其他值得研究的方法吗?讨论每种方法的优缺点会很有帮助。

4

2 回答 2

2

我仔细考虑了这个问题,最终决定 GUID 通常是最好的解决方案。以下是有关原因的一些信息:

身份

Identity 选项听起来像是消除了所有负面因素,但是在实现了实现该系统的单页 Web 应用程序之后,我可以告诉您它增加了代码的大量复杂性。临时 id 可以很快地在您的客户端数据中传播,并且在查找每个可能的用法时,很难创建一个没有漏洞的系统。它通常会导致应用程序和数据特定的硬编码信息来跟踪客户端上的外键(随着数据库的更改并且您忘记更新此信息,这很乏味且容易出错)。它还为每次同步增加了很多开销,因为每次同步可能必须运行多个表来检查临时 ID。可能有更好的方法来实现这个系统,但我还没有看到一个没有的好方法

合成的

复合方法还在生成会话 id 和从中创建 id 时给您的代码增加了很多复杂性,除了您可以保证它是唯一的之外,它们并没有真正提供优于 GUID 的任何优势 - 但问题是,GUID 是理论上是独一无二的,虽然我害怕重复的可能性,但我意识到这是一个非常小的机会,实际上有一个非常简单的方法来处理它不是唯一的小可能性。

GUID

我对使用 GUID 的最大担忧是

  1. 它们的大小很大,不是传统的整数,这会使传输大量数据变慢并降低数据库性能
  2. 如果你真的遇到了冲突,它可能会毁掉你的应用程序,所以你必须编写复杂的代码来处理你可能永远不会使用的情况。

然后我意识到,在离线风格的 Web 应用程序中,您通常不会一次传输大量数据,因为它们都存储在客户端上。

您也不必担心服务器数据库性能,因为这是在后台同步完成的 - 您只需担心客户端数据性能。

最后,我意识到处理冲突真的是一件微不足道的事情。只需测试冲突,如果遇到冲突,请在服务器上创建一个新的 GUID 并继续操作。然后向客户端发送一条消息,导致客户端抛出一个小错误消息,然后删除所有客户端数据并从服务器重新下载。这非常快速且易于实施,而且您可能已经希望将其作为离线 Web 应用程序上的一种可能操作。虽然这对用户来说可能听起来不方便,但用户看到此错误的可能性几乎为 0%。

结论

最后,我认为对于这种类型的应用程序,GUID 是最容易实现的,并且在出错的可能性最小且不会创建过于复杂的代码的情况下工作得最好。

如果您的应用程序不必离线运行,但出于性能或其他原因您有客户端数据库,您还可以考虑抛出加载 gif 并暂停客户端执行,直到通过 ajax 从服务器返回 id。

于 2019-07-11T23:37:16.290 回答
0

要记住的关键(双关语)是为您存储在持久存储中的每个对象简单地拥有一个唯一的密钥。您如何处理该对象的存储完全取决于您以及您如何访问该密钥的方法。您列出的每个策略都有自己的原因,说明他们为什么要做他们所做的事情,但最终他们将某个对象的密钥存储在数据库中,因此可以更改其所有属性,同时在数据库中保留相同的对象引用.

于 2013-04-22T18:15:57.773 回答