常见的数据模型构建方法有范式建模法、维度建模法和实体建模法等。这些方法从不同的角度看待业务中的问题,代表了哲学上的一种世界观。其中,范式建模法是一种常用的技术层面的方法,它主要解决关系型数据库的数据存储问题。目前,在关系型数据库中的建模方法大部分采用的是三范式建模法。

范式是指符合某一种级别的关系模式的集合。构造数据库必须遵循一定的规则,而在关系型数据库中这种规则就是范式,这一过程也被称为规范化。目前关系数据库有六种范式:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、Boyce-Codd范式(BCNF)、第四范式(4NF)和第五范式(5NF)。

在数据仓库的模型设计中,一般采用第三范式。一个符合第三范式的关系必须具有以下三个条件:

* 每个属性值唯一,不具有多义性;

* 每个非主属性必须完全依赖于整个主键,而非主键的一部分;

* 每个非主属性不能依赖于其他关系中的属性,因为这样的话,这种属性应该归到其他关系中去。

根据 Inmon 的观点,数据仓库模型得建设方法和业务系统的企业数据模型类似。在业务系统中,企业数据模型决定了数据的来源,而企业数据模型也分为两个层次,即主题域模型和逻辑模型。同样地,主题域模型可以看成是业务模型的概念模型,而逻辑模型则是域模型在关系型数据库上的实例话。

从业务数据模型转向数据仓库模型时,同样也需要有数据仓库的域模型,即概念模型,同时也存在域模型的逻辑模型。这里,业务模型中的数据模型和数据仓库的模型稍微有一些不同。主要区别在于:数据仓库的域模型应该包含企业数据模型得域模型之间的关系以及各主题域定义;并且数据仓库的域模型的概念应该比业务系统的主题域模型范围更加广。

Inmon 的范式建模法的最大优点是能够比较方便地实现数据仓库的建模。该方法从关系型数据库的角度出发,结合了业务系统的数据模型,具有一定的灵活性和性能。但是,由于建模方法限定在关系型数据库之上,有时候反而限制了整个数据仓库模型的灵活性和性能。例如,当需要将底层数据向数据集市的数据进行汇总时,需要进行一定的变通才能满足相应的需求。

维度建模法则是以分析决策的需求出发构建模型。该方法构建的数据模型为分析需求服务,重点解决用户如何更快速完成分析需求,同时还有较好的大规模复杂查询的响应性能。典型的代表是我们比较熟知的星形模型(Star-schema),以及在一些特殊场景下适用的雪花模型(Snow-schema)。

在维度建模中,有两个重要的概念:事实表(Fact table)和维度表(Dimension table)。按照这两个表来构建数据仓库、数据集市是最简单的描述。

事实数据表是存储在数据仓库中的实际事件的度量值,每个事实数据表包含一个由多个部分组成的索引,该索引包含作为外键的相关性维度表的主键,而维度表包含事实记录的特性。事实数据表的主要特点是包含数字数据(事实),并且这些数字信息可以汇总,以提供有关单位作为历史的数据。事实数据表不应该包含描述性的信息,也不应该包含除数字度量字段及使事实与维度表中对应项的相关索引字段之外的任何数据。事实数据表中的“度量值”有两种:一种是可以累计的度量值,另一种是非累计的度量值。最有用的度量值是可累计的度量值,其累计起来的数字是非常有意义的。用户可以通过累计度量值获得汇总信息。非累计的度量值也可以用于事实数据表,但汇总结果一般是没有意义的。一般来说,一个事实数据表都要和一个或多个维度表相关联。

维度表可以看作是用户来分析数据的窗口,维度表中包含事实数据表中事实记录的特性,有些特性提供描述性信息,有些特性指定如何汇总事实数据表数据,以便为分析者提供有用的信息。维度表包含帮助汇总数据的特性的层次结构。每个维度表都包含单一的主键列。维度表的主键可以作为与之关联的任何事实表的外键。当然,维度表行的描述环境应与事实表行完全对应。

上图所示是一个典型的星型架构。星型模式之所以广泛被使用,在于针对各个维作了大量的预处理,如按照维进行预先的统计、分类、排序等。通过这些预处理,能够极大的提升数据仓库的处理能力。特别是针对3NF的建模方法,星型模式在性能上占据明显的优势。同时,维度建模法另外一个优点就是非常直观,紧紧围绕着业务模型,可以直观地反映出业务模型中的业务问题。不需要经过特别的抽象处理即可完成维度建模。这一点也是维度建模的优势所在。

维度建模法的缺点显而易见:在构建星型模式之前,需要进行大量的数据预处理,这将导致大量的数据处理工作。当业务发生变化,需要重新定义维度时,往往需要重新进行维度数据的预处理。在这些过程中,可能会产生大量冗余数据。此外,仅依靠维度建模无法保证数据来源的一致性和准确性,因此在数据仓库底层并不特别适用。虽然维度建模主要用于解决数据仓库建模中的性能问题,但它很难提供一个完整描述真实业务实体之间复杂关系的方法。

实体建模法并非数据仓库建模中常见的方法,它来源于哲学的一个流派。从哲学意义上说,客观世界应该是可以细分的,由实体和实体之间的关系组成。因此,在数据仓库建模过程中,完全可以引入这种抽象方法,将整个业务划分为实体、事件和说明。实体建模法可以轻松实现业务模型的划分,因此在业务建模阶段和领域概念建模阶段具有广泛应用。在没有现成行业模型的情况下,可以采用实体建模方法与客户一起理清整个业务模型,进行领域概念模型的划分,抽象出具体的业务概念,结合客户使用特点,创建出一个符合需求的数据仓库模型。

以下是重构后的内容:

数据仓库建模方法有很多种,每种方法都有自己的优点和局限性。在创建自己的数据仓库模型时,可以参考使用上述的三种数据仓库建模方法,在各个不同阶段采用不同的方法,从而能够保证整个数据仓库建模的质量。

维度建模方法是最常用的一种建模方法之一。这种方法由Kimball最先提出,其最简单的描述就是按照事实表、维度表来构建数据仓库、数据集市。在维度建模方法体系中,维度是描述事实的角度,如日期、客户、供应商等;事实是要度量的指标,如客户数、销售额等。

除了维度建模之外,还有范式建模法和实体建模法等其他建模方法。这些方法各有优缺点。但是很少有直接回答一个问题:也就是数据仓库为什么要采用维度建模?

为了能更真切地理解什么是维度建模,我将在后续的文章中模拟一个大家都十分熟悉的电商场景,运用讲到的理论进行建模。理论和现实的工作场景毕竟会有所差距。这一块我会分享一下企业在实际的应用中所做出的取舍。

一、维度建模简介

维度建模是一种数据仓库技术,我们可以通过星型模型和雪花模型来实现。星型模型是维度建模的典型代表,由事实表和维度表组成。事实表用于存储业务操作中的度量数值,维度表则包含了与业务相关的描述性属性。

二、维度建模的基本要素

1. 事实表

事实表是维度建模的核心,用于存储现实世界中的操作型事件所产生的可度量数值。从最低的粒度级别来看,事实表行对应一个度量事件,反之亦然。

2. 维度表

维度表包含单一的主键列,可以作为与之关联的任何事实表的外键。维度表通常比较宽,是扁平型非规范表,包含大量的低粒度的文本属性。常见的维度表有客户表、商品表、时间表等。

三、维度模型的优缺点

1. 优点:

- 数据冗余小:因为很多具体的信息都存在相应的维度表中了,比如客户信息就只有一份。

- 结构清晰:表结构一目了然。

- 便于做OLAP分析:数据分析用起来会很方便。

2. 缺点:

- 增加使用成本:比如查询时要关联多张表。

- 数据不一致:比如用户发起购买行为的时候的数据,和我们维度表里面存放的数据不一致。

四、没有数据仓库的宽事实表的优缺点

1. 优点:

- 业务直观:在做业务的时候,这种表特别方便,直接能对到业务中。

- 使用方便:写sql的时候很方便。

2. 缺点:

- 数据冗余巨大:在几亿的用户规模下,他的订单行为会很恐怖、粒度僵硬,什么都写死了,这张表的可复用性太低。

数据仓库建模方法

数据仓库的建模方法有很多种,我目前主要学习了解的是维度建模方法。接下来,我将尝试撰写关于数据仓库系列文章,文中如有错误或误导的地方,欢迎大家指出并予以纠正。希望这篇文章能够给大家带来帮助,最后感谢大家的阅读。

----------------------

概念建模

数据建模大致分为三个阶段:概念建模阶段、逻辑建模阶段和物理建模阶段。在这其中,概念建模和逻辑建模阶段与数据库厂商毫无关系,换言之,与MySQL、SQL Server、Oracle等没有直接关系。而物理建模阶段则与数据库厂商存在很大的联系,因为不同厂商对同一功能的支持方式不同,如高可用性、读写分离、甚至是索引、分区等。

概念建模阶段:

在实际工作中,我们主要做三件事:1. 客户交流;2. 理解需求;3. 形成实体。这也是一个迭代过程,如果先有需求,尽量去理解需求,明白当前项目或者软件需要完成什么,不明白或者不确定的地方要及时与客户交流,并与客户确认过的需求落实到实体(Package)。但是很多时候我们需要通过先与客户交流,进而将交流结果落实到需求,之后再进一步具体到实体。本文可能会涉及到一些来自于EA(Enterprise Architect 7.1)建模术语,EA中将每个实体视为一个Package。这里并不对各种建模工具进行比较,如Visio、EA、PowerDesigner、ERWin等;实际上,作为员工的我们在选择上并没有太多余地,公司有哪个产品的Licence,我们就用哪个吧。

举例说明:在一个B2C电子商务网站中,这样的需求再普通不过了:客户可以在该网站上自由进行购物!我们就以这个简单例子,对其进行细分,来讲解整个数据建模的过程。通过上面这句话,我们可以得出三个实体:客户、网站、商品。就像Scrum(敏捷开发框架的一种)中倡导的一样,每个Sprint都要产出确确实实的东西。因此,在概念建模阶段,我们就要产出实体:客户和商品(我们将网站这个实体暂时忽略掉,因为在这个简单的例子中它并不需要)。

在创建这两个实体(Package)的时候,我们需要注意需求的理解以及业务规则,并将这些信息以Notes的形式添加到Package中。这些信息将在未来成为数据字典中的重要组成部分,即所谓的元数据。值得一提的是,EA或其他建模工具应该可以自动生成数据字典,只是最终生成的格式可能有所不同。以Customer为例,在Package的Notes上,我们可以这样写:用户需要通过填写个人基本信息和一个邮箱来注册账户,之后使用该邮箱作为登录账号登录系统进行交易。

在概念建模阶段,我们只需关注实体本身,无需关注任何实现细节。许多人希望在这个阶段就考虑具体的表结构、索引、约束甚至存储过程,但这是不必要的。因为这些内容将在物理建模阶段进行考虑,而目前还为时尚早。也许有人担心会遗漏或忽略某些实体?不必担心,许多公司在2013年开始采用Scrum开发模式。只要你当前抽象出的实体满足当前的User Story或User Story中的实体,你就已经抽象出来了。如果你觉得User Story太大,实体太多,不容易抽象,那么建议你们的团队重新召开Sprint计划会议。

逻辑建模阶段是对实体进行细化,形成具体的表结构,并丰富表结构。这个阶段的产物是在数据库中生成的具体表及其他数据库对象,包括主键、外键、属性列、索引、约束甚至视图和存储过程。在实际项目中,我将在物理建模阶段建立除主外键之外的所有数据库对象,因为其他数据库对象更贴近开发,需要与开发一起进行。例如,约束可以在Web页面上使用JavaScript实现,也可以在业务逻辑层或数据库中实现。具体实现方式应根据实际需求、性能和安全性来确定。

以Customer实体及我们对需求的理解为例,我们可以得出以下几个表的结构:用户基本信息表(User)、登录账户表(Account)、评论表(Commnets),用户可能会对产品进行评价。当然,在这个案例中还会有更多的表,如用户需要自行上传头像(图片),则需要Picture表。

针对产品实体,我们需要构建以下几个表来存储相关信息:

1. 产品基本信息表(Product):存储产品的基本信息,如产品ID、名称、描述等。

2. 产品大类表(ProductCategory):存储产品的大类信息,如大类ID、名称等。一个产品可能会属于不同的大类,因此需要建立这个表来表示产品和大类之间的关系。

3. 产品小类表(ProductSubCategory):存储产品的小类信息,如小类ID、名称等。一个产品可能会属于多个小类,因此需要建立这个表来表示产品和小类之间的关系。

4. 产品折扣表(ProductDiscount):存储产品的折扣信息,如折扣ID、折扣名称、折扣比例、生效时间等。某些产品会因为节假日等原因进行打折,因此需要创建这个表来存储这些信息。

5. 产品图片表(ProductPicture):存储产品的图片信息,如图片ID、图片名称、图片URL等。一个产品可能会有多张图片,因此需要建立这个表来表示产品和图片之间的关系。

6. 产品图片关系表(ProductPictureRelationship):存储产品图片之间的关系,如关联的图片ID和对应的产品ID。如果我们将所有的图片存放在一张Picture表中,那么可以通过这张关系表来实现一对多的关系。

7. 交易表(Transaction):存储客户的交易信息,如交易ID、客户ID、交易日期等。一笔交易会涉及到一个或多个商品,因此需要创建这个表来记录交易关系。

8. 商品折扣与交易关联表(Item):存储交易中涉及的商品折扣信息,如TransactionID和ProductDiscountID(s)。这个表用于表示一笔交易中涉及到的折扣信息。

以上这些表共同构成了一个完整的产品管理系统的数据模型。通过这些表的设计,可以实现对产品和相关数据的统一管理和查询。

根据需求,我们确定具体需要哪些表,并进一步丰富每个表的属性(Column)。在这个过程中,我们需要考虑主键的选择、代理键(Surrogate Key)的使用、外键关联以及约束设置等细节。笔者认为,只要将每个实体属性(Column)明确落实即可,因为随着项目的开展,很多表的Column都会有相应的改动。不同数据库厂商在实现细节上可能有所不同。

关于主键的选择,有人喜欢使用自增长ID作为主键,而有人则希望找到一个或多个属性作为唯一标识当前记录的主键。将自增长ID作为代理主键时,在将来以多个类似当前Transaction System作为数据源构建数据仓库时,可能会遇到一些麻烦(多个系统中,相同表存在大量主键重复)。因此,选择一个属性或多个属性作为主键时,无论它们是否可编辑,读写效率都是我们需要考虑的因素。这里并没有放之四海而皆准的原则,只是给大家提供一些建议。

物理建模阶段,EA可以将逻辑建模阶段创建的各种数据库对象生成为相应的SQL代码,用于创建具体的数据库对象。然而,在这个阶段,我们不仅仅创建数据库对象,还会根据业务需求进行数据拆分(水平或垂直拆分)。例如,对于一个B2B网站,我们可以将商家和普通用户放在同一张表中。但从性能考虑,我们可以将其分为两张表。随着业务量的上升,Transaction表变得越来越大,整个系统运行速度变慢。此时,我们可以考虑数据拆分,甚至采用读写分离(即实现MASTER-SLAVE模式)。在MySQL/SQLServer中,可以使用Replication来实现这一点。不同存储引擎采用不同的方案。在这个阶段,还可能会涉及到集群的问题。如果你是架构师或者数据建模师,可以跟DBA说:“好的,我已经完成了建模工作,接下来就看你们的表现了。”

众所周知,范式非常重要。许多人将3NF视为经典之作,但3NF是在几十年前提出的。那时的数据量和访问频率与2012年完全不在一个数量级上。因此,在数据建模过程中,我们不能一味地遵守3NF。在保证数据结构清晰的前提下,提高性能才是我们关注的重点。因此,笔者强烈建议在适当的情况下增加数据冗余。

在这篇文章中,笔者结合了一些实际的例子来阐述自己对数据建模的观点,希望这些观点能对读者有所帮助。在进行数据建模时,我们不应期待一次性就设计出完美的数据库。无论是针对数据仓库还是事务型数据库的设计方案,笔者从未有过一次成功的经验。随着项目的推进,客户和开发团队对业务知识的掌握日益深入,因此原有的设计也在不断地完善和调整。

毕竟,数据建模或设计数据库并非我们的最终目标。我们需要的是一个健壮、性能优越、易于扩展和使用的软件系统 [3] !