对象数据库索引方法

5 投票
2 回答
664 浏览
提问于 2025-04-16 21:21

我在使用一个对象数据库(ZODB)来存储很多对象之间复杂的关系,但遇到了性能问题。因此,我开始构建索引,以加快对象的检索和插入速度。以下是我的经历,希望你能帮我。

最开始,当我往数据库里添加一个对象时,我会把它放在一个专门用于该对象类型的分支里。为了防止出现多个对象代表同一个实体,我添加了一个方法,遍历这个分支中已有的对象,寻找重复的对象。一开始这个方法有效,但随着数据库的增大,加载每个对象到内存并检查属性所需的时间急剧增加,变得不可接受。

为了解决这个问题,我开始根据对象的属性创建索引,这样当添加一个对象时,它不仅会被保存到对象类型的分支,还会被保存到属性值索引的分支中。举个例子,如果我保存一个人的对象,属性是firstName = 'John'和lastName = 'Smith',那么这个对象会被添加到人的对象类型分支,同时也会被添加到属性索引分支中,键是'John'和'Smith'。

这样做大大节省了检查重复的时间,因为新对象只需要分析与属性索引交集中的对象集合。

不过,我很快又遇到了另一个问题,就是在更新对象时。索引需要更新,以反映它们可能不再准确的事实。这就需要记住旧的值,以便可以直接访问并删除对象,或者遍历所有属性类型的值来找到并删除对象。无论哪种方式,性能又开始迅速下降,我不知道该怎么解决。

你之前遇到过这种问题吗?你是怎么解决的,还是说这是使用对象数据库管理系统时必须面对的事情?

提前感谢你的帮助。

2 个回答

0

可以考虑使用一个属性哈希值(类似于Java中的hashCode()),然后用这个32位的哈希值作为键。Python也有哈希函数,不过我对它不是很熟悉。

8

是的,repoze.catalog很好用,而且文档也很齐全。

简单来说:不要把索引作为你网站结构的一部分!

  1. 考虑使用容器/项目的层级结构来存储和遍历内容对象;计划能够通过(a)路径(图的边缘看起来像文件系统)或(b)通过在某个特定位置识别单例容器来遍历内容。

  2. 使用RFC 4122 UUID(uuid.UUID类型)或64位整数来标识你的内容。

  3. 使用一个中央目录来进行索引(例如repoze.catalog);这个目录应该位于你ZODB的根应用对象的已知位置。你的目录可能会索引对象的属性,并在查询时返回记录ID(通常是整数)。你的任务是将这些整数ID映射到数据库中存储内容的某个物理遍历路径(可能通过UUID间接映射)。如果你使用zope.location和zope.container来提供常见的对象图遍历接口,会更有帮助。

  4. 使用zope.lifecycleevent处理程序来索引内容并保持信息的更新。

问题 - 概述

ZODB太灵活了:它只是一个持久化的对象图,支持事务,但这也让你在自己的数据结构和接口中可能会迷失方向。

解决方案 - 概述

通常,从ZODB社区中选择已有的模式就能解决问题:使用zope.lifecycleevent处理程序,利用zope.container和zope.location进行“容器式”的遍历,以及像repoze.catalog这样的工具。

更具体的建议

只有在你用尽了通用模式并且知道为什么它们不适用时,才尝试使用ZODB中的各种BTrees构建自己的索引。其实我经常这样做,虽然不太愿意承认,但通常是有正当理由的。

在所有情况下,保持你的索引(搜索、发现)和网站(遍历和存储)结构是分开的。

问题领域的模式

  • 掌握ZODB的BTrees:你可能想要:

    • 将内容对象存储为Persistent的子类,放在提供容器接口的OOBTree子类的容器中(见下文)。
    • 为你的目录或全局索引存储BTrees,或者使用像repoze.catalog和zope.index这样的包来抽象这些细节(提示:目录解决方案通常将索引存储为OIBTrees,这样可以为搜索结果提供整数记录ID;然后你通常会有某种文档映射工具,将这些记录ID转换为在你的应用中可解析的内容,比如UUID(前提是你能遍历图到UUID)或路径(就像Zope2目录那样)。
  • 在我看来,不要费心去处理intids和键引用等(这些不太符合常规,如果你不需要它们会更难)。只需使用repoze.catalog中的Catalog和DocumentMap,以整数到UUID或路径的形式获取结果,然后再想办法获取你的对象。注意,你可能需要某种工具/单例,负责根据搜索返回的ID或UUID来检索你的对象。

  • 使用zope.lifecycleevent或类似的包,提供同步事件回调(处理程序)注册。这些处理程序应该在你对对象进行原子编辑时调用(通常每个事务调用一次,但不在事务机制中)。

  • 学习Zope组件架构;这不是绝对必要的,但肯定会有帮助,即使只是为了理解zope.interface接口和上游包如zope.container。

  • 了解Zope2(ZCatalog)是如何做到这一点的:一个目录可以前置多个索引或各种类型,每个索引都搜索一个查询,各自有专门的数据结构,并返回整数记录ID序列。这些通过目录进行集合交集合并,并作为“脑”对象的懒映射返回,包含元数据存根(每个脑对象都有一个getObject()方法来获取实际的内容对象)。从目录搜索中获取实际对象依赖于Zope2的模式,使用从根应用对象到被目录化项目的位置的路径来识别。

撰写回答