从Nettuts+文章中阅读到的建议后,我设计了一个表模式来将高度不稳定的数据与经常被查询的其他表分离,并同时降低整个数据库模式所需的表数目。然而,由于它不遵循规范化的规则,我不确定这是否是一个好主意,我想听听你的建议,以下是一般想法:
在一个 Class Table Inheritance 结构中,我对四种用户进行了建模,在主要的“用户”表中,我存储所有用户的共同数据(id
, username
, password
, 几个 flags
等)以及一些时间戳字段(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
是指TABLE
表中的主键字段(id
),我相信其余部分应该很简单。这种设计中我喜欢的一点是,我可以跟踪所有用户登录,而不仅仅是最后一个登录,因此可以利用该数据生成一些有趣的指标。
由于events
表的不断增长,我还考虑进行一些优化,例如:
- #9:由于表的数量和事件的数量是有限的(并且预先确定的),
TABLE
和EVENTS
列可以设置为ENUM
,而不是VARCHAR
,以节省一些空间。 - #14:使用
INET_ATON()
将IP
存储为UNSIGNED INT
,而不是VARCHAR
。 - 将
DATE
存储为TIMESTAMP
,而不是DATETIME
。 - 使用
ARCHIVE
引擎(或)而不是CSV
?InnoDB
/MyISAM
。- 仅支持
INSERT
和SELECT
,并且数据在传输过程中进行了压缩。
- 仅支持
总的来说,每个事件只会消耗14个(未压缩)字节,这对于我的流量来说还可以接受。
优点:
- 能够存储更详细的数据(例如登录)。
- 不需要设计(和编写)近十个附加表(日期和统计信息)。
- 减少了每个表的一些列并将易变的数据分离。
缺点:
- 非关系型(仍不如EAV糟糕):
SELECT * FROM events WHERE id = 2 AND table = 'user' ORDER BY date DESC();
- 每个事件的开销为6个字节(
ID
、TABLE
和EVENT
)。
我更倾向于采用这种方法,因为优点似乎远远超过了缺点,但我仍然有些犹豫...我有什么遗漏的吗?您对此有什么想法?
谢谢!
@coolgeek:
我做的一件稍微有些不同的事情是维护一个entity_type表,并在对象类型列(在您的情况下为“TABLE”列)中使用其ID。 对于event_type表,您还需要执行相同的操作。
只是为了明确,您的意思是我应该添加一个额外的表来映射哪些事件在表中允许,并在事件表中使用该表的主键,而不是使用 TABLE /
EVENT 对吗?
@ben:
这些都是从现有数据派生出来的统计数据,是吗?
额外的表大多与统计数据相关,但我没有已经存在的数据,以下是一些示例:
user_ad_stats user_post_stats
------------- ---------------
user_ad_id (FK) user_post_id (FK)
ip ip
date date
type (impressed, clicked)
如果我删除这些表,就没有办法跟踪谁、什么和何时发生了什么事情,不确定视图在这里能起到什么作用。
我同意应该分开处理,但更多的是因为它们是基本不同的数据。一个人是什么,一个人做什么是两回事。我认为波动性并不那么重要。
我听说过两种方法,并且我在MySQL手册中找不到任何说明哪个是正确的。无论如何,我同意您的观点,它们应该是分离的表,因为它们代表了不同类型的数据(与正常方法相比具有更加描述性)。
我觉得你对局面看得太细了。
你的表达式是“User ID from IP IP at time DATE EVENTed to TABLE”,这似乎是合理的,但存在问题。
我所指的“不像EAV那么糟”是指所有记录都遵循线性结构,并且它们非常容易查询,没有分层结构,因此所有查询都可以通过简单的SELECT
完成。
关于您的第二个声明,在这里我认为您误解了我的意思;IP地址不一定与用户相关联。表结构应该读取如下:
IP地址(IP
)对表的PK(ID
)进行了某些操作(EVENT
)(TABLE
),日期为(DATE
)。
例如,在上面的示例的最后一行中,它应该读取IP 217.0.0.1(某个管理员)在2010-04-20 03:20:00删除了用户#2(其最后已知的IP是127.0.0.2)。
您仍然可以将用户事件与用户连接起来,但无法实现外键约束。
确实,这是我的主要关注点。但是我不完全确定这种设计可能出现什么问题,而传统的关系设计不能出现。我可以发现一些警告,但只要处理数据库的应用程序知道它在做什么,我想就不应该有任何问题。
另外一件事也算是这个论点的一部分,那就是我将存储更多的事件,并且每个事件将比原始设计增加一倍以上,使用ARCHIVE
存储引擎在这里是很合理的选择,唯一的问题是它不支持FK
(也不支持UPDATE
或DELETE
)。