43. 复制或保存冲突之保存篇

来源:互联网 发布:淘宝商城赚钱的吗 编辑:程序博客网 时间:2024/05/21 22:43
复制或保存冲突是Lotus Notes的一大独特问题。这些冲突之所以会产生,就在于Notes数据库最基本的设计之一就是副本独立读写,再通过复制保持数据一致。此外不像关系型数据库优先数据的一致性,读取要被改动的记录时会锁定这些记录,阻止同时有其他写入。Lotus Notes的文档锁定功能直到R6才被引入,并且仍然由于Notes数据库的分布式本质,应用并不广泛。
既然易于出现,我们这要好好地了解它们,以尽量减少这些麻烦。
原因
我们先来看保存冲突,因为它不必有副本就可能产生,比复制冲突更广泛。Lotus Notes帮助文档中对保存冲突的描述略有误导之嫌。
A save conflict occurs when two or more users open and edit the same document at the same time on the same server, even if they're editing different fields.
很多资料提到保存冲突产生的原因时,还会说是因为同时保存同一个文档,就更令人迷惑了。何谓同时?Notes数据库能并行地处理多个保存同一个文档的请求吗?文档在物理存储中总是唯一的,如何同时保存?同时的标准又是什么?时间上相差0.1秒算吗,还是0.1毫秒,0.1微秒?如果还是串行地先后保存,会产生冲突吗?【注1】
实际上,要了解这个保存冲突产生的原因,正可以借鉴复制冲突的原因。两个副本中的同一文档都被修改,再被复制的时候,肯定就会产生冲突,这很好理解。保存冲突其实也是同样的道理,某个文档被读取并修改,在这些变化被写回数据库之前,这个文档又被同一用户或其他用户(不必像帮助文档所写,限于两个或以上不同用户的情况,单个用户也可能产生冲突文档)读取,或有或无修改,再保存。这两次保存时间上较晚的一次就会产生冲突。不论这两次从读取到保存的会话的先后顺序如何,只要它们时间上有重叠,冲突的问题就产生了。或者从机制上说,当内存中的文档要被写回磁盘里对应的文档时,如果发现此时后者与前者被读取时的内容不同,也就是后者的最近修改时间不再是前者被读取时的最近修改时间,就意味着这个文档有了两个不同的版本。
冲突产生后,一个版本被选为主文档,其它则成为它的冲突文档。冲突文档会被添加两个域,一个$Conflict表明是冲突文档,另一个与普通响应文档相同的$Ref域记录主文档的全局Id。
情境
在客户端应用中,如果一个表单触发的代码会通过某种间接的方式修改保存当前文档(不是直接地通过NotesUIWorkspace.CurrentDocument的方式,而是比如经由某个视图修改的文档包括当前文档),然后用户再直接修改保存当前文档,就会产生冲突。与此相对的是,即使一个数据库没有选中锁定文档的选项,在客户端中如果有一个用户甲正在编辑某个文档,其他用户在试图编辑同一数据库副本里的该文档时,也会收到Lotus Notes的提示,甲正在编辑该文档,因此只能阅读不能编辑。即使如此,同一用户在同一客户端内也能很容易地触发冲突——在视图里选中一条文档,按Ctrl+E编辑,返回该视图再按Ctrl+E,Notes会在两个标签页窗口打开同一条文档的编辑界面(如果是直接双击视图里的文档,Notes会检查该文档是否已经在某个窗口打开,若是则直接转到该窗口。)。然后在两个窗口里先后保存,后者就会提示是否要将文档保存为冲突。
在传统的Notes Web应用中,在两个窗口中以不同用户身份打开同一文档,即使不修改就分别提交,后者也会产生一个冲突文档;如果两个窗口的用户相同,则不会产生冲突。文档生成的HTML有一个%%ModDate字段记录上次修改时间,如<input type="hidden" name="%%ModDate" value="48257D9A002451E9">,Domino将数据库里文档的上次修改时间与从表单提交的该时间做比较,以确定是否产生冲突【注2】。
应对措施
了解了保存冲突产生的原因,就可以在开发中尽量避免。除非在某些特殊场合,在调用文档的保存方法NotesDocument.Save()时,都会使用不创建冲突文档的参数选项。对于产生原因仍然不明的冲突文档,我们也需要有措施应对。可以建立一个选择条件为
SELECT @IsAvailable($Conflict)
的视图,有需要或不定时地检查是否有新的冲突文档。有些人会写一段脚本自动清除这些冲突文档。笔者比较谨慎,会先人工对比一下冲突文档和主文档有什么不同,再看看能不能找到产生的原因,之后再做相应的处理。
可以写一个代理来打开要检查的冲突文档的主文档。
%REMAgent agOpenMainDocument%END REMOption PublicOption DeclareSub InitializeDim ws As New NotesUIWorkspaceDim doc As NotesDocumentIf Not ws.CurrentView Is Nothing Then 'Call this agent from a viewSet doc=ws.currentView.Documents.Getfirstdocument()If doc Is Nothing ThenMsgBox "Please select a document for which the parent document you want to open."Exit Sub End IfElseIf Not ws.CurrentDocument Is Nothing Then'Call this agent from a documentSet doc=ws.Currentdocument.DocumentElseExit Sub End ifDim id As Stringid=doc.ParentdocumentunidIf id="" ThenMsgBox "This document has not a parent document."Exit Sub End IfDim docParent As NotesDocumentSet docParent=doc.Parentdatabase.Getdocumentbyunid(id)If Not doc Is Nothing ThenCall ws.Editdocument(False, docParent)ElseMsgBox "Cannot find the parent document with the id contained by the document."End IfEnd Sub
本来简单用OpenDocument @Command和@InheritedDocumentUniqueID函数就可以完成,但是OpenDocument只能在视图下使用,并且要打开的文档须在此视图内。所以只能用LotusScript实现这个代理。在视图下调用时,须先选中要打开其父文档的子文档。对于文档不是响应文档以及它包含的父文档的全局Id无效的情况,都做了相应的提示。此外为了比较主文档和冲突文档,也可以写一段代码检查两者有哪些域的内容不同。
在下一篇文章里,我们会讨论一下另一种冲突文档——复制冲突。看看Lotus Notes是怎样创建和处理它们的,隐秘的域级别的冲突检查,以及一点理论上的探讨。

注1:只有多核CPU才能真正同时运行代码。作为操作对象的文档和物理的存储介质都是唯一的,即使是多核CPU并行运行的代码,保存也不可能是并行的。一条文档保存在多个存储介质,例如硬盘的单元上。保存一条文档也就涉及到一组连续的原子的磁盘写入操作。每个原子的磁盘写入操作都不可能并行,即使多核CPU同时运行对一条文档的超过一个保存指令,理论上最多也只可能发生两个保存指令对应的两组磁盘写入操作交替混杂运行。实际上Notes文档虽然会产生冲突,但每个文档的数据都是完整的,也就是说整个文档的保存是原子性的。

注2:Domino HTTP进程还会将表单提交的值与数据库里文档对应字段的值作比较,只有发生变化的,才会更新该字段的Seq Num。