关系数据库架构设计:大规模版本控制属性

时间:2012-06-21 09:29:25

标签: mysql database performance schema

我在使用版本控制为动态属性设计架构时遇到了一些问题。假设以下用例:

我有一个名为Actor的表,其中包含idname(为了简单起见)。我的案例的上限是,该表包含大约100万个条目。

此外,每个演员都会获得分配给他们的属性。因为我当时不知道属性,所以我需要一个表来管理属性。我想到了Property - 表。生成的n:m关系将通过ActorProperty之间的表来解析,该表包含其主键和属性值(类型?)。

此刻这似乎很容易处理。有一百万个条目,每个条目有10个属性,ActorProperty表将有一千万个节点。我相信btree索引(log2(n))这应该没问题。

现在是我正在努力的部分。应该以某种方式跟踪属性。随着时间的推移,这些属性会发生变化,但历史不应该丢失。最有可能的是,它将通过时间戳完成。请注意,多个属性会同时更新。一个例子是:我每天拍摄所有演员的快照,如果有变化,我会同时更新所有更改的属性。这导致每年365个时间戳。

如果我使用另一个表来管理版本(时间戳)并将另一个外键添加到ActorProperty表,我将获得365 * 1000万个条目。这应该是我得到的最大值。大多数情况下,数据集将显着缩小。

我现在的问题是更多地解决这个问题。我阅读了有关索引的以下答案:How does database indexing work。查询具有该数量条目的表是不是非常慢?一个示例查询将是:在给定时间戳id = x的前100个actor及其所有属性。我觉得我想到的架构可能不是最好的。有没有人对具有更高可扩展性的模式有任何建议或想法?

顺便说一句,我目前还在评估NoSql方法,所以我想暂时专注于关系方法。我的目标是收集不同技术的优点和缺点,然后为所描述的用例提供理论架构或模型。在关系数据库中使用最佳模型的性能是我看似无法评估或找到的。

谢谢!

4 个答案:

答案 0 :(得分:1)

  

应该以某种方式跟踪属性

究竟如何跟踪它们是重要的。在最简单的情况下,您可能希望在任何给定时间查询状态 - 因此解决方案是在分解表中具有多个与时间相关的记录:

create table actor_property (
  actor_id INT NOT NULL,
  property_id INT NOT NULL,
  starttime DATE NOT NULL,
  endtime DATE NOT NULL DEFAULT 99991231
  PRIMARY KEY (actor_id, property_id, starttime, endtime) 
);

这样做的结果是,当您尝试将actor链接到属性并且链接已经存在于表中时,您需要处理这种情况(您无法在触发器中更新表,但是您可以检查冲突并强迫例外)。然后,您可以随时查询数据的状态.....

SELECT a.name, property.name
FROM actor a
INNER JOIN actor_property ap
   ON a.id=ap.actor_id
INNER JOIN property p
   ON p.property_id
WHERE $snapshot_date >= ap.starttime
AND $snapshot_date <= ap.endtime

在上面的actor_property中使用当前记录的物化视图会稍微快一些 - 取决于关系变化的频率。

  

查询具有该数量条目的表是不是非常慢?

实际上,除非您需要经常分析整个数据集,否则大多数操作只会查看行的一小部分,并且通常数据库会演变出热数据的区域 - 读取缓存远比mysql的查询缓存更有效(这是非常具体)。

答案 1 :(得分:1)

我在其中一个应用程序中使用了类似的设计。

首先,我认为这组属性不会那么大(理论上),所以分享它是很好的。为此,我将创建一个包含唯一PROPERTY_TYPEID列的NAME表。这种方式在主PROPERTY表格中有ACTOR_IDPROPERTY_TYPE_IDVALUE列,这样可以带来2个好处:

  1. 由于所有用例仅存储属性名称一次,因此表的大小大幅减少;
  2. 查询的效果会更好。
  3. 现在进行物业追踪。我喜欢这种方法,当一个人及时跟踪对象的实例时,每个实例都有它的开始和结束时间。可以使用now() BETWEEN start_dt AND coalesce(end_dt, now())找到当前有效的属性实例,因为开放实例的end_dt实际上是NULL

    架构如下所示:

    CREATE TABLE actor (
        actor_id   integer not null,
        actor_name varchar(100) not null,
        PRIMARY KEY (actor_id)
        );
    CREATE TABLE property_type (
        property_type_id   integer not null,
        property_type_name varchar(100) not null,
        PRIMARY KEY (property_type_id),
        UNIQUE (property_type_name)
        );
    CREATE TABLE actor_property (
        actor_id         integer not null,
        property_type_id integer not null,
        property_value   varchar(500) not null,
        start_dt         timestamp not null,
        end_dt           timestamp
        PRIMARY KEY (actor_id, property_type_id, start_dt)
        );
    

    有关实施的说明:

    1. 更新属性实际上是一个原子关闭实例+创建实例操作。因此,将它包装到START TRANSACTION; ... COMMIT;块或(我更喜欢)创建一个能够完成工作的函数是件好事;
    2. 在任何情况下使用DB端函数都是一种很好的风格;
    3. 所有表上的主键都有隐含的索引,这些索引反过来会给你预期的性能;
    4. actor_property表中潜在的365e6行在现代硬件上并不是什么大问题。鉴于您的索引已经到位并且平衡良好,在最坏的情况下,您将执行最多30次磁盘页面读取以查询此表中的单个条目。

答案 2 :(得分:1)

@symcbean和@vyegorov都采用他们的方法 - 在现代硬件上,简单的查询应该对你所谈论的数据量没有问题。

但是,架构设计(通常称为“实体/属性/值”或EAV)在查询您可能需要考虑时有一些缺点。

常见的关系陈述可能变得非常复杂 - 而且往往很慢。例如,设想一个查询来查找具有属性“height”&gt;的actor。 1.9,财产“年龄”&lt; = 25,财产“代理人”不喜欢'sleazeball',并且当前没有出现“难以使用”的财产。

如果“property_value”列是varchar,则数字比较往往会违反直觉。

搜索“in”,“not in”等是很尴尬的。

解释“代理人不喜欢'sleazeball'可能意味着两件事 - 有一个叫做代理人的财产,而且它的价值不是很小的,或者甚至没有一个叫做代理人的财产。

我提到所有这些问题的原因是为了让您在设计中更进一步 - 仅仅将性能视为假设是不够的,您需要考虑现实场景。

答案 3 :(得分:0)

根据您的具体情况,如果将问题分解为“当前属性”和“过去的属性”,您可能会发现性能会更好。各种ORM正在采用这种方法来实现其版本化行为,因为它大大降低了增加表格大小的指数成本。

因此,在您的情况下,请考虑将您的Actor表与之配对:

  • ActorProperty(fk = actor_id
  • ActorPropertyVersionable(fk = actor_id, version_num

因此,在为actor编写新属性时,应首先复制现有值并将其插入可版本化表中,然后然后将新值添加到当前表中。将其包含在交易中以确保其安全。

通常,属性查询通常对当前属性值感兴趣,并且需要更少地访问过去的值(当然,您需要对自己的用例做出判断)。每次询问数据时,它确实需要两个不同的查询(当前值,过去值),但性能优势可能是值得的。