在阅读了这篇很棒的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:由于只有有限数量的表和有限(和预定)数量的事件,可以将
TABLE
andEVENTS
列设置为ENUM
s 而不是VARCHAR
s 以节省一些空间。 - #14 :将 s 存储
IP
为UNSIGNED INT
s而INET_ATON()
不是VARCHAR
s。 - 将 s存储
DATE
为TIMESTAMP
s而不是DATETIME
s。 - 使用
ARCHIVE
(或) 引擎代替CSV
?InnoDB
/MyISAM
。- 仅支持
INSERT
s 和SELECT
s,并且动态压缩数据。
- 仅支持
总的来说,每个事件只会消耗 14 个(未压缩的)字节,我猜这对我的流量来说是可以的。
优点:
- 能够存储更详细的数据(例如登录)。
- 无需设计(和编写代码)几乎十几个附加表(日期和统计数据)。
- 减少每个表的几列并保持易失性数据分离。
缺点:
- 非关系型(仍然没有 EAV 差):
SELECT * FROM events WHERE id = 2 AND table = 'user' ORDER BY date DESC();
- 每个事件的 6 字节开销(
ID
和TABLE
)EVENT
。
我更倾向于采用这种方法,因为优点似乎远大于缺点,但我还是有点不情愿......我错过了什么吗?您对此有何看法?
谢谢!
@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
) 做了一些事情 ( )。EVENT
ID
TABLE
DATE
例如,在我上面示例的最后一行中,它应该显示 IP 217.0.0.1(某个管理员),在 2010-04-20 03:20:00 删除了用户 #2(其最后一个已知 IP 是 127.0.0.2) .
例如,您仍然可以将用户事件加入用户,但您不能实现外键约束。
确实,这是我最关心的问题。但是,我不完全确定这种设计会出现什么问题,而传统的关系设计不会出现问题。我可以发现一些警告,但只要与数据库混淆的应用程序知道它在做什么,我想应该不会有任何问题。
在这个论点中重要的另一件事是我将存储更多的事件,并且每个事件将比原始设计增加一倍以上,在ARCHIVE
这里使用存储引擎非常有意义,唯一的事情是它没有支持FK
s(既不支持UPDATE
s也不支持DELETE
s)。