|
|
@ -117,8 +117,231 @@
|
|
|
|
- 这样一来,我们就避免了冗余数据,而且还能够满足业务的需求,这样的数据表设计,才是合格的设计。
|
|
|
|
- 这样一来,我们就避免了冗余数据,而且还能够满足业务的需求,这样的数据表设计,才是合格的设计。
|
|
|
|
- 一般来说,MySQL 的数据库设计满足第三范式,就足够了。不过,第三范式,并不是终极范式,还有 **BCNF 范式(也叫 BC 范式)、第四范式和第五范式**。
|
|
|
|
- 一般来说,MySQL 的数据库设计满足第三范式,就足够了。不过,第三范式,并不是终极范式,还有 **BCNF 范式(也叫 BC 范式)、第四范式和第五范式**。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 3.2 ER模型:如何理清数据库设计思路?
|
|
|
|
|
|
|
|
- 接上面对表按范式进行拆解
|
|
|
|
|
|
|
|
- 但是,当我们按照这样的方式拆分一连串数据表时,却发现越拆越多,而且支离破碎。事实上,**局部最优的表,不仅有可能存在进一步拆分的情况,还有可能会出现数据缺失**。
|
|
|
|
|
|
|
|
- 毕竟,数据库设计是牵一发而动全身的。那有没有什么办法提前看到数据库的全貌呢?
|
|
|
|
|
|
|
|
- ER 模型就是一个这样的工具。ER 模型也叫作实体关系模型,是用来描述现实生活中客观存在的事物、事物的属性,以及事物之间关系的一种数据模型。
|
|
|
|
|
|
|
|
- 在开发基于数据库的信息系统的**设计阶段**,通常使用 ER 模型**来描述信息需求和信息特性**,帮助我们理清业务逻辑,从而设计出优秀的数据库。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- ER 模型包括哪些要素?
|
|
|
|
|
|
|
|
- 在 ER 模型里面,有三个要素,分别是**实体、属性和关系**。
|
|
|
|
|
|
|
|
- 实体。在 ER 模型中,用**矩形来表示**。实体分为两类,分别是**强实体和弱实体**。强实体是指不依赖于其他实体的实体;弱实体是指对另一个实体有很强
|
|
|
|
|
|
|
|
的依赖关系的实体。
|
|
|
|
|
|
|
|
- 属性,则是指**实体的特性**。比如超市的地址、联系电话、员工数等。在 ER 模型中用**椭圆形来表示**。
|
|
|
|
|
|
|
|
- 关系,则是指**实体之间的联系**。比如超市把商品卖给顾客,就是一种超市与顾客之间的联系。在 ER 模型中用**菱形来表示**。
|
|
|
|
|
|
|
|
- 需要注意的是,有的时候,**实体和属性不容易区分**
|
|
|
|
|
|
|
|
- 该如何区分实体和属性呢?
|
|
|
|
|
|
|
|
- 一个原则:我们要从系统整体的角度出发去看,**可以独立存在的是实体,不可再分的是属性**。也就是说,属性不需要进一步描述,不能包含其他属性。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- 在 ER 模型的 3 个要素中,关系又可以分为 3 种类型,分别是 **1 对 1、1 对多和多对多**
|
|
|
|
|
|
|
|
- 1 对 1:**指实体之间的关系是一一对应的**,比如个人与身份证信息之间的关系就是 1 对1 的关系。一个人只能有一个身份证信息,一个身份证信息也只属于一个人。
|
|
|
|
|
|
|
|
- 1 对多:**指一边的实体通过关系,可以对应多个另外一边的实体**。相反,另外一边的实体通过这个关系,则只能对应唯一的一边的实体。比如超市与超市里的收款机之间的从
|
|
|
|
|
|
|
|
属关系,超市可以拥有多台收款机,但是每一条收款机只能从属于一个超市。
|
|
|
|
|
|
|
|
- 多对多:**指关系两边的实体都可以通过关系对应多个对方的实体**。比如在进货模块中,供货商与超市之间的关系就是多对多的关系,一个供货商可以给多个超市供货,一个超
|
|
|
|
|
|
|
|
市也可以从多个供货商那里采购商品。
|
|
|
|
|
|
|
|
- 超市业务创建 ER 模型
|
|
|
|
|
|
|
|
- ![超市业务创建ER模型](pic/超市业务创建ER模型.png)
|
|
|
|
|
|
|
|
- 在这个图中,供货商和超市之间的供货关系,两边的数字都不是 1,表示多对多的关系。
|
|
|
|
|
|
|
|
- 同样,超市和顾客之间的零售关系,也是多对多的关系。
|
|
|
|
|
|
|
|
- 这个 ER 模型,包括了 3 个实体之间的 2 种关系:
|
|
|
|
|
|
|
|
- 超市从供货商那里采购商品;
|
|
|
|
|
|
|
|
- 超市把商品卖给顾客。
|
|
|
|
|
|
|
|
- 有了这个 ER 模型,我们就可以从整体上理解超市的业务了。但是,这里没有包含属性,这样就无法体现实体和关系的具体特征。现在,我们需要把属性加上,用椭圆来表示,这样
|
|
|
|
|
|
|
|
我们得到的 ER 模型就更加完整了。
|
|
|
|
|
|
|
|
- ER 模型的细化
|
|
|
|
|
|
|
|
- 进货模块
|
|
|
|
|
|
|
|
- 实体及属性
|
|
|
|
|
|
|
|
- 供货商:名称、地址、电话、联系人。
|
|
|
|
|
|
|
|
- 商品:条码、名称、规格、单位、价格。
|
|
|
|
|
|
|
|
- 门店:编号、地址、电话、联系人。
|
|
|
|
|
|
|
|
- 仓库:编号、名称。
|
|
|
|
|
|
|
|
- 员工:工号、姓名、住址、电话、身份证号、职位。
|
|
|
|
|
|
|
|
- 实体关系
|
|
|
|
|
|
|
|
- 其中,供货商、商品和门店是强实体,因为它们不需要依赖其他任何实体。
|
|
|
|
|
|
|
|
- 而仓库和员工是弱实体,因为它们虽然都可以独立存在,但是它们都依赖门店这个实体,因此都是弱实体。
|
|
|
|
|
|
|
|
- ER 模型如下:
|
|
|
|
|
|
|
|
- ![ER模型](pic/ER模型.png)
|
|
|
|
|
|
|
|
- 这里我是用粗框矩形表示弱实体,用粗框菱形,表示弱实体与它依赖的强实体之间的关系。
|
|
|
|
|
|
|
|
- 零售模块
|
|
|
|
|
|
|
|
- 零售业务包括普通零售和会员零售两种模式。普通零售包含的实体,包括门店、商品和收银款台;会员零售包含的实体,包括门店、商品、会员和收银款台。
|
|
|
|
|
|
|
|
- 实体及属性
|
|
|
|
|
|
|
|
- 商品:条码、名称、规格、单位、价格。
|
|
|
|
|
|
|
|
- 会员:卡号、发卡门店、名称、电话、身份证、地址、积分、储值。
|
|
|
|
|
|
|
|
- 门店:编号、地址、电话、联系人。
|
|
|
|
|
|
|
|
- 收银款台:编号、名称。
|
|
|
|
|
|
|
|
- 实体关系
|
|
|
|
|
|
|
|
- 其中,商品和门店不依赖于任何其他实体,所以是强实体;
|
|
|
|
|
|
|
|
- 会员和收银款台都依赖于门店,所以是弱实体。
|
|
|
|
|
|
|
|
- 零售模块的 ER 模型了:
|
|
|
|
|
|
|
|
- ![零售ER模型](pic/零售ER模型.png)
|
|
|
|
|
|
|
|
- 完整的 ER 模型:
|
|
|
|
|
|
|
|
- ![完整ER模型](pic/完整ER模型.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- 如何把 ER 模型图转换成数据表?
|
|
|
|
|
|
|
|
- 通过绘制 ER 模型,我们已经理清了业务逻辑,现在,我们就要进行非常重要的一步了:把绘制好的 ER 模型,转换成具体的数据表。
|
|
|
|
|
|
|
|
- 我来介绍下转换的原则。
|
|
|
|
|
|
|
|
- 一个实体通常转换成一个数据表;
|
|
|
|
|
|
|
|
- 一个多对多的关系,通常也转换成一个数据表;
|
|
|
|
|
|
|
|
- 一个 1 对 1,或者 1 对多的关系,往往通过表的外键来表达,而不是设计一个新的数据表;
|
|
|
|
|
|
|
|
- 属性转换成表的字段。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 3.3 查询有点慢,语句该如何写?
|
|
|
|
|
|
|
|
- 查询分析语句
|
|
|
|
|
|
|
|
- 虽然 MySQL 的查询分析语句并不能直接优化查询,但是却可以帮助你了解 SQL 语句的执行计划,有助于你分析查询效率低下的原因,进而有针对性地进行优化。查询分析语句的
|
|
|
|
|
|
|
|
语法结构是:
|
|
|
|
|
|
|
|
- { EXPLAIN | DESCRIBE | DESC }查询语句;
|
|
|
|
|
|
|
|
```shell
|
|
|
|
|
|
|
|
mysql> SELECT itemnumber,quantity,price,transdate
|
|
|
|
|
|
|
|
-> FROM demo.trans
|
|
|
|
|
|
|
|
-> WHERE itemnumber=1
|
|
|
|
|
|
|
|
-> AND transdate>'2020-06-18 09:00:00'
|
|
|
|
|
|
|
|
-> AND transdate<'2020-06-18 12:00:00';
|
|
|
|
|
|
|
|
+------------+----------+-------+---------------------+
|
|
|
|
|
|
|
|
| itemnumber | quantity | price | transdate |
|
|
|
|
|
|
|
|
+------------+----------+-------+---------------------+
|
|
|
|
|
|
|
|
| 1 | 0.276 | 70.00 | 2020-06-18 11:04:00 |
|
|
|
|
|
|
|
|
| 1 | 1.404 | 70.00 | 2020-06-18 11:10:57 |
|
|
|
|
|
|
|
|
| 1 | 0.554 | 70.00 | 2020-06-18 11:18:12 |
|
|
|
|
|
|
|
|
| 1 | 0.431 | 70.00 | 2020-06-18 11:27:39 |
|
|
|
|
|
|
|
|
| 1 | 0.446 | 70.00 | 2020-06-18 11:42:08 |
|
|
|
|
|
|
|
|
| 1 | 0.510 | 70.00 | 2020-06-18 11:56:43 |
|
|
|
|
|
|
|
|
+------------+----------+-------+---------------------+
|
|
|
|
|
|
|
|
6 rows in set (6.54 sec)
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
- 结果显示,有 6 条记录符合条件。这个简单的查询一共花去了 6.54 秒,这个速度显然太慢了。
|
|
|
|
|
|
|
|
- 现在,我们用下面的语句分析一下这个查询的具体细节:
|
|
|
|
|
|
|
|
```shell
|
|
|
|
|
|
|
|
mysql> EXPLAIN SELECT itemnumber,quantity,price,transdate -- 分析查询执行情况
|
|
|
|
|
|
|
|
-> FROM demo.trans
|
|
|
|
|
|
|
|
-> WHERE itemnumber=1 -- 通过商品编号筛选
|
|
|
|
|
|
|
|
-> AND transdate>'2020-06-18 09:00:00' -- 通过交易时间筛选
|
|
|
|
|
|
|
|
-> AND transdate<'2020-06-18 12:00:00';
|
|
|
|
|
|
|
|
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
|
|
|
|
|
|
|
|
| id | select_type | table | partitions | type | possible_keys | key |key_len | ref | rows | filtered | Extra |
|
|
|
|
|
|
|
|
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
|
|
|
|
|
|
|
|
| 1 | SIMPLE | trans | NULL | ALL | NULL | NULL | NULL | NULL | 4157166 | 1.11 | Using where |
|
|
|
|
|
|
|
|
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
|
|
|
|
|
|
|
|
1 row in set, 1 warning (0.00 sec)
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
- id:是一个查询序列号。
|
|
|
|
|
|
|
|
- table:表示与查询结果相关的表的名称。
|
|
|
|
|
|
|
|
- partition:表示查询访问的分区。
|
|
|
|
|
|
|
|
- key:表示优化器最终决定使用的索引是什么。
|
|
|
|
|
|
|
|
- key_len:表示优化器选择的索引字段按字节计算的长度。如果没有使用索引,这个值就是空。
|
|
|
|
|
|
|
|
- ref:表示哪个字段或者常量被用来与索引字段比对,以读取表中的记录。如果这个值是“func”,就表示用函数的值与索引字段进行比对。
|
|
|
|
|
|
|
|
- rows:表示为了得到查询结果,必须扫描多少行记录。
|
|
|
|
|
|
|
|
- filtered:表示查询筛选出的记录占全部表记录数的百分比。
|
|
|
|
|
|
|
|
- possible_key:表示 MySQL 可以通过哪些索引找到查询的结果记录。如果这里的值是空,就说明没有合适的索引可用。你可以通过查看 WHERE 条件语句中使用的字段,来
|
|
|
|
|
|
|
|
决定是否可以通过创建索引提高查询的效率
|
|
|
|
|
|
|
|
- Extra:表示 MySQL 执行查询中的附加信息。你可以点击这个链接查询详细信息。
|
|
|
|
|
|
|
|
- type:表示表是如何连接的。至于具体的内容,你可以参考下查询分析语句输出内容说明。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- 除了刚刚这些字段,还有 1 个比较重要,那就是 select_type。
|
|
|
|
|
|
|
|
- SIMPLE:表示简单查询,不包含子查询和联合查询。
|
|
|
|
|
|
|
|
- PRIMARY:表示是最外层的查询。
|
|
|
|
|
|
|
|
- UNION:表示联合查询中的第二个或者之后的查询。
|
|
|
|
|
|
|
|
- DEPENDENTUNION:表示联合查询中的第二个或者之后的查询,而且这个查询受外查询的影响。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- 关于这个 DEPENDENTUNION 取值,
|
|
|
|
|
|
|
|
```shell
|
|
|
|
|
|
|
|
mysql> SELECT *
|
|
|
|
|
|
|
|
-> FROM demo.goodsmaster a
|
|
|
|
|
|
|
|
-> WHERE itemnumber in
|
|
|
|
|
|
|
|
-> (
|
|
|
|
|
|
|
|
-> SELECTb.itemnumber
|
|
|
|
|
|
|
|
-> FROM demo.goodsmaster b
|
|
|
|
|
|
|
|
-> WHERE b.goodsname = '书'
|
|
|
|
|
|
|
|
-> UNION
|
|
|
|
|
|
|
|
-> SELECTc.itemnumber
|
|
|
|
|
|
|
|
-> FROM demo.goodsmaster c
|
|
|
|
|
|
|
|
-> WHERE c.goodsname = '笔'
|
|
|
|
|
|
|
|
-> );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 rows in set (0.00 sec)
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
- MySQL 在执行的时候,会把这个语句进行优化,重新写成下面的语句:
|
|
|
|
|
|
|
|
```sql
|
|
|
|
|
|
|
|
SELECT *
|
|
|
|
|
|
|
|
FROM demo.goodsmaster a
|
|
|
|
|
|
|
|
WHERE EXISTS
|
|
|
|
|
|
|
|
(
|
|
|
|
|
|
|
|
SELECT b.id
|
|
|
|
|
|
|
|
FROM demo.goodsmaster b
|
|
|
|
|
|
|
|
WHERE b.goodsname = '书' ANDa.itemnumber=b.itemnumber
|
|
|
|
|
|
|
|
UNION
|
|
|
|
|
|
|
|
SELECT c.id
|
|
|
|
|
|
|
|
FROM demo.goodsmaster c
|
|
|
|
|
|
|
|
WHERE c.goodsname = '笔' AND a.itemnumber=c.itemnumber
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
- 在这里,子查询中的联合查询是:
|
|
|
|
|
|
|
|
```sql
|
|
|
|
|
|
|
|
SELECT c.id
|
|
|
|
|
|
|
|
FROM demo.goodsmaster c
|
|
|
|
|
|
|
|
WHERE c.goodsname = '笔' AND a.itemnumber=c.itemnumber
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
- 这个查询就用到了与外部查询相关的条件 a.itemnumber=c.itemnumber,因此,查询类别就变成了“UNION DEPENDENT”。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- 分析一下刚刚的查询语句。
|
|
|
|
|
|
|
|
- 这个查询是一个简单查询,涉及的表是 demo.trans,没有分区,连接类型是扫描全表,没有索引,一共要扫描的记录数是 4157166。因此,查询速度慢的主要原因是没
|
|
|
|
|
|
|
|
有索引,导致必须要对全表进行扫描才能完成查询。所以,针对这个问题,可以通过创建索引的办法,来提高查询的速度。
|
|
|
|
|
|
|
|
- 下面,我们用条件语句中的筛选字段 itemnumber 和 transdate 分别创建索引:
|
|
|
|
|
|
|
|
```shell
|
|
|
|
|
|
|
|
mysql> CREATE INDEX itemnumber_trans ON demo.trans(itemnumber);
|
|
|
|
|
|
|
|
Query OK, 0 rows affected (59.86 sec)
|
|
|
|
|
|
|
|
Records: 0 Duplicates: 0 Warnings: 0
|
|
|
|
|
|
|
|
mysql> CREATE INDEX transdate_trans ON demo.trans(transdate);
|
|
|
|
|
|
|
|
Query OK, 0 rows affected (56.75 sec)
|
|
|
|
|
|
|
|
Records: 0 Duplicates: 0 Warnings: 0
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- 2 种查询优化的方法
|
|
|
|
|
|
|
|
- 怎么在包含关键字“LIKE”和“OR”的条件语句中,利用索引提高查询效率。
|
|
|
|
|
|
|
|
- 使用关键字“LIKE”
|
|
|
|
|
|
|
|
- “LIKE”经常被用在查询的限定条件中,通过通配符“%”来筛选符合条件的记录。比如
|
|
|
|
|
|
|
|
- WHERE字段 LIKE ‘aa%’,表示筛选出所有以“aa”开始的记录;
|
|
|
|
|
|
|
|
- WHERE字段 LIKE ‘%aa%’,表示所有字段中包含“aa”的记录。
|
|
|
|
|
|
|
|
- 这里你要注意的是,**通配符在前面的筛选条件是不能用索引的**。也就是说,WHERE字段LIKE‘%aa’和WHERE字段 LIKE ‘%aa%’都不能使用索引,但是通配符在后面的筛选条
|
|
|
|
|
|
|
|
件,就可以使用索引。
|
|
|
|
|
|
|
|
- 使用关键字“OR”
|
|
|
|
|
|
|
|
- 关键字“OR”表示“或”的关系,“WHERE 表达式 1 OR 表达式 2”,就表示表达式 1 或者表达式 2 中只要有一个成立,整个 WHERE 条件就是成立的。
|
|
|
|
|
|
|
|
- 需要注意的是,**只有当条件语句中只有关键字“OR”,并且“OR”前后的表达式中的字段都建有索引的时候,查询才能用到索引**。
|
|
|
|
|
|
|
|
- 我刚才已经用字段条码给商品流水表创建了一个索引,现在我再用商品编号“itemnumber”创建一个索引:
|
|
|
|
|
|
|
|
```shell
|
|
|
|
|
|
|
|
mysql> CREATE INDEX trans_itemnumber ON demo.trans(itemnumber);
|
|
|
|
|
|
|
|
Query OK, 0 rows affected (20.24 sec)
|
|
|
|
|
|
|
|
Records: 0 Duplicates: 0 Warnings: 0
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
- 我们先看一下关键字“OR”前后的表达式中的字段都创建了索引的情况:
|
|
|
|
|
|
|
|
```shell
|
|
|
|
|
|
|
|
mysql> EXPLAIN SELECT * FROM demo.trans
|
|
|
|
|
|
|
|
-> WHERE barcode LIKE '6953150%'
|
|
|
|
|
|
|
|
-> OR itemnumber = 1;
|
|
|
|
|
|
|
|
+----+-------------+-------+------------+-------------+-----------------------
|
|
|
|
|
|
|
|
| id | select_type | table | partitions | type | possible_keys | key | key_len
|
|
|
|
|
|
|
|
+----+-------------+-------+------------+-------------+-----------------------
|
|
|
|
|
|
|
|
| 1 | SIMPLE | trans | NULL | index_merge | trans_barcode,trans_itemnumber | t
|
|
|
|
|
|
|
|
+----+-------------+-------+------------+-------------+-----------------------
|
|
|
|
|
|
|
|
1 row in set, 1 warning (0.01 sec)
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
- 我们先看一下关键字“OR”前后的表达式中的字段都创建了索引的情况:
|
|
|
|
|
|
|
|
```shell
|
|
|
|
|
|
|
|
mysql> EXPLAIN SELECT * FROM demo.trans
|
|
|
|
|
|
|
|
-> WHERE barcode LIKE '6953150%'
|
|
|
|
|
|
|
|
-> OR itemnumber = 1;
|
|
|
|
|
|
|
|
+----+-------------+-------+------------+-------------+-----------------------
|
|
|
|
|
|
|
|
| id | select_type | table | partitions | type | possible_keys | key | key_len
|
|
|
|
|
|
|
|
+----+-------------+-------+------------+-------------+-----------------------
|
|
|
|
|
|
|
|
| 1 | SIMPLE | trans | NULL | index_merge | trans_barcode,trans_itemnumber | t
|
|
|
|
|
|
|
|
+----+-------------+-------+------------+-------------+-----------------------
|
|
|
|
|
|
|
|
1 row in set, 1 warning (0.01 sec)
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
- 说明优化器选择了合并索引的方式。因此,这个关键字“OR”前后的表达式中的字段都创建了索引的查询,是可以用到索引的。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 3.4 表太大了,如何设计才能提高性能?
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|