<audio id="audio" title="27 | 手把手带你设计一个完整的连锁超市信息系统数据库（上）" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/73/88/73efa8a13f586f9c03e34cc814e3f288.mp3"></audio>

你好，我是朱晓峰。

从创建第一个MySQL数据库开始到现在，我们已经学完了MySQL的核心操作。最后这两节课，我想带你实际设计一个超市信息系统的数据库。毕竟，设计数据库很考验我们综合运用单个技术点的能力。所以，通过这个项目，我不仅会带你把前面的内容串联起来，而且还会教你设计数据库的通用思路。

为什么选择超市项目呢？一方面呢，超市的场景与我们的日常生活密切相关，你比较容易理解其中的业务逻辑，另一方面，超市的业务又相当复杂，几乎能用到我们学到的所有知识点，利于我们对前面学过的内容进行整合。

今天，我就带你一起，从需求分析开始入手，一直到容灾和备份，完成一个全流程的连锁超市数据库设计。这节课，我会主要给你讲解需求分析、ER模型、分库分表和数据库设计方案。我们做项目，首先要从大处着眼，把握方向，这样才不容易出现大的偏差。下节课，我们再设计具体的细节，比如创建数据表、外键约束，设计灾备方案和备份数据等。

在开始设计之前，咱们得先了解一下项目背景。

随着互联网使用的日益广泛，传统的桌面管理系统的数据不能互通、资源利用率低等弊端越来越明显，显然不能满足用户的需求了。因此，我们需要开发一款基于云服务的连锁超市管理信息系统。具体的要求是：

1. 基于浏览器，无需安装，账号开通即可使用，方便快捷；
1. 数据部署在云端，由运营商负责维护，安全可靠；
1. 用户无需自备服务器，只需租用信息服务，资源利用率高。

知道了具体要求，那该怎么进行设计呢？下面我带你来分析一下。

## 如何设计数据结构？

用户账号开通即可使用，所以必然要设计分层的数据结构；数据要部署在云端，所以必然要使用云。根据这些要求，我们可以设计一个基于云服务的2层数据结构，这个结构的示意图如下所示：

<img src="https://static001.geekbang.org/resource/image/38/9d/38e25fa9d5e2b449yy498a60ef874e9d.jpg" alt="">

我来解释一下图中展示的内容。

首先，你可以看到，所有的系统资源和服务都部署在云端。

其次，我们来看下数据结构层面，主要有2层。

1. 第一层是商户。每个入驻的商户都有一个组号，所有与这个商户有关的数据，通过这个组号进行识别。
1. 第二层是分支机构。分支机构从属于商户，相同商户的分支机构有相同的组号。分支机构分为几种，包括总部、门店、配送中心等。门店又分为直营店和加盟店。每个分支机构有一个机构编号，同一分支机构的数据，有相同的组号和机构编号。

这样一来，新商户只需要开通账号，分配一个新的组号，就可以使用了。组号用于隔离商户之间的数据，使商户之间互相不影响。

最后，数据由我们统一进行运维，安全性有保障。商户自己不需要采购服务器，只需租用服务，资源的利用率更高。

系统的整体结构设计思路有了，那具体在应用层面如何实现呢？

我先用一张图来展示具体的应用构成：

<img src="https://static001.geekbang.org/resource/image/59/4c/5985a0c712296421bf7b36d50e4d2b4c.jpg" alt="">

这个图展示了应用的3个层级。

1. 展现层：包括门店收款机App、移动端的手机App、小程序，以及通过浏览器访问的后台管理部分。
1. 服务层：包括云端的销售模块、库存模块、营运模块、会员模块等。
1. 数据层：MySQL数据库。

门店收款App、移动端的手机App，小程序等与数据库设计无关，我就不多说了。下面我重点介绍一下后台管理部分下面的服务层和数据层的相关内容。

服务层包括了销售、库存、营运、会员等管理模块。下面我就以库存管理中的盘点模块为例，详细介绍一下。因为这个模块比较简单，容易理解。

盘点，简单来说，就是把超市仓库里的商品都数一遍，然后跟电脑里的库存数比对，看看有没有不对的地方。实际数出来的库存数量叫做盘存数量，电脑中的库存数量叫做结存数量，比对的结果叫做盈亏数量。要是盘存数量比结存数量多，叫盘盈，否则叫做盘亏。

盘点操作是超市库存管理模块中的一个重要环节，是掌握实际库存的唯一办法。盘点盈亏数据也是衡量超市管理水平的重要指标。

盘点作业一般都在晚上门店营业结束以后进行。这也很好理解，毕竟，在白天营业的过程中，商品不断被顾客取走，又不断得到补充，库存处于一种变化状态，无法获取准确数据。

下面我来介绍下盘点的步骤。

1. 先生成一张盘点表，把当前系统中的库存数量提取出来，获得结存数量；
1. 把员工实际数出来的库存数据录入盘点表中，获得盘存数量；
1. 计算盈亏数量，公式是“盈亏数量=盘存数量-结存数量”；
1. 数据确认无误后，验收盘点表，并调整当前库存：库存数量=库存数量+盈亏数量。

经过这些操作，系统中的库存数量与实际的库存数量就一致了，盘点盈亏也被记录下来了，体现在日报等报表中，超市经营者可以进行查看，为经营决策提供依据。

介绍完了盘点业务，现在回到数据库设计的主题上来，看看如何把盘点业务用数据表的形式表现出来。

盘点业务都是在门店进行，由员工实际操作，对仓库中的商品进行清点。因此，盘点业务模块中肯定要包含员工、门店、仓库、商品等实体。这个时候，我们就可以使用ER模型这个工具，来理清盘点模块的业务逻辑。

## 盘点模块的ER模型

我先把模型直接展示给你，一会儿我再带你具体分析一下。

<img src="https://static001.geekbang.org/resource/image/c2/93/c2a44370af7ba1538169bf82e78c5993.jpg" alt="">

首先，我们来分析下模型中的实体和关系。

这个ER模型中包括了5个实体，分别是：

1. 商户
1. 门店
1. 员工
1. 商品
1. 仓库

其中，商户和商品是强实体，门店、仓库和员工是弱实体。

这个ER模型中还包含了5个关系，我们按照1对多和多对多来分下类。

1对多：

1. 商户与门店的从属关系
1. 门店与员工的雇佣关系
1. 门店与仓库的拥有关系

多对多：

1. 仓库与商品的库存关系
1. 仓库、商品和员工参与的盘点关系

接下来，我们再分析一下这5个实体各自的属性。

1. 商户：组号、名称、地址、电话、联系人。
1. 门店：组号、门店编号、名称、地址、电话、类别。
1. 员工：组号、门店编号、工号、名称、身份证、电话、职责。
1. 仓库：组号、门店编号、仓库编号、类别。
1. 商品：组号、条码、名称、规格、单位、价格。

除此之外，还有2个多对多关系的属性。

1. 仓库与商品的库存关系：库存数量。
1. 仓库、商品和员工参与的盘点关系：盘存数量、结存数量和盈亏数量。

通过建立ER模型，我们理清了业务逻辑。接下来，我们就可以把盘点业务中这些实体和关系落实到实际的数据表了。

### ER模型转换成数据表

你还记得在[第23讲](https://time.geekbang.org/column/article/369434)里学的转换规则吗？强实体和弱实体转换成独立的数据表，多对多的关系转换成独立的数据表，1对多的关系转换成外键约束。

首先，我们把强实体转换成独立的数据表。

商户表（demo.enterprice）：

<img src="https://static001.geekbang.org/resource/image/bf/8f/bf5228f8513beb3bd9ab61747c8d5c8f.jpeg" alt="">

商品信息表（demo.goodsmaster）：

<img src="https://static001.geekbang.org/resource/image/65/a5/65d72ff47b7210968026ee4eba33fba5.jpeg" alt="">

接着，我们把弱实体转换成独立的数据表。

门店表（demo.branch）：

<img src="https://static001.geekbang.org/resource/image/89/31/8908eaa15ba0ef46f6a5346005087631.jpeg" alt="">

员工表（demo.employee）：

<img src="https://static001.geekbang.org/resource/image/54/87/54d3662c47d68443c468884099d6aa87.jpeg" alt="">

仓库表（demo.stockmaster）：

<img src="https://static001.geekbang.org/resource/image/44/a2/448b405b670498b70b4a088214f932a2.jpg" alt="">

第三步，把多对多的关系转换成独立的数据表。

库存表（demo.inventory）：

<img src="https://static001.geekbang.org/resource/image/b0/0d/b0dd7aff562eccb063ee81f44981f80d.jpeg" alt="">

盘点关系可以转换成2个表，分别是盘点单头表和盘点单明细表，这样做是为了满足第三范式的要求，防止冗余。

盘点单头表（demo.invcounthead）：

<img src="https://static001.geekbang.org/resource/image/68/ab/6884c011854524ae68898109eb2fccab.jpeg" alt="">

盘点单明细表（demo.invcountdetails）：

<img src="https://static001.geekbang.org/resource/image/0f/ba/0f357a14eed20a91980fe5b4a36f92ba.jpeg" alt="">

这样一来，我们就把ER模型中的实体和多对多的关系，转换成了独立的数据表。

这里你要注意的是，我在盘点单明细表中保留了组号和门店编号。这是因为，虽然这2个字段是冗余数据，但是可以加快查询的速度，经过权衡利弊，最后决定还是加上。

在我把1对多的关系转换成外键约束之前，我们还要进行一项重要的工作：分库分表。因为外键约束与数据表以及表中的字段有关，分库分表会影响到表和表中的字段。

而且外键约束需要在创建数据表的时候创建，所以咱们下节课和创建数据表一起讲，这节课我们先学习下分库分表。

在前面的课程中，每节课我们都是以具体技术点为核心展开的。而分库分表，只有在进行数据库系统整体设计的阶段才会用到。所以，今天我就结合咱们这个项目的系统设计，来给你具体讲一讲如何进行分库分表。

为什么要分库分表呢？当数据量足够大的时候，即便我们把索引都建好，系统资源调优到极致，仍然有可能遇到运行缓慢、CPU使用率居高不下的情况。因为单个数据库中单个表的数据量高到一定程度，超过了系统的承载能力。

面对这种情况，我们有2种选择：一种是购买更多的资源，增加内存，增加CPU的算力，但是这样会增加系统的成本。这个时候，我们就可以用另一种方法，也就是接下来我要讲的分库分表。

## 如何进行分库分表？

所谓的分库分表，其实就是把大的数据库拆成小数据库，把大表拆成小表，让单一数据库、单一数据表的数据量变小。这样每次查询时，需要扫描的数据量减少了，也就达到了提升查询执行效率的目的。

分库分表又可以分成垂直分表、垂直分库、水平分库和水平分表。

### 垂直分表

**所谓垂直分表，就是把一个有很多字段的表，按照使用频率的不同，拆分成2个或多个表**。

为了帮助你理解，还是用我们的盘点模块中的表来演示说明一下。

每个商户都有一个自己的商品信息表，数据量比较大。所以，我们可以拆分下这个表。我们把经常使用的字段条码、名称和价格，拆分成商品常用信息表（demo.goods_o）；把剩下的字段，也就是规格和单位拆分成商品不常用信息表（demo.goods_f）。

商品常用信息表：

<img src="https://static001.geekbang.org/resource/image/0c/5d/0c4bea15a6339f79f000429f4065985d.jpeg" alt="">

商品不常用信息表：

<img src="https://static001.geekbang.org/resource/image/a4/36/a4e5260d7e28fbeabf632edf3510cb36.jpeg" alt="">

至于商户表、门店表、 员工表、仓库表，这些表的数据量有限，不需要拆分。库存表、盘点单头表和盘点单明细表，虽然数据量大，但是评估之后，我们发现字段的使用频率都很高，拆分的价值不大，所以也不需要拆分。

下面我再介绍一下什么是垂直分库。

### 垂直分库

**垂直分库的意思是，把不同模块的数据表分别存放到不同的数据库中**。这样做的好处是，每个数据库只保存特定模块的数据，系统只有用到特定模块的数据时，才会访问这个数据库。这样就减少了数据库访问的次数，就相当于是把数据访问的流量分散了。

这个可能不太好理解，我来画一个简单的示意图：

<img src="https://static001.geekbang.org/resource/image/44/43/44ea44e705c890cbcd93eac6ae6a1043.jpg" alt="">

在这个图中，数据不再存储在一个数据库中，而是根据业务模块的不同，分别存储在不同的数据库中，比如销售数据库、库存数据库、营运数据库、会员数据库等。这样一来，业务模块可以主要与自己的数据库进行数据交互。业务内的数据交互多了，业务与业务之间的数据交互就可以大大减少了。

这样做的好处主要有3个：

1. 单个数据库的数据量减小了；
1. 单个数据库的访问流量分散了；
1. 系统整体的故障风险减小了。

下面我再介绍一下什么是水平分库和水平分表。

### 水平分库和水平分表

当垂直分表已经穷尽，垂直分库也不能再拆分的时候，我们还可以做水平分库和水平分表。

**水平分表的意思是，把数据表的内容，按照一定的规则拆分出去**。

盘点数据会不断累积，数据量越来越大。为了提升系统效率，我们制定了水平分表的策略。

第一步，我们把盘点单头表和盘点单明细表水平拆分：把验收处理过的盘点单头表和盘点单明细表拆分到盘点单头历史表和盘点单明细历史表。

这样做的好处是，盘点单头表和盘点单明细表经常需要进行插入、删除和修改操作，只保留当前正在处理的数据，可以提升效率，避免在一个不断增长的大表中进行DML操作。

而盘点单头历史表和盘点单明细历史表中的数据虽然不断增长，但数据不会修改，只进行查询操作。用经常作为筛选条件的字段创建索引，可以大大加快查询的速度。

拆分出来的盘点单头历史表（demo.invcountheadhist）与盘点单头表类似，不同之处是增加了验收人编号（confirmer）和验收日期（confirmationdate）。

盘点单头表历史表：

<img src="https://static001.geekbang.org/resource/image/0a/90/0aa2010fcaabc0782f02c6d1823d0f90.jpeg" alt="">

拆分出来的盘点单明细历史表（demo.invcountdetailshist）的字段则与盘点单明细表一样。

盘点单明细历史表：

<img src="https://static001.geekbang.org/resource/image/51/e7/51e7b78c773310c54322071ac1aebfe7.jpeg" alt="">

第二步，我们把组号大于500、小于1000的商户数据，拆分到另外的数据表里进行保存。这里的数字是我们根据对入驻平台商户的数据量进行评估之后得出的，在实际工作中，你可以根据实际情况来决定。**原则是：确保单个数据表中的数据量适中，不会成为操作的瓶颈**。

这样，我们就完成了对盘点模块中数据表的水平拆分。

接下来，我们来进行水平分库。水平分库的目的是使单个数据库中的数据量不会太大。这样可以确保我们设计出来的数据库，在大数据环境下，也能高效运行。

**水平分库的意思与水平分表类似，就是按照一定的规则，<strong><strong>把数据库中的数据**</strong>拆分出去，保存在新的数据库当中</strong>。

新的数据库可以在相同的服务器上，也可以在不同的服务器上。比如，我们可以把组号大于500、小于1000的用户数据拆分出来，保存到新的服务器的数据库中。不过，保存到新的服务器，也就意味着增加系统的开销。

因此，我们可以以500个商户为单位，每500个商户，在相同的服务器上创建一套新的数据库；每5000个商户，购置新的服务器。这样，我们就完成了对数据库进行分库的设计。

## 总结

这节课，我们一起设计了一个基于云服务的连锁超市管理系统数据库。你要重点掌握如何进行需求分析、如何把分析的结果转换成数据库的设计，以及如何在总体设计的阶段通过使用分库分表使设计出来的数据库能够处理大量数据。

在实际项目中，你要重点关注3个方面。

- 第一，要充分理解项目需求。有的时候，客户自己也不清楚自己的需求。这个时候，就需要你帮助客户理清思路。你可以把客户的需求用图表等方式整理出来，再跟客户一起讨论。在这个阶段，投入较多的时间是值得的，如果等系统开发完成之后再改，成本就很高了。
- 第二，使用ER模型工具来整理思路，可以提高效率，提高设计的质量。
- 第三，要充分考虑到系统投入运行之后的承载能力。如果有可能处理大量数据，就需要考虑分库分表的策略。

最后，还有几点你需要注意下。

1. 分库分表的策略，需要在设计阶段完成。如果缺乏整体分库分表的策略而匆忙上线，遇到瓶颈时再解决，花费的成本要远远高于在设计阶段投入的时间成本。
1. 分库分表的策略，必然带来开发和运维方面成本的提升，因此，你需要在设计阶段就有一个整体规划。比如，在类的设计里面，用正则表达式计算访问的服务器、数据库和数据表的名称。
1. 分库分表一般适用于比较大的项目，如果你开发的应用数据量小，系统规模有限，团队成员不多，不如就用一个服务器、一个数据库。因为分库分表对数据量小的项目没有什么作用，却会大大提升开发的复杂度，增加开发、运维和项目管理的成本。所以，你要综合考虑利与弊。

## 思考题

假设我有一个数据库demo，当中有一个商品流水表（demo.trans），示例如下：

<img src="https://static001.geekbang.org/resource/image/1e/cc/1ecd89a9d71e7ayye43ac9e85b8e2bcc.jpg" alt="">

在设计阶段，如果预见到未来数据量会非常庞大，你会如何制定分库分表策略？

欢迎在留言区写下你的思考和答案，我们一起交流讨论。如果你觉得今天的内容对你有所帮助，也欢迎你分享给你的朋友或同事，我们下节课见。
