29

在阅读了这篇很棒的Nettuts+ 文章的提示后,我想出了一个表模式,它将高度易变的数据与其他遭受大量读取的表分开,同时减少了整个数据库模式中所需的表数量,但是我'不确定这是否是一个好主意,因为它不遵循规范化规则,我想听听你的建议,这是一般的想法:


我在类表继承结构中建模了四种类型的用户,在主“用户”表中,我存储所有用户共有的数据(id, username, password, 几个flags, ...)以及一些TIMESTAMP字段(date_created, date_updated, date_activated, date_lastLogin, . ..)。

引用上面提到的 Nettuts+ 文章中的提示 #16:

示例 2:您的表中有一个“last_login”字段。每次用户登录网站时它都会更新。但是对表的每次更新都会导致该表的查询缓存被刷新。您可以将该字段放入另一个表中,以尽量减少对用户表的更新。

现在它变得更加棘手,我需要跟踪一些用户统计信息,例如

  • 用户个人资料被查看的唯一次数
  • 来自特定类型用户的广告被点击的唯一次数
  • 特定类型用户的帖子被查看的唯一次数
  • 等等...

在我完全规范化的数据库中,这增加了大约 8 到 10 个额外的表,虽然不是很多,但如果可以的话,我想保持简单,所以我想出了以下“ events”表:

|------|----------------|----------------|---------------------|-----------|
| ID   | TABLE          | EVENT          | DATE                | IP        | 
|------|----------------|----------------|---------------------|-----------|
| 1    | user           | login          | 2010-04-19 00:30:00 | 127.0.0.1 |
|------|----------------|----------------|---------------------|-----------|
| 1    | user           | login          | 2010-04-19 02:30:00 | 127.0.0.1 |
|------|----------------|----------------|---------------------|-----------|
| 2    | user           | created        | 2010-04-19 00:31:00 | 127.0.0.2 |
|------|----------------|----------------|---------------------|-----------|
| 2    | user           | activated      | 2010-04-19 02:34:00 | 127.0.0.2 |
|------|----------------|----------------|---------------------|-----------|
| 2    | user           | approved       | 2010-04-19 09:30:00 | 217.0.0.1 |
|------|----------------|----------------|---------------------|-----------|
| 2    | user           | login          | 2010-04-19 12:00:00 | 127.0.0.2 |
|------|----------------|----------------|---------------------|-----------|
| 15   | user_ads       | created        | 2010-04-19 12:30:00 | 127.0.0.1 |
|------|----------------|----------------|---------------------|-----------|
| 15   | user_ads       | impressed      | 2010-04-19 12:31:00 | 127.0.0.2 |
|------|----------------|----------------|---------------------|-----------|
| 15   | user_ads       | clicked        | 2010-04-19 12:31:01 | 127.0.0.2 |
|------|----------------|----------------|---------------------|-----------|
| 15   | user_ads       | clicked        | 2010-04-19 12:31:02 | 127.0.0.2 |
|------|----------------|----------------|---------------------|-----------|
| 15   | user_ads       | clicked        | 2010-04-19 12:31:03 | 127.0.0.2 |
|------|----------------|----------------|---------------------|-----------|
| 15   | user_ads       | clicked        | 2010-04-19 12:31:04 | 127.0.0.2 |
|------|----------------|----------------|---------------------|-----------|
| 15   | user_ads       | clicked        | 2010-04-19 12:31:05 | 127.0.0.2 |
|------|----------------|----------------|---------------------|-----------|
| 2    | user           | blocked        | 2010-04-20 03:19:00 | 217.0.0.1 |
|------|----------------|----------------|---------------------|-----------|
| 2    | user           | deleted        | 2010-04-20 03:20:00 | 217.0.0.1 |
|------|----------------|----------------|---------------------|-----------|

基本上是ID指表中的主键(id)字段TABLE,我相信其余的应该很简单。在这个设计中我喜欢的一件事是,我可以跟踪所有用户登录,而不仅仅是最后一个,从而用这些数据生成一些有趣的指标。

由于events表的不断增长的性质,我还考虑进行一些优化,例如:

  • #9:由于只有有限数量的表和有限(和预定)数量的事件,可以将TABLEandEVENTS列设置为ENUMs 而不是VARCHARs 以节省一些空间。
  • #14 :将 s 存储IPUNSIGNED INTsINET_ATON()不是VARCHARs。
  • 将 s存储DATETIMESTAMPs而不是DATETIMEs。
  • 使用ARCHIVE(CSV? ) 引擎代替InnoDB/ MyISAM
    • 仅支持INSERTs 和SELECTs,并且动态压缩数据。

总的来说,每个事件只会消耗 14 个(未压缩的)字节,我猜这对我的流量来说是可以的。

优点:

  • 能够存储更详细的数据(例如登录)。
  • 无需设计(和编写代码)几乎十几个附加表(日期和统计数据)。
  • 减少每个表的几列并保持易失性数据分离。

缺点:

  • 非关系型(仍然没有 EAV 差):
    • SELECT * FROM events WHERE id = 2 AND table = 'user' ORDER BY date DESC();
  • 每个事件的 6 字节开销(IDTABLEEVENT

我更倾向于采用这种方法,因为优点似乎远大于缺点,但我还是有点不情愿......我错过了什么吗?您对此有何看法?

谢谢!


@coolgeek:

我做的稍有不同的一件事是维护一个 entity_type 表,并在 object_type 列(在您的情况下为“TABLE”列)中使用其 ID。您可能希望对 event_type 表执行相同的操作。

为了清楚起见,您的意思是我应该添加一个附加表来映射表中允许的事件,并在事件表中使用该表的 PK 而不是TABLE/EVENT对?


@本:

这些都是从现有数据中得出的统计数据,不是吗?

附加表主要与统计信息有关,但我的数据尚不存在,一些示例:

user_ad_stats                          user_post_stats
-------------                          ---------------
user_ad_id (FK)                        user_post_id (FK)
ip                                     ip
date                                   date
type (impressed, clicked)

如果我删除这些表,我将无法跟踪谁、什么或何时,不确定视图如何在这里提供帮助。

我同意它应该是分开的,但更多的是因为它是根本不同的数据。某人是什么和某人做了什么是两件不同的事情。我认为波动性并不那么重要。

我听说过这两种方式,但我在 MySQL 手册中找不到任何说明任何一种都是正确的。无论如何,我同意你的观点,因为它们代表了各种数据(具有比常规方法更具描述性的额外好处),它们应该是单独的表格。

我认为您可以说是只见树木不见森林。

您的表的谓词将是“从 IP IP 在时间 DATE EVENTed 到 TABLE 的用户 ID”,这似乎是合理的,但存在问题。

我所说的“不如 EAV 差”的意思是所有记录都遵循线性结构,而且它们很容易查询,没有层次结构,所以所有查询都可以用一个简单的SELECT.

关于你的第二个陈述,我认为你在这里理解错了;IP 地址不一定与用户相关联。表结构应如下所示:

IP 地址 ( ) 在日期 ( )对表 ( )的 PK ( IP) 做了一些事情 ( )。EVENTIDTABLEDATE

例如,在我上面示例的最后一行中,它应该显示 IP 217.0.0.1(某个管理员),在 2010-04-20 03:20:00 删除了用户 #2(其最后一个已知 IP 是 127.0.0.2) .

例如,您仍然可以将用户事件加入用户,但您不能实现外键约束。

确实,这是我最关心的问题。但是,我不完全确定这种设计会出现什么问题,而传统的关系设计不会出现问题。我可以发现一些警告,但只要与数据库混淆的应用程序知道它在做什么,我想应该不会有任何问题。

在这个论点中重要的另一件事是我将存储更多的事件,并且每个事件将比原始设计增加一倍以上,在ARCHIVE这里使用存储引擎非常有意义,唯一的事情是它没有支持FKs(既不支持UPDATEs也不支持DELETEs)。

4

3 回答 3

5

我强烈推荐这种方法。由于您可能对 OLTP 和 OLAP 使用相同的数据库,因此您可以通过添加一些星形和雪花来获得显着的性能优势。

我有一个社交网络应用程序,目前有 65 张桌子。我维护一个表来跟踪对象(博客/帖子、论坛/线程、画廊/相册/图像等)视图,另一个用于对象推荐,以及第三个表来总结十几个其他表中的插入/更新活动。

我做的稍有不同的一件事是维护一个 entity_type 表,并在 object_type 列(在您的情况下为“TABLE”列)中使用其 ID。您可能希望对 event_type 表执行相同的操作。

澄清 Alix - 是的,您维护一个对象引用表和一个事件引用表(这些将是您的维度表)。您的事实表将包含以下字段:

id
object_id
event_id
event_time
ip_address
于 2010-04-20T05:56:45.977 回答
3

它看起来是一个非常合理的设计,所以我只是想挑战你的一些假设,以确保你有具体的理由来做你正在做的事情。

在我完全规范化的数据库中,这增加了大约 8 到 10 个额外的表

这些都是从现有数据中得出的统计数据,不是吗?(更新:好吧,他们不是,所以无视跟随。)为什么这些不只是视图,甚至物化视图?

然而,收集这些统计数据似乎是一项缓慢的操作:

  • 适当的索引可以使它相当快
  • 这不是一个常见的操作,所以速度并不重要
  • 消除冗余数据可能会使其他常见操作快速可靠

我想出了一个表模式,它将高度易失的数据与其他受到大量读取的表分开

我猜您是在谈论如何将非常不稳定的用户(仅选择一张表)事件与用户数据分开。我同意它应该是分开的,但更多的是因为它是根本不同的数据。某人是什么和某人做了什么是两件不同的事情。

我认为波动性并不那么重要。DBMS 应该已经允许您将日志文件和数据库文件放在不同的设备上,这完成了同样的事情,并且争用不应该是行级锁定的问题。

非关系型(仍然不如 EAV)

我认为您可以说是只见树木不见森林。

您的表的谓词将是“从 IP IP 在时间 DATE EVENTed 到 TABLE 的用户 ID”,这似乎是合理的,但存在问题。(更新:好的,所以有点像那样。)

例如,您仍然可以将用户事件加入用户,但您不能实现外键约束。这就是EAV 通常存在问题的原因。某物是否完全是 EAV 并不重要。在你的 schema 中实现一个约束通常是一两行代码,但在你的应用中可能是几十行代码,如果多个应用在多个地方访问相同的数据,它很容易乘以数千代码行。因此,一般来说,如果您可以使用外键约束来防止不良数据,则可以保证没有应用程序会这样做。

您可能认为事件并不那么重要,但例如,广告印象就是金钱。我肯定希望在设计过程中尽早发现与广告印象有关的任何错误。

进一步评论

我可以发现一些警告,但只要与数据库混淆的应用程序知道它在做什么,我想应该不会有任何问题。

并且有一些注意事项,您可以制作一个非常成功的系统。使用适当的约束系统,您可以说,“如果任何与数据库混淆的应用程序不知道它在做什么,DBMS 将标记错误。” 这可能需要比你拥有更多的时间和金钱,所以你可以拥有的更简单的东西可能比你不能拥有的更完美的东西更好。这就是生活。

于 2010-04-20T08:17:38.103 回答
0

我无法对 Ben 的回答添加评论,所以有两件事......

首先,在独立的 OLAP/DSS 数据库中使用视图是一回事。在您的事务数据库中使用它们是另一回事。高性能 MySQL 人员建议不要在性能很重要的地方使用视图

我同意 WRT 数据完整性,这是使用带有“事件”的星形或雪花作为中心事实表(以及像我一样使用多个事件表)的另一个优势。但是您不能围绕 IP 地址设计参照完整性方案

于 2010-04-20T13:42:00.107 回答