是否有任何技术/建议可以强制执行唯一约束?是的,我们可以创建唯一的密钥,但我们无法更改密钥和密钥,而且这种方法也不适用于复杂的验证(单独的独特登录,单独的独立电子邮件等等)
例如,帐户应该有 独特的登录和电子邮件得出一把钥匙 从这个领域将导致 不一致性:
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"}
哎呀,现在我们有两个登录帐户:“mary”
答案 0 :(得分:24)
核心答案
为具有您想要保持唯一字段的文档构建 POST / PUT ,如下所示:
创建view。在地图功能中,使用您要强制执行的字段作为键。价值可以是没有。使用 reduce功能计算每个键的计数。最佳方式(性能)是使用内置的_count reduce函数。
在 PUT / POST 将新文档放入数据库后,立即抓住返回的id
和rev
以及 GET / ?yourdb / _design / yourapp / _view /视图名组=真安培;键=" 值的-您的唯一场-从步1 "
如果上一个 GET 的结果为您提供了 1 以外的计数值,那么您只需插入一个副本。立即删除 /yourdb/id-from-step-2?rev=rev-from-step-2.
放松
粗略示例
假设您要存储用户帐户,并且您希望确保电子邮件地址是唯一的,但您不希望将其设为文档ID(无论出于何种原因)。构建视图以快速检查电子邮件地址的唯一性,如上所述。让我们称之为电子邮件。它会有一个地图功能可能与此类似......
function(doc) {
if(doc.type === 'account') {
emit(doc.email, 1);
}
}
仅_count
作为 reduce函数。如果您发出 1 ,如上所述_sum
也可以。如上所示拥有并检查您的文档上的type
字段只是一个惯例,但我发现它有时很有帮助。如果您要存储的只是用户帐户,那么您可能不需要它。
现在假设我们正在插入一个类似的文档......
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
,则您可能只是插入了重复的电子邮件。因此,删除文档并按电子邮件地址必须唯一的方式返回错误,类似于典型的SQL数据库响应,或者您想要的任何内容。
删除会像这样......
DELETE /mydb/7c5c249481f311e3ad9ae13f952f2d55?rev=1-442a0ec9af691a6b1690379996ccaba6
简短讨论(如果您关心)
如果你来自一个好的旧* SQL背景,这似乎是错误和奇怪的。在你翻开之前考虑这些要点。对字段进行约束(例如唯一性或其他任何内容)意味着架构。 CouchDB是无模式。 来自* SQL意味着你必须改变你的想法。 ACID和Locks不是唯一的方法。 CouchDB具有很大的灵活性和强大功能。如何使用它来获得你想要的东西将取决于你的用例的细节以及你如何摆脱传统的关系数据库思维的限制。
我之所以有时以这种方式实现唯一性,是因为它对我来说感觉非常 Couchy 。如果您考虑CouchDB处理更新冲突等的方式,这种方法遵循相同的流程。我们存储了我们提供的文档,然后检查我们的字段是否是唯一的。如果没有,抓住我们刚刚插入的方便手柄插入的文档,并做一些事情来解决对唯一性的需求,比如删除它。
您可能会在上面的示例中尝试在您POST
文档之前检查电子邮件地址的唯一性。小心!!如果有多个事情发生,可能会在之后 中将具有相同电子邮件地址的另一个文档插入到数据库中,检查电子邮件是否存在,但之前你做POST
!这将给你留下重复的电子邮件。
在POST
之前运行电子邮件地址检查没有任何问题。例如,如果您的用户填写表单并且您可以将电子邮件字段值输出到数据库,这是一个好主意。您可以预先警告用户该电子邮件地址存在或阻止提交等。但是,在所有情况下,您也始终检查之后的唯一性 {{ 1}} doc。然后根据需要做出反应从UI方面来看,上面的步骤看起来与传统的* SQL数据库在唯一性约束上的结果没有什么不同。
可以出问题吗?是。考虑一下。假设数据库中尚不存在 joe@example.org 。几乎同时有两个文档保存到数据库中。 Doc A 是POST
,然后 Doc B 是POSTed
,但在我们能够检查 Doc A的唯一性之前 POSTed
。现在,当我们对 Doc A POST
进行唯一性检查时,我们发现 joe@example.org 在数据库中两次。因此,我们将删除它并报告问题。但是,我们可以在删除 Doc A 之前说, Doc B 的唯一性检查也会发生,为 joe@example.org 。现在两个POST
都将被拒绝,即使 joe@example.org 最初实际上不在数据库中!换句话说,如果两个具有匹配值的文档几乎同时进入您的应用,其可能他们可以看到彼此POSTs
并错误地断定他们携带的值是已经在数据库中了!我们无法真正阻止这种情况,因为CouchDB中没有传统的RDB样式锁定。但是为了换取这么小的价格,我们获得了主 - 主复制和大量其他很酷的东西。我接受了!如果这对于您的实现来说是一个巨大的问题,您可以尝试通过实现某种重试机制等来解决它。
最后,CouchDB确实是一个数据库的宝石,但不要只是采取这种方式并期望它是一种防弹方法。很多事情都取决于具体应用的细节。
答案 1 :(得分:15)
这是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" }
创建或更新用户将采取以下步骤:
第3步会考虑一下。例如,您不希望为未更改的字段尝试声明唯一值。你可以,但是你必须加入一些额外的逻辑来处理一个你已经拥有该值的用户声明一个值的情况。
第3步也是让人们接受旧版索赔价值的好地方。例如,如果一个用户“释放”了用户名kurt,我可以在验证它已不再使用之后更新该特定文档以指向我的新密钥。另一种方法是在变化时清除声称的唯一值。我不确定哪种工作会少,真的。留下陈旧的声称价值观对我来说是最有意义的。
这个解决方案的有趣之处在于,一旦创建它们,您就不需要使用这些指针文档。您可以在用户文档上正常创建视图,并使用它们通过电子邮件或用户名进行查询。
此设置还允许您进行关系,而无需担心级联用户密钥。我不了解您,但我的用户文档几乎被系统中的每个其他文档引用。改变用户密钥将是一个巨大的痛苦。
答案 2 :(得分:9)
这取决于。考虑多主复制的情况,每个主服务器中可能存在一致的冲突条目,但一旦复制就不一致。您可能只使用一个couchdb服务器,但通常他们假设一个多主机案例设计它,并且不要放入任何只能在单个未复制服务器中正常工作的功能。
如果您只关心单个服务器案例,可以想象您可以通过网络支持重建您的couchjs并在validate_doc_update()
函数中执行http查询,该查询将对数据库执行查询以查看电子邮件地址是否已经已经使用过,如果是这样,则更新失败。检查here以获取有关验证机制的更多详细信息。我不建议这样做,而是我会在id字段中嵌入所有唯一性(直接或通过散列),如果用户更改了影响该文档的任何内容,则只处理移动文档。
答案 3 :(得分:0)
CouchDB具有“更改提要” http://docs.couchdb.org/en/2.3.1/api/database/changes.html#changes
更新动作与新动作相同,只是_rev和对象的'new'属性不同。
过滤器功能类似于列表或显示功能,但在面向“更改队列操作”方面有所不同 http://docs.couchdb.org/en/2.3.1/ddocs/ddocs.html#filterfun
设计文档时,请考虑能够过滤更改队列中所有添加内容的功能。