范式和反范式

三范式:

◆ 第一范式(1NF):强调的是列的原子性,即列不能够再分成其他几列。

考虑这样一个表:【联系人】(姓名,性别,电话)

如果在实际场景中,一个联系人有家庭电话和公司电话,那么这种表结构设计就没有达到 1NF。要符合 1NF 我们只需把列(电话)拆分,即:【联系人】(姓名,性别,家庭电话,公司电话)。1NF 很好辨别,但是 2NF 和 3NF 就容易搞混淆。

◆ 第二范式(2NF):首先是 1NF,另外包含两部分内容,一是表必须有一个主键;二是没有包含在主键中的列必须完全依赖于主键,而不能只依赖于主键的一部分。

考虑一个订单明细表:【OrderDetail】(OrderID,ProductID,UnitPrice,Discount,Quantity,ProductName)。

因为我们知道在一个订单中可以订购多种产品,所以单单一个 OrderID 是不足以成为主键的,主键应该是(OrderID,ProductID)。显而易见 Discount(折扣),Quantity(数量)完全依赖(取决)于主键(OderID,ProductID),而 UnitPrice,ProductName 只依赖于 ProductID。所以 OrderDetail 表不符合 2NF。不符合 2NF 的设计容易产生冗余数据。

可以把【OrderDetail】表拆分为【OrderDetail】(OrderID,ProductID,Discount,Quantity)和【Product】(ProductID,UnitPrice,ProductName)来消除原订单表中UnitPrice,ProductName多次重复的情况。

◆ 第三范式(3NF):首先是 2NF,另外非主键列必须直接依赖于主键,不能存在传递依赖。即不能存在:非主键列 A 依赖于非主键列 B,非主键列 B 依赖于主键的情况。

考虑一个订单表【Order】(OrderID,OrderDate,CustomerID,CustomerName,CustomerAddr,CustomerCity)主键是(OrderID)。

其中 OrderDate,CustomerID,CustomerName,CustomerAddr,CustomerCity 等非主键列都完全依赖于主键(OrderID),所以符合 2NF。不过问题是 CustomerName,CustomerAddr,CustomerCity 直接依赖的是 CustomerID(非主键列),而不是直接依赖于主键,它是通过传递才依赖于主键,所以不符合 3NF。

通过拆分【Order】为【Order】(OrderID,OrderDate,CustomerID)和【Customer】(CustomerID,CustomerName,CustomerAddr,CustomerCity)从而达到 3NF。


范式的优缺点

优点:

1.范式化的更新操作通常比反范式化要快。

2.当数据较好的范式化时,就只有很少或者没有重复数据,所以只需要修改更少的数据。

3.范式化的表通常更小,可以更好地放在内存里,所以执行操作会更快。

4.很少有多余的数据意味着检索列表数据时更少需要DISTINCT或者GROUP BY语句。

缺点:

范式化设计的schema的缺点是通常需要关联。稍微复杂一些的查询语句在符合范式的schema上都可能需要至少一次关联,也许更多。这不但代价昂贵,也可能使一些索引策略无效。例如,范式化可能将列存放在不同的表中,而这些列如果在一个表中本可以属于同一个索引。

反法式的优缺点

优点:

反范式化的schema因为所有数据都在一张表中,可以很好地避免关联。

如果不需要关联表,则对大部分查询最差的情况-即使表没有使用索引-是全表扫描。当数据比内存大时这可能比关联要快得多,因为这样避免了随机I/O。

关联会使的,在执行数据库语句时,使得需要在一个索引中又排序又过滤。如果采用反范式化组织数据将两张表的字段合并一下,并且增加一个索引,就可以不通过关联写出这个查询。

缺点:

表格内的冗余较多,删除数据时候会造成表有些有用的信息丢失。


混用范式化和反范式化

完全范式和完全反范式在实际的开发中几乎不存在。

最常见的反范式化数据的方法是复制或者缓存,在不同的表中存储相同的特定列。在MySQL5.0和更新版中,可以使用触发器更新缓存值,这使得实现这样的方案变得更简单。

在一个网站实例中,这个网站,允许用户发送消息,并且一些用户是付费用户。现在想查看付费用户最近的10条信息。  在user表和message表中都存储用户类型(account_type)而不用完全的反范式化。这避免了完全反范式化的插入和删除问题,因为即使没有消息的时候也绝不会丢失用户的信息。这样也不会把user_message表搞得太大,有利于高效地获取数据。

另一个从父表冗余一些数据到子表的理由是排序的需要。

缓存衍生值也是有用的。如果需要显示每个用户发了多少消息(类似论坛的),可以每次执行一个昂贵的自查询来计算并显示它;也可以在user表中建一个num_messages列,每当用户发新消息时更新这个值。

推荐阅读更多精彩内容