我在使用版本控制为动态属性设计架构时遇到了一些问题。假设以下用例:
我有一个名为Actor
的表,其中包含id
和name
(为了简单起见)。我的案例的上限是,该表包含大约100万个条目。
此外,每个演员都会获得分配给他们的属性。因为我当时不知道属性,所以我需要一个表来管理属性。我想到了Property
- 表。生成的n:m关系将通过Actor
和Property
之间的表来解析,该表包含其主键和属性值(类型?)。
此刻这似乎很容易处理。有一百万个条目,每个条目有10个属性,ActorProperty
表将有一千万个节点。我相信btree
索引(log2(n))这应该没问题。
现在是我正在努力的部分。应该以某种方式跟踪属性。随着时间的推移,这些属性会发生变化,但历史不应该丢失。最有可能的是,它将通过时间戳完成。请注意,多个属性会同时更新。一个例子是:我每天拍摄所有演员的快照,如果有变化,我会同时更新所有更改的属性。这导致每年365个时间戳。
如果我使用另一个表来管理版本(时间戳)并将另一个外键添加到ActorProperty
表,我将获得365 * 1000万个条目。这应该是我得到的最大值。大多数情况下,数据集将显着缩小。
我现在的问题是更多地解决这个问题。我阅读了有关索引的以下答案:How does database indexing work。查询具有该数量条目的表是不是非常慢?一个示例查询将是:在给定时间戳id = x的前100个actor及其所有属性。我觉得我想到的架构可能不是最好的。有没有人对具有更高可扩展性的模式有任何建议或想法?
顺便说一句,我目前还在评估NoSql方法,所以我想暂时专注于关系方法。我的目标是收集不同技术的优点和缺点,然后为所描述的用例提供理论架构或模型。在关系数据库中使用最佳模型的性能是我看似无法评估或找到的。
谢谢!
答案 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_TYPE
和ID
列的NAME
表。这种方式在主PROPERTY
表格中有ACTOR_ID
,PROPERTY_TYPE_ID
和VALUE
列,这样可以带来2个好处:
现在进行物业追踪。我喜欢这种方法,当一个人及时跟踪对象的实例时,每个实例都有它的开始和结束时间。可以使用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)
);
有关实施的说明:
START TRANSACTION; ... COMMIT;
块或(我更喜欢)创建一个能够完成工作的函数是件好事; 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编写新属性时,应首先复制现有值并将其插入可版本化表中,然后然后将新值添加到当前表中。将其包含在交易中以确保其安全。
通常,属性查询通常对当前属性值感兴趣,并且需要更少地访问过去的值(当然,您需要对自己的用例做出判断)。每次询问数据时,它确实需要两个不同的查询(当前值,过去值),但性能优势可能是值得的。