内容


用于 Lotus Domino 数据库的 DigestSearch 方法

Comments

对 DigestSearch 方法的最佳描述是,它是用于处理 Profile 文档的 LotusScript 的 View.GetDocumentByKey 搜索方法的替代解决方案。它的主要目的是使用一个关键字查找一个或多个文档,例如使用社会保障号(Social Security Number)、电话号码或者是一个存储的结构化查询语言(Structured Query Language ,SQL)记录的惟一序号(Unique Sequence Number)。

DigestSearch 的主要优势是搜索大数据库(尤其是搜索来自 Notes 客户机的基于服务器的数据库),就速度而言它的性能要优于所有其他常规方法。实际上,依据搜索复杂程度的不同,DigestSearch 方法最多可以快 20 倍!并且 DigestSearch 执行搜索不需要任何视图,所以可以通过移除不必要的视图来使数据库变小。数据库中文档数目的多少并不会对使用 DigestSearch 方法进行搜索的速度产生实质性的影响。

DigestSearch 最大的缺点是它仅能够接收一个不含通配符的关键字。就这一点而言,该方法与 LotusScript 的 View.GetDocumentByKey 方法和 @DBLookup 类似。所以,如果性能对您来说很重要,并且关键字是可以预知的,就可以考虑使用 DigestSearch 方法。在数据库中实现 DigestSearch 方法并不需要重大的设计更改或者是对现有文档的修改。尽管该方法还有一些需要改进的地方(尤其是在进行多个关键字索引和搜索方面),但是它现在已具有极大的性能优势。

本文中讨论的样例数据库 Digestprofile.nsf(Profile 文档数据库)、Testindex.nsf(Demo Index 数据库)、Digest2.nsf(DigestSearch 简单搜索)和 Demonab.nsf(demo Domino 目录)都包含在 Digest_dbs.zip 文件中,可从 “下载” 一节下载。

在本文中,展示了两种使用 DigestSearch 方法的方式:

  • 作为 Profile 文档和其他类型临时存储文档的有效替代物。
  • 作为一种搜索 Domino Directory 并返回指定组中所有用户个人信息的方式。

本文假设您是经验丰富的 Notes/Domino 程序员。

性能

当您比较 DigestSearch 方法和传统 Domino 搜索方法的搜索速度时,如下面的两个表所示,在使用单一关键字搜索单一文档时,DigestSearch 的性能好于所有其他方法,尤其是当数据库驻留在服务器而您在 Notes 客户机上执行搜索时。在我们的性能测试中,我们测量了获取一个对象来处理 100 个文档所需的时间。我们测量了 Domino Directory 搜索示例的结果,并且模拟了一个由几个步骤组成的搜索。您可以使用 Digest Search 2(Digest2.nsf)数据库中的 Performance 测试代理来运行自己的测试。

注意:该性能测试并不是设计用来进行方法之间的一般速度对比;结果仅适用于搜索组中成员的特定任务。

在第一个表中,模拟用户在 Notes 客户机上执行一个对服务器上数据库的搜索:

搜索方法 时间(秒)
DigestSearch2.9
Db.Search13.1
Db.FTSearch6.1
View.GetDocumentByKey5.8
@DBLookup12.1

第二个表显示了在 Notes 客户机上对本地数据库进行搜索所得到的结果:

搜索方法 时间(秒)
DigestSearch1.2
Db.Search9.7
Db.FTSearch2.8
View.GetDocumentByKey0.9
@DBLookup1.2

正如我们所看到的,在本地运行时 @DBLookup 是第二快的方法;当在基于服务器的数据库上运行时,它是第二慢的方法。(然而,返回文档句柄而不是返回纯文本结果也会影响测试结果。)另一个有趣的事实是,搜索本地数据库时 Db.Searchis 方法所用的时间只比搜索服务器所用的时间少 30%,而其他方法都至少减少 50%。

注意:在该测试中,我们在同一查询中使用了带有 Cache 参数和几个关键字组合的 @DBLookup,在实际搜索中这样做并不总是可行的。

DigestSearch 是如何工作的

DigestSearch 方法完全是用 LotusScript 实现的,并且它的核心代码大约只有 30 行。可以使用 DigestSearch 进行后台搜索,并且它的语法和功能会让人想起 View.GetAllDocumentsByKey(实际上是 “searchword”)方法。这两个方法主要的共同之处是,它们都使用一个关键字作为参数,并返回一个或多个精确匹配的文档作为结果。不同之处是执行 DigestSearch 搜索不需要视图,并且它总是进行搜索字的精确匹配。

使用 LotusScript 命令调用 DigestSearch:

Set doc=FastSearchByKey(db, "searchword")

该方法称作 DigestSearch 是因为它使用惟一摘要(单向散列)值来表示一个搜索字。该惟一键是一个加密的搜索字,结果是一个 32 个字符的字符串,这个字符串然后被用作文档的 Universal ID (UNID)。因此,DigestSearch 并不是真正搜索数据库;它只是简单地检查文档是否存在指定的 UNID。

这是搜索流程的一个简单例子。当理解了该流程是如何工作的以后,就可以很容易地进行修改和定制:

  1. 用户搜索 +1 212 12345678 这个电话号码来找到其所有者的名字。
  2. 使用 @Password 函数将 +1 212 12345678 转换为一个摘要值。得到的摘要/散列字符串是 3F915F67F52D35053113AAB40385FE46。
  3. 脚本检查数据库中是否有 UNID 为 3F915F67F52D35053113AAB40385FE46 的文档。
  4. 如果找到一个文档,那么搜索就结束了。脚本从文档中读取字段并将它们显示给用户,或者仅仅是在用户界面中将文档打开。如果没有找到文档,用户将看到一条消息指出没有带有该关键字的文档。

下面的代码显示了一个简化了的可以工作的执行摘要搜索的 @formula 示例:

seachrword:="+1 212 12345678";
fullname:=@GetDocField(@Middle(@Password(seachword);1;32);
"FullName");@Prompt([Ok];"Result for "+searchword;
@If(@IsError(fullname);"Document "+searchword+a?? does not exist";
"Fullname: "+fullname))

上面代码中使用的 LotusScript 代理代码是:

Option Public
Use "DigestSearchLib"
Sub Initialize
Dim session as New NotesSession
Dim db As NotesDatabase
Dim doc As NotesDocument, curdoc as NotesDocument
Dim workspace As New NotesUIWorkspace
Dim uidoc As NotesUIDocument
Dim searchstring As String
Set uidoc = workspace.CurrentDocument
Set curdoc=uidoc.CurrentDocument
Set db=session.CurrentDatabase
searchstring=doc.inputfield(0) ' Phone number input field on form
' Exit if no phone number was supplied
If searchstring = "" Then Exit Sub
Set doc= FastSearchByKey(db, searchstring) ' Perform DigestSeach and
' return document handle
If Not doc Is Nothing Then
curdoc.FullName=doc.FirstName(0)+" "+doc.LastName(0)
' populate fields in current document with information
' from the found document
curdoc.Address=doc.Address(0)
curdoc.Job=doc.JobDescription(0)
Else
' No document was found, notify user about it
Msgbox "Info for "+searchtxt+" not found"
End If
End Sub

在后台的脚本库中,当运行该代理时将会执行下列过程:使用 @Password 方法将搜索字转换为摘要。(这一步骤得到一个与 UNID 兼容的 32 个字符的字符串。)

ev=Evaluate(|@Password("|+skey+|")|)

该库检查是否存在一个带有这个 UNID 的文档:

On Error 4091 Goto wrongiderr4091 Set digestdoc= digestdb.GetDocumentByUNID(Mid(ev(0),2,32))

注意:必须处理可能出现的 Invalid universal ID 错误,在搜索摘要没有相应文档时,会出现这个错误。

如果 digestdoc 没有导致错误 4091 Invalid universal ID,脚本会将文档的句柄传递回调用的函数:

Set FindDocByDigestKey=digestdoc
Exit Function
wrongiderr4091:
Set FindDocByDigestKey=Nothing

脚本为用户显示找到的文档中的值:

curdoc.FullName=doc.FirstName(0)+" "+doc.LastName(0)

下面的代码显示了整个 DigestSearchLib LotusScript 库,其中包含了 DigestSearch 的核心代码:

' ---- DigestSearchLib script library start ------
Dim digestdb As NotesDatabase
Dim digestdoc As NotesDocument
Dim lastkey As String
Dim lastgeneratedID As String
Dim lastgeneratedDoc As NotesDocument
Function FindDocByDigestKey(custdb As NotesDatabase,skey As String)_
As NotesDocument ' this is main function for getting search result
Dim unid As String
Set digestdb=custdb
On Error Goto errh
unid=CalculateDigest(skey)
If Len(unid)<>32 Then
Set FindDocByDigestKey=Nothing
Exit Function
End If
Set digestdoc = lastgeneratedDoc
' lastgeneratedDoc is a global variable
'return document handle back to calling agent
Set FindDocByDigestKey=digestdoc
Set digestdoc=Nothing
Exit Function
errh:
Set FindDocByDigestKey=Nothing 'no document for that keyword is found
Resume endas
endas:
End Function
Function IsDigestKeyTaken(unid As String)
' this function checks if document for the keyword already exist
On Error Resume Next
' error nr 4091 means invalid Universal ID
On Error 4091 Goto wrongiderr4091
' check if document with unique keyword already exist
Set digestdoc= digestdb.GetDocumentByUNID(unid) IsDigestKeyTaken=True
Set lastgeneratedDoc=digestdoc
Exit Function
wrongiderr4091:
IsDigestKeyTaken=False
Resume wrongID
wrongID:
End Function
function CalculateDigest(skey As String)
'this function computes 32-character digest for the keyword
Dim unid As String
'calculate digest for the keyword
ev=Evaluate(|@Password("|+skey+|")|)
unid=Mid(ev(0),2,32) 'strip parentheses around generated digest
lastgeneratedID=unid 'assign global variable a new value
If IsDigestKeyTaken(unid)=False Then
unid="no doc with that digest yet"
End If
CalculateDigest=unid
End Function
Sub MakeSearchable(sdoc As NotesDocument)
' this function sets new Universal ID and saves the document
unid=lastgeneratedID
sdoc.UniversalID=unid
End Sub
' ----- script library end ------

使用 DigestSearch 处理 Profile 文档

您可能已经注意到了,Lotus Notes 对 Profile 文档进行缓存。如果两个或更多的用户同时修改 Profile 文档,那么这一操作就会出现问题。由于缓存的问题,用户会得到旧的、过期的值。但是可以使用 DigestSearch 替代标准的 GetProfileDocument 函数来解决该问题,如下面的代码片段所示:

Set db=session.CurrentDatabase
' Perform search and return a document handle
Set profiledoc = FastSearchByKey(db, "My Profile 1")
If Not profiledoc Is Nothing Then
MsgBox profiledoc.Field1(0) ' show a field from profile document
profiledoc.Field2=Cstr(Now) ' update profile with new field
Call profiledoc.save(True,False) 'save profile document
End If

甚至还可以使用 formula 语言来访问新的摘要 Profile 文档,如下面的代码所示:

profname:="My Profile 1";
comment:=@GetDocField(@Middle(@Password(profname);1;32); "comment");
createddate:=
@GetDocField(@Middle(@Password(profname);1;32); "doccreated");
@Prompt([Ok];"Result for "+profname; @If(@IsError(comment);
"Profile "+profname+"does not exist";
"Comment: "+comment+@Char(10)+"Created: "+createddate))

不幸的是,不能使用 Notes @formula 语言创建新的摘要 Profile 文档。只能够搜索和修改现有的文档(使用 @SetDocField)。本文的 “下载” 一节包含一个 ZIP 文件,其中有一个名为 “DigestSearch demo for profile docs”(Digestprofile.nsf)的数据库,包含了 “Modify example with Formula” 代理的源代码和示例。该数据库还包含了 LotusScript 和 @formula 代码,可以使用它们进行测试(参见图 1)。在 Instructions 视图中单击 Create profile doc 创建一个新的 profile 文档,然后单击 Find profile doc 找到刚刚创建的 profile 文档。

图 1. 用于 profile 文档测试的 Action 按钮
用于 profile 文档测试的 Action 按钮
用于 profile 文档测试的 Action 按钮

使用 DigestSearch 进行简单搜索

进行搜索比处理 Profile 文档更加复杂。Profile 文档的数量通常是有限的;它们是根据需要创建的,并且不依赖于其他文档。这种情况与搜索包含无数互相连接并与其他数据库相连接的文档的数据库时完全不同。

为了维护现有数据库的一致性,不能直接修改这些数据库中文档的 UNID。因此,需要一个特殊的镜像/索引数据库用于保存父数据库中文档的索引文档。

索引数据库不需要任何设计元素;它只包含两种类型的文档:

  • 引用持有者文档(Reference holder document)
  • 关键字持有者文档(Keyword holder document)

源数据库中对每一个进行了索引的文档存在一个引用持有者文档(即一个 SourceRefHolder 表单)。该引用持有者文档包含两个动态创建的字段:SKey 和 REFUNIDs。SKey 字段只是用来提示源数据库中最初文档的 UNID,而不用于搜索。REFUNIDs 字段是一个多值字段,用来对关键字持有者文档进行跟踪。也就是说该字段包含关键字持有者文档文档的 UNID。

每一个源文档的关键字持有者文档的数目与每个文档的搜索字段的数目相同。在 Configuration 文档中指定这些字段,如图 2 所示。每一个索引文档也具有多值字段 UNID,其中包含与为该索引文档分配的关键字相匹配的源数据库中的所有文档的 UNID。

图 2. Configuration 文档
Configuration 文档
Configuration 文档

搜索的源代码与上文中提到的 Profile 文档示例类似。不同之处是:

  • 对于搜索,使用的是 Demo Index 数据库(Testindex.nsf)而不是当前数据库。
  • 保持对一个特殊的文档中多个关键字的跟踪。

假设更新了 Demo Index 数据库,并包含来自源数据库(也就是 Domino Directory)的所有文档,搜索得到的结果与搜索源数据库得到的结果相同。

可以通过以下三种主要方式,使用新文档更新 Demo Index 数据库:

  • 在源数据库中的文档上运行 QuerySave 脚本
  • 使用日程安排代理
  • 使用附加的服务器程序,它会在文档修改时立即执行更新

注意:Digest Search 2 数据库(Digest2.nsf)包含一个称作 Process database index 的代理,用于为源数据库中的文档创建索引。

搜索 Domino Directory 示例

在样例数据库 Digest Search 2 中包含两个用于执行 Domino Directory 搜索的代理。其中一个代理(Find users by first name)查找所有这样的 Domino Directory 文档,即其中人的名字与您在输入框中输入的名字匹配。另一代理(Find group members)查找指定组中的人的所有 Person 文档。要运行示例代理需要三个数据库,如图 3 所示:

  • Digest Search 2(Digest2.nsf)
  • Demo NAB 数据库(Demonab.nsf);您也可以使用 Domino Directory 的副本
  • 一个空数据库用于保存索引,即 Demo Index(Testindex.nsf)
图 3. 示例代理的数据库
示例代理的数据库
示例代理的数据库

三个数据库全部都包含在本文 “下载” 一节的 ZIP 文件中。

在 Digest Search 2 数据库中,必须配置 Demo NAB 和 Demo Index 数据库的位置,如图 2 和图 4 所示。为数据库配置了路径后,必须使用来自 Demo NAB 的信息填充索引数据库。要完成该操作,在 Configuration 视图中单击 Synchronize Index(参见图 4)。

图 4. Configuration 视图中的 Action 按钮
Configuration 视图中的 Action 按钮
Configuration 视图中的 Action 按钮

一旦完成索引后,就可以执行搜索。只需单击 “Find persons by first name” 或者 “Find all users in a group”。然后输入用户的名字或组名,并单击 OK。在我们的例子中,用户的名字是 John。您应该将其更改为存在于您目录中的适当名字(参见图 5)。

图 5. 按名字进行搜索
按名字进行搜索
按名字进行搜索

如果一切都进行了正确的配置,将显示类似图 6 所示的窗口。

图 6. 按名字进行搜索的结果
按名字进行搜索的结果

当您单击 “Find all users in a group” 时,将发生下列后台事件:

  1. 脚本识别与关键字 listname=demogroup 匹配的索引文档(就是关键字持有者文档)。
  2. 在此索引文档中,脚本识别源数据库中源文档的 UNID。
  3. 脚本从源文档中读取 Members 字段,然后对于该字段中的每一个人,脚本执行一个新构造的关键字为 "fullname="+doc.members(x) 的搜索。
  4. 直到搜索完字段中的全部记录,搜索循环才结束,然后将结果组合成一个文本字符串。
  5. MessageBox 将得到的文本字符串显示给用户。

何时(及何时不)使用 DigestSearch

当需要用一个关键字快速查找一个或多个文档时,可以使用 DigestSearch。Profile 文档和用于临时存储数据的文档是最明显的使用场所。类似地,不管什么原因,在您无法使用传统的搜索方法,而是需要一个高性能的解决方案时(更适合于静态数据,例如 Domino Directory 中的数据),可以使用 DigestSearch。

下面是应该考虑使用 DigestSearch 的实际示例:

  • 按社会保障号或电话号码查找 Person 文档。
  • 按产品编号查找产品。
  • 当与 SQL 同步时,通过记录的惟一编号判断该记录是否已经存在于 Notes 数据库中。
  • 针对一个特定组中的所有成员,从 Domino Directory 中的用户文档获得特定的字段。

何时 使用 DigestSearch 的实际示例包括:

  • 使用标准的搜索方法能够获得令人满意的搜索速度(<1 秒)。
  • 数据库不是很大(<5,000 个文档)。
  • 需要定义多于一个的搜索关键字(例如,Form="Document" & Status="Active")。
  • 需要使用更灵活的搜索关键字(例如, @Left(Product;2)="Di", @Created=@Today)。
  • 作为特定关键字的搜索结果返回的文档数目多于 500。

DigestSearch 方法最初创建的目的是为了提高大量 SQL 记录与 Domino 数据库进行同步的速度。每一条 SQL 记录都有它自己的惟一序号,您可以为记录中的所有组合字段计算一个定制的惟一编号。每一条记录都是惟一的,这就为使用 DigestSearch 方法提供了一个绝佳的环境。可以避免将现有的文档(在我们的例子中,有超过 1,000,000 个文档)缓存到数组或者列表中,因而同步所需的时间会缩短 10 到 25 分钟。

下一个 DigestSearch 版本的任务列表

正如上文中提到的,DigestSearch 还有一些改进的空间。一些问题可以很容易地解决,而其他一些问题还需要新的方法。在 DigestSearch 方法的未来版本中,可能将会看到下面一些更改:

  • 使用新文档更新索引数据库的代理目前速度很慢,且未进行优化,通过添加一对跟踪字段到索引文档中就可以解决这一问题。
  • 检查实现增量索引是否可行。
  • 开发一个服务器附件,自动将新的和修改了的文档从源数据库添加到索引数据库。
  • 使用相同的索引文档来存储 4,096 个源文档的引用,而不是每个源文档都有自己的索引文档。(需要更多的调查研究以确定这样做是否会影响性能。)
  • 允许每个搜索使用多个关键字(例如,姓和城市)。通过比较来自两个或更多单独搜索的结果并消除不匹配的结果,可以实现该功能。
  • 使多个源数据库可以使用相同的索引数据库。
  • 考虑使用 B-Tree 搜索技术改进性能和管理索引的可行性。(我们目前正在研究使用定制的 B-Tree 算法进行带有通配符的搜索并且更加紧凑地存储索引文档是否可行。与 DigestSearch 的对特定关键字进行快速查找的能力相结合,这将成为一种无可伦比的解决方案。)

速度非常重要

在通常情况下,DigestSearch 能够比常规的搜索方法更快地得到结果。如果您不需要 FTSearch 和 DBSearch 所提供的奢侈的高级查询,而是在寻找 Profile 文档、GetDocumentByKey 方法或 @DBLookup 的替代方法,那么下载样例数据库并看一看 DigestSearch 是否对您有用。在本文中的示例和样例数据库的帮助下,您可以花几分钟的时间来测试一下 DigestSearch 的功能,并且只需修改 Configuration 文档就可以在您自己的应用程序中加以实施。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Lotus
ArticleID=110956
ArticleTitle=用于 Lotus Domino 数据库的 DigestSearch 方法
publish-date=04252006