27

是否有任何技术/建议来强制执行独特的约束?是的,我们可以创建唯一的密钥,但我们不能更改密钥和密钥,而且这种方法不适合复杂的验证(单独的唯一登录名、单独的唯一电子邮件等)

例如,帐户应具有唯一的登录名和电子邮件。从此字段派生密钥将导致不一致:

key1: "Account-john@example.net-john", { email: "john@example.net", login: "john"}
key2: "Account-mary@example.net-mary", { email: "mary@example.net", login: "mary"}

看起来不错,但是:

key1: "Account-john@example.net-mary", { email: "john@example.net", login: "mary"}
key2: "Account-mary@example.net-mary", { email: "mary@example.net", login: "mary"}

糟糕,现在我们有 2 个登录帐户:“mary”

4

4 回答 4

26

核心答案

为具有要保持唯一的字段的文档构建POST/PUT ,如下所示:

  1. 创建一个视图。在map 函数中,使用要强制唯一的字段作为。价值可以什么都不是。使用reduce 函数获取每个键的计数。最好的方法(性能)是使用内置的_count减少功能。

  2. 在您将新文档PUT/POST到数据库后,立即获取返回的和idGET / yourdb/_design/yourapp/_view/viewname?group=true&key=" value-of-your-unique-field-from-step-1 ”。rev

  3. 如果最后一个GET的结果为您提供了1以外的计数值,那么您只是插入了一个副本。立即删除/yourdb/ id-from-step-2 ?rev= rev-from-step-2

  4. 放松


粗略的例子

假设您正在存储用户帐户,并且您想确保电子邮件地址是唯一的,但您不想将其设为文档 ID(无论出于何种原因)。如上所述,构建一个视图以快速检查电子邮件地址的唯一性。让我们称之为电子邮件。它将具有可能类似于此的地图功能...

function(doc) {  
  if(doc.type === 'account') {
    emit(doc.email, 1);
  }
}

就像reduce 函数_count一样。如果你发出一个像上面的1也可以。如上所示,在您的文档上拥有并检查一个字段只是一种约定,但我发现它有时很有帮助。如果您存储的只是用户帐户,那么您可能不需要它。_sumtype

现在让我们说我们正在插入一个像这样的文档......

POST /mydb/
{
  "name": "Joe",
  "email": "joe@example.org"
}

CouchDB 会以类似...

{
  ok: true,
  id: 7c5c249481f311e3ad9ae13f952f2d55,
  rev: 1-442a0ec9af691a6b1690379996ccaba6
}

检查我们现在在数据库中是否有多个 joe@example.org ...

GET /mydb/_design/myapp/_view/emails/?group=true&key="joe@example.org"

CouchDB 会以类似...

{
  rows: [
    {
      key: "joe@example.org",
      value: 1
    }
  ]
}

如果value不是1该回复中的任何内容,您可能只是插入了重复的电子邮件。因此,删除文档并返回错误,如Email Address must be Unique,类似于典型的 SQL 数据库响应,或任何您想要的。

删除会是这样的......

DELETE /mydb/7c5c249481f311e3ad9ae13f952f2d55?rev=1-442a0ec9af691a6b1690379996ccaba6

简短讨论(如果你关心的话)

如果您来自一个良好的旧 *SQL 背景,这将看起来是错误的和奇怪的。在你翻出来之前考虑这些点。对字段有约束,例如唯一性或其他任何内容,都意味着模式。CouchDB 是无模式的。来自*SQL 意味着您将不得不改变您的思维方式。ACID 和锁不是唯一的方法。CouchDB 具有很大的灵活性和强大的功能。你如何使用它来获得你想要的东西将取决于你的用例的细节以及你能在多大程度上摆脱传统关系数据库思维的限制。

我有时以这种方式实现唯一性的原因是因为它对我来说非常Couchy。如果您考虑 CouchDB 处理更新冲突等的方式,这种方法遵循相同的流程。我们存储给定的文档,然后检查我们的字段是否是唯一的。如果没有,请抓住我们刚刚插入的那个文档,我们仍然拥有它的方便句柄,并做一些事情来解决对唯一性的需求,例如删除它。

在上面的示例中,您可能会在查看文档之前检查电子邮件地址的唯一性。POST当心!!如果发生了多件事情,则可能您检查电子邮件是否存在之后,但您执行POST! 这会给您留下一封重复的电子邮件。

在您之前检查电子邮件地址也没有错POST。例如,如果您的用户正在填写表单并且您可以将电子邮件字段值 ajax 到数据库,这是一个好主意。您可以预先警告用户该电子邮件地址存在或阻止提交等。但是,在所有情况下,您还应该始终检查文档后的唯一性POST然后根据需要做出反应。从 UI 端的角度来看,上述步骤看起来与传统 *SQL 数据库在唯一性约束上的结果没有什么不同。

会出什么问题吗?是的。考虑一下。假设数据库中不存在joe@example.org 。两个文档几乎同时进入以保存到数据库中。Doc APOSTed,然后Doc BPOSTed,但在我们能够检查Doc A POST的唯一性之前。所以现在当我们对Doc A POST进行唯一性检查时,我们看到joe@example.org在数据库中出现了两次。因此,我们将删除它并报告问题。但是让我们说在我们可以删除Doc A之前, Doc B的唯一性检查也会发生,为joe@example.org获得相同的计数值。现在两者POSTs将被拒绝,即使joe@example.org最初实际上不在数据库中!换句话说,如果两个具有匹配值的文档几乎同时进入您的应用程序,他们可能会看到彼此POSTs并错误地得出他们携带的值已经在数据库中的结论!我们无法真正阻止这种情况,因为 CouchDB 中没有传统的 RDB 样式锁定。但是为了换取这么小的价格,我们得到了主主复制和大量其他很酷的东西。我要买它!如果这对您的实现来说是一个大问题,您可以尝试通过实现某种重试机制等来解决它。

最后,CouchDB 确实是数据库的瑰宝,但不要仅仅接受它并期望它是一种防弹的方法。很多事情实际上取决于您的特定应用程序的细节。

于 2014-03-16T17:56:43.973 回答
17

这是 CouchDB 中不太有趣的部分之一。我发现处理可以更改的唯一字段(例如在您的用户示例中)的最佳方法是创建“指针”文档,并将唯一值作为键的组成部分,然后使用它们让您声明唯一值。这样做的重要部分是为主文档提供可预测的键,然后在保存主文档之前保存唯一字段声明文档(并让键冲突阻止主文档保存)。

给定具有唯一用户名和唯一电子邮件的用户,您的主要文档可能如下所示:

user-1234: { username: "kurt", email: "kurt@localhost" }
user-9876: { username: "petunia", email: "pie@crust.com" }

唯一字段指针看起来像这样:

user-username-kurt: { primary_doc: "user-1234" }
user-email-kurt@localhost: { primary_doc: "user-1234" }
user-username-petunia: { primary_doc: "user-9876" }
user-email-pie@crust.com: { primary_doc: "user-9876" }

创建或更新用户将采取以下步骤:

  1. 准备您的用户文档,如有必要,为其生成密钥
  2. 为每个更改的唯一字段保存一个“指针”文档
  3. 如果保存其中任何一个失败,请停止并修复错误
  4. 保存主要用户文档

第 3 步需要一些思考。例如,您不想尝试为未更改的字段声明唯一值。您可以,但是您必须添加一些额外的逻辑来处理您为已经拥有该价值的用户声明价值的情况。

第 3 步也是让人们采用旧的声称值的好地方。例如,如果一个用户“释放”了用户名 kurt,我可以在验证它不再使用后更新该特定文档以指向我的新密钥。另一种方法是在声明的唯一值更改时清除它们。我不确定哪个工作会更少,真的。留下陈旧的声称价值对我来说最有意义。

这个解决方案的有趣之处在于,一旦创建了这些指针文档,您就不需要将它们用于任何内容。您可以像往常一样在用户文档上创建视图,并使用它们通过电子邮件或用户名进行查询。

此设置还允许您建立关系,而不必担心级联用户密钥。我不了解您,但系统中几乎所有其他文档都引用了我的用户文档。更改用户密钥将是一个巨大的痛苦。

于 2009-12-19T17:42:58.073 回答
9

这取决于。考虑多主机复制的情况,可能会在每个主机中一致地添加冲突条目,但一旦它们复制就不一致。您可能只使用一个 couchdb 服务器,但通常他们在设计它时假设采用多主机情况,并且不会添加任何只能在单个未复制服务器中正常工作的功能。

如果您只关心单个服务器的情况,可以想象,您可以使用网络支持重建您的 couchjs,并在您的validate_doc_update()函数中执行一个 http 查询,该查询将对数据库执行查询,以查看电子邮件地址是否已被使用,如果更新失败所以。在这里查看有关验证机制的更多详细信息。我不建议这样做,而是将所有唯一性嵌入到 id 字段中(直接或通过散列),如果用户更改了任何影响它的内容,则只需处理移动文档。

于 2009-10-18T14:16:07.593 回答
0

CouchDB 有“更改提要” http://docs.couchdb.org/en/2.3.1/api/database/changes.html#changes

更新操作与新操作相同,只是 _rev 和对象的“新”属性不同。

过滤器功能类似于列表或显示功能,但在面向“更改队列操作”方面有所不同 http://docs.couchdb.org/en/2.3.1/ddocs/ddocs.html#filterfun

设计文档,考虑过滤任何添加到更改队列的能力。

https://docs.couchdb.org/en/master/ddocs/index.html

于 2019-07-09T16:03:13.930 回答