我见过的一种技术是使用一种“硬编码”的 EAV 模式。不要挂断电话!它适用于您正在谈论的数据集大小并且实际上并没有使用 EAV - 它只是 EAV 式的。
这个想法是有一组表来存储这些自定义属性,其中包含一些触发器(如下所述)。自定义属性表集存储有关属性的元数据(它使用的表、数据类型、约束等)。你可以很喜欢这个,但我没有必要。
您的元表上的触发器用于重新生成将基础+扩展汇总到数据库中的第一类对象的视图。因此,您有一个包含两者的员工视图,而不是表人员 + 员工扩展表。当您将新值放入自定义属性表时,触发器将重新滚动视图并包含新内容。如果你想发疯,你也可以让触发器重写存储过程。根据你的中间层代码的结构,你仍然会被迫重新编码一些,但是如果你应用读取数据的规则,无论如何都会出现这种情况。
在测试中,我发现对于您所说的相对较小的记录数,性能稍慢,但遵循大致相同的降级模式(记录数的 2 倍,慢约 2 倍)。
-- 编辑 --
我是怎么看它的,你有一个代表你的第一类对象的表,所以一行代表“人”,一行代表“员工”,等等。我们称之为 FCO。然后你有一个辅助表来存储代表 FCO 的表。我们称它为 Srcs.. 对于 person,会有一行,即 person 表。对于 Employee,将有两行,即 person 表和 Employee 扩展。还有第三个表,称为 Attribs,它存储构成 FCO 的表中的列。为简单起见,我们会说 Employee 有 ID、Name 和 Address,Employee 有 Hire Date 和 Department,显然 PersonID 指的是 Person 表。因此,FCO 表中的 2 行(人员和员工),Src 表中的 3 行,Attribs 中的 8 行。
该视图(我们将其称为 vw_Employee)从两个表中选择 PersonID、Name、Address、Hire Date、Department。它是由我们称为 OnMetadataChange 的 SQL 存储过程构建的。
该 SP 被触发(通过触发器或批处理),其目的是生成 CREATE VIEW 语句。它将遍历每个 First Class 对象,从哪些表中收集哪些字段构成视图,并基于此发出 CREATE 语句。因此 OnMetadataChange 为每个视图生成一个 DROP 和 CREATE,它生成一个动态 SQL 语句,该语句在 FCO 表中的每个条目执行一次。最好使用触发器来执行此操作,但不是必需的。希望您的 FCO 定义不会经常更改,并且当它们更改时,可能也会有代码发布。那时您可以运行 OnMetadataChange SP。
最终结果是一个 2 层数据库。这些视图构成了对应用程序有意义的 First Class Object 层。该应用程序仅使用视图。这些表构成了应用程序不应该关心的“物理”层。元表本质上是 FCO 层和物理层之间的映射。设置它需要一些时间,但它非常有效,并为您提供了 EAV 的许多好处,同时为您提供了 3nf 表的具体好处(可索引性等)。
如果您愿意,我可以在那里抛出一些示例 SQL。