84. 从视图索引说Notes数据库(下)
上文介绍了关系型数据库里的索引。Notes数据库里的索引隐藏在视图概念里(本文的讨论只针对Notes的视图索引,不包含全文索引。)。开发人员创建的视图仅仅是存放在数据库里的一条设计文档,数据库引擎会依据它创建和更新索引。关系型数据库里的索引是从记录中抽取的数据排序而组成的数据结构(主要是B树),Notes视图的索引还包括未排序的列、计算值、分类、总计等等数据(数据结构仍然是B树,如果运气足够好的话,你会遇到Notes报出B-tree structure is invalid的错误)。用户在客户端里看到的视图就是索引的数据,再加上外观的设置。这样的区别缘于两类数据库底层设计的差异。
关系型数据库里的索引是为了查询而建的。SELECT NAME, JOB FROM PERSON WHERE COUNTRY=‘China‘这样一个简单的查询语句,会因为PERSON表的COUNTRY列建有索引而大大提高速度。而对SELECT * FROM PERSON这样无选择的查询语句,是否有索引则没有区别。作为文档型数据库的Notes,情况略有不同。文档型数据库里作为存储数据单元的文档,与关系型数据库里对应的记录相比最大的差异就是,文档没有记录受到的所在表定义的约束,例如一条记录有多少列,列的名称和数据类型是什么,长度几何等等。每条文档都可以有任意数量、名称和数据类型的字段。如果说某个表里的每条记录都有一个共同的schema,那么可以说每条文档都有自己的schema。在有些文档型数据库里,如大热的MongoDB,文档还被归类在表示同一种实体的collection里,有类似table的含义和用途。而在Notes数据库和另一热门CouchDB里,连这样的容器都没有,所有的文档,无论内容和用途是什么,都直接处于数据库这一级别之下(CouchDB与Notes数据库的相似之处还不仅这一点,考虑到它的最初作者Damien Katz就是来自Notes开发核心团队就一点都不奇怪了)。如此一来,即使是SELECT * FROM PERSON这样查询表格内所有数据的语句,在Notes里也没有直接的对应物了。这也意味着在Notes数据库里任何有意义的查询(也就是按文档的id查询之外)都必须藉助于视图这一概念。
由此可见,Notes视图索引的第一个作用是从数据库里选择某些文档,提取字段的值作为或计算列值,从而提供一个类似table的数据集合或接口。当我们在LotusScript或Java使用NotesView对象访问其中的NotesViewEntry时,读取的就是索引。同时,文档的响应层次、分类、求和、平均值等功能又使得视图承担了关系型数据库里依靠SQL和其他方法实现的报表功能。
Notes视图索引第二个作用就是便于查找文档。常用的NotesView.GetAllDocumentsByKey和GetDocumentByKey的实质都是在索引里找到条目,再返回对应的文档。用户在客户端展现的视图里排序、折叠展开分类以及按开头搜索,也都是利用索引完成的。
Notes视图索引的代价和关系型数据库里讨论的一样,都是占据存储空间和消耗计算资源。
更新的时间
索引是何时建立的?又是何时更新的?从上一节对索引作用的讨论可知,展现文档集合和使用户能够快速查找文档,这两个至关重要的用途都要求索引的数据是最新的,也就是与它所基于的文档的数据是一致的。在上一篇文章里,我们提到理论上更新索引在时间上有三种选择,一是与文档修改(新增、修改和删除)同时,二是延迟至索引被读取时才检查是否需要更新,三是定时更新。选项一保证了索引与文档始终是一致的,但是加大了文档修改时数据库的负担。选项二也能保证每次读取的索引是最新的。与选项一相比优点是,在索引被读取前,相关的文档可能已经过多次修改。不同用户修改不同文档,单个用户多次修改某个文档,等等各种情况都会发生。采用第一种方案时每次修改索引都需要更新,而方案二使得对索引两次被读取之间的所有修改(如果有),只需进行一次批量更新,从而减少开销。缺点是如果读取时发现索引需要更新,就增加了等待时间。第三种方案实际是对第二种方案的补充,理念同样是批量更新,目的是减少读取索引时需要更新的次数和文档数量。
综合以上分析可见,采用方案一时用户访问索引的速度是最快的,代价是所需系统资源也最多。方案二和三的延迟思路,节省了系统资源,但用户有时需忍受延时。关系型数据库基本上都采用第一种方案。文档型数据库则选择各异。MongoDB采用方案一,CouchDB采用方案二,Notes则混合使用方案二和三。
更新的过程
定时更新
我们先来看Notes视图的定时更新。这由服务器运行的Update和Updall两个任务来完成。两者的运行时间都可以在notes.ini里设置,默认情况下Update持续运行,Updall则在每天凌晨两点运行一次。Update维护两个队列(queue),一个是即时队列(immediate queue),另一个是延迟队列(deferred queue)。队列里保存的是一条条某个数据库索引需更新的请求。请求的来源有几类:复制器(replicator)复制后如果某个副本的文档有变动,就会发送一条请求到队列。邮件路由器(router)将新消息派送到某个邮箱后也会发送一条请求。最频繁的则是一个用户在某个数据库里创建、修改或删除了文档,当他退出该数据库时,会发送一条请求到队列。复制和用户修改产生的请求都被发送到延迟队列,特殊的例如触发数据库全文索引及时更新的操作会发送一条请求到即时队列。Update每隔五秒(可以通过notes.ini的UPDATE_IDLE_TIME和UPDATE_IDLE_TIME_MS设置修改此默认值,下同)检查两个队列,对即时队列里的请求马上处理,对延迟队列里的则会比较同一个数据库的请求,最先到达的请求之后的十五分钟(Update_Suppression_Time)内所有的请求都会被忽略,这样做还是为了减少资源的消耗,十五分钟以内对某个数据库的修改只会触发Update更新其索引一次。
请求只记录数据库的路径,Update在处理时先要判断对那些视图的索引进行更新。此时Notes再次显示了尽量节省资源的特性。首先过去七天(UPDATE_ACCESS_FREQUENCY)内未被打开过的视图被忽略。接着根据索引上次更新时间和视图的选择条件搜索最近发生更改过的并且包含在该视图中的文档,如果数量少于二十(UPDATE_NOTE_MINIMUM),也不做更新。
Updall任务因为是在凌晨进行,Notes显得大方一些。它不管队列,而是检查所有数据库的所有视图,需要更新的更新,需要重建的重建。
打开时更新
定时更新不能保证索引被访问时数据是最新的,只能尽最大限度减少那时更新的负担。用户通过客户端界面或程序访问视图时,会调用Notes API的NIFOpenCollection()函数打开索引(NIF意为Notes Indexing Facility,就是Notes索引部分的组件),如果索引数据不是最新的,NIFOpenCollection()就会调用NIFUpdateCollection()更新索引。我们每次在客户端里打开一个视图时,都会发生这个过程(除非以下说明的特殊情况)。如果距离上次Update更新该视图索引,发生更改的文档不多,这个过程就很快能完成。而如果这期间有大量文档发生改动,甚至该视图的索引不存在,就需要等待很长时间。自R7后,Notes会在单独的后台线程中进行索引更新,用户界面因而不会被卡住,用户还能进行其他操作。
为了提高视图打开的速度,或者说最大程度地减少索引更新的次数,Notes视图里还有控制打开时是否更新的选项。
分别是:1. 自动,初次使用后。2. 自动。3. 手动。4. 自动,最多每n小时更新一次。手动意为打开视图时索引不自动更新,需按F9更新。三种自动的区别在于,设为1或4时,如果一个视图未被打开过没有索引,Update和Updall运行时不会创建索引;设为2时则会;设为4时,如果索引在设置的最近n小时内被更新过,表现得就和设为3时一样。
索引在数据库里占据的空间不可小觑,数据库里如果视图多且复杂(列多,分类和排序多),索引占据的空间甚至会超过文档的空间。所以视图里又有丢弃索引的设置,可以设为数据库关闭后或者未被访问超过一定天数后丢弃。如果符合设置的条件,Updall任务负责删除索引。
所以,我们在设计视图时要……
每个视图包含的索引有三类:
1. 按照NotesID排序的默认索引。
2. 按照某列排序的索引。
3. 父文档和子文档关系的索引。
每增加一个列排序(包括分类),就新增一个索引。多级分类比同样数量的排序需要的计算更多。
我们已经从上面的讨论看到Notes是如何挖空心思地减少索引更新的次数,因为这是很消耗计算资源的动作。在设计视图时,如果罔顾Notes的苦心,建一大堆用途不大的视图,又加上用户很少使用的排序和必要性不大的分类,不但会导致Domino服务器上Update任务不堪重负,也会吞下视图打开缓慢的苦果。
以前我曾提到,XPages视图层和数据层分开的好处之一就是使视图可以仅仅作为数据集合,不同的展示需要,比如外观、列和文档的进一步筛选都可以利用XPages的视图控件实现,为减少视图数量提供了可能,从而也就减少了索引的数量,提高了性能和可维护性。
Notes帮助里有用的参考文章:
Notes Help - Refreshing view indexes
Administrator help - Indexer tasks: Update and Updall
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。