很多人看来,数据库Schema设计是一件非常简单的事情,大体按照系统设计时候的相关实体对象对应成一个一个表格就可以了。为了在功能上尽可能容易扩展,根据数据库范式规则进行调整,做到第三范式或第四范式,基本就算完事了
真的这么简单么?看一个案例
需求概述:一个简单的讨论区系统,需要有用户、用户组、组讨论区这三部分基本功能
简要分析:
(1)须要存放用户数据的表;
(2)须要存放分组信息和用户与组关系的表;
(3)须要存放讨论信息的表
方案一:分别用4个表来存放用户、分组、用户与组关系,以及各组的讨论帖子的信息,如下所示。
user用户表:
id
nick_name
password
email
status
sexuality
msn
sign
birthday
hobby
location
description
groups分组表:
id
gmt_create
gmt_modified
name
status
description
user_group关系表:
user_id
group_id
user_type
gmt_create
gmt_modified
status
group_message讨论组帖子表:
id
gmt_create
gmt_modified
group_id
user_id
subject
content
方案二
user用户表:
id
nick_name
password
email
status
user_profile用户属性表(记录与user一一对应):
user_id
sexuality
msn
sign
birthday
hobby
location
description
groups和user_group这两个表和方案一完全一样
group_message讨论组帖子表:
id
gmt_create
gmt_modified
group_id
user_id
subject
author
group_message_content帖子内容表(记录与group_mes-sage一一对应):
group_msg_id
content
区别主要体现在两点上
一个是在group_message表中增加了author字段来存放发帖作者的昵称,与user表的nick_name相对应
另一个就是第二个方案将user表和group_message表都分拆成了两个表,分别是一一对应的
方案二看上去比方案一要更复杂一些,首先是表的数量多了2个,然后是在group_message中冗余存放了作者昵称
一个讨论区系统,访问最多的页面会是什么?是帖子标题列表页面。而帖子标题列表页面最主要的信息都来自group_message表中,同时帖子标题后面的作者一般都是通过用户名(昵称)来展示的。按照第一种解决方案设计,须要执行类似SQL
SELECT t.id, t.subject,user.id, u.nick_name
FROM (
SELECT id, user_id, subject
FROM group_message
WHERE group_id = ?
ORDER BY gmt_modified DESC LIMIT 20
) t, user u
WHERE t.user_id = u.id
但是第二种解决方案就会简单很多
SELECT t.id, t.subject, t.user_id, t.author
FROM group_message
WHERE group_id = ?
ORDER BY gmt_modified DESC LIMIT 20
不仅如此,由于一方案中的group_message表中包含一个大字段“content”,该字段所存放的信息要占整个表的绝大部分存储空间,但在这条系统中执行最频繁的Query完全不需要该字段所存放的信息,可是这个Query没办法不访问group_message表的数据,所以第一条Query在数据读取过程中会须要读取大量没有任何意义的数据
在系统中用户数据的读取也是比较频繁的,但是大多数地方需要的用户数据只是几个基本属性,如用户的id、昵称、密码、状态、邮箱等,所以将用户表的这几个属性单独分离出来,也会让大量的Query语句在运行的时候减少数据的检索量,从而提高性能
可能有人会觉得,将一个表分成两个表,如果要访问被分拆出去的信息,性能不是就会变差了吗?是的,但是由于两个表都是一对一的关联关系,关联字段的过滤性也非常高,而且这样的查询需求在整个系统中所占有的比例也并不高,这里带来的性能损失实际上要远远小于在其他Query上节省出来的资源