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