一. 子查询(Sub Query)
1.1 什么是子查询
- 子查询概念
一条select查询语句的结果, 作为另一条select语句的一部分
2) 子查询的特点
子查询一般作为父查询的查询条件使用
子查询必须放在小括号中使用
- SQL语句含有多个select,先执行子查询,再执行外查询
1.2单行子查询
单行子查询: 查询出的结果为一列一行(一个数据) 如: 最高, 最低, 平均等, 可以使用判断符号 如: > | < | = | != 等
语法格式
select 字段 from 表 where 字段 判断符号 (子查询) |
---|
需求:
#1 查询价格最高的商品信息 #2 查询化妆品分类下的 商品名称 商品价格 #3 查询小于平均价格的商品信息 |
---|
SQL实现
#1 查询价格最高的商品信息 — 1.查询出商品最高的价格 select max(price) from products; — 5000 select from products where price = 5000; — 2.将查询出的最高价格作为条件查询, 获取商品信息 select from products where price = (select max(price) from products); #2 查询化妆品分类下的 商品名称 商品价格 — 1. 查询化妆品分类的cid select cid from category where cname = ‘化妆品’; —c003 select pname, price from products where cid = ‘c003’; — 2. 将查询出的化妆品分类cid作为条件, 查询 商品名称 商品价格 select pname, price from products where cid = (select cid from category where cname = ‘化妆品’); #3 查询小于平均价格的商品信息 — 1.查询出商品平均价格 select avg(price) from products; — 1866.6667 — 2.将查询出的商品平均价格作为条件, 查询商品信息 select * from products where price < (select avg(price) from products); |
---|
1.3 多行子查询
多行子查询: 查询出的结果为一列多行(多个数据) 如: 化妆品类别下的商品cid, , 可以使用判断符号 如: in all any
In : 等于任意一个
使用方式:
in(值1, 值2 …)
all: 所有
使用方式:
如: 字段 > |
使用方式
如: 字段 > | < any(值1, 值2 …) 大于任意一个值
如: 字段 = any(值1, 值2 …) 等于任意一个值 效果等同于 in
语法格式
select 字段 from 表 where 字段 判断符号(in | any | all) (子查询) |
---|
需求:
#1 查询化妆品类别中的商品价格和鞋服类别中的商品价格一样的商品信息 #2 查询价格比所有鞋服类别中商品的价格都高的商品信息 #3 查询价格比任意一个鞋服类别中商品的价格高的商品信息 |
---|
SQL实现
#1 查询化妆品类别中的商品价格和鞋服类别中的商品价格一样的商品信息 — 1.查询化妆品类别下所有商品的价格 select price from products where cid = ‘c003’; — 800 200 — 2.查询鞋服类别下所有商品的价格 select price from products where cid = ‘c002’; — 800 200 300 2000 — 3.化妆品中商品的价格和鞋服中商品的价格进行条件判断 select from products where cid = ‘c003’ and price in (select price from products where cid = ‘c002’); #2 查询价格比所有鞋服类别中商品的价格都高的商品信息 — 1.查询出所有鞋服类别中商品的价格 select price from products where cid = ‘c002’; — 800 200 300 2000 — 2.鞋服类别中商品的价格作为判断条件 select from products where price > all(select price from products where cid = ‘c002’); #3 查询价格比任意一个鞋服类别中商品的价格高的商品信息 — 1.查询出所有鞋服类别中商品的价格 select price from products where cid = ‘c002’; — 800 200 300 2000 — 2.鞋服类别中商品的价格作为判断条件 select * from products where price > any(select price from products where cid = ‘c002’); |
---|
注意事项:
单行子查询: 查询出的结果为一列一行(一个数据), 可以使用 > < = !=
多行子查询: 查询出的结果为一列多行(多个数据), 需要使用in all any
二. 数据库事务控制(TCL)
2.1 什么是事务
常用的存储引擎有InnoDB(MySQL5.5以后默认的存储引擎)和MyISAM(MySQL5.5之前默认的存储引擎),其中InnoDB支持事务处理机制,而MyISAM不支持
事务是一个整体, 由一条或者多条SQL语句组成, 这些SQL语句要么都执行成功, 要么就失败, 只要有一条SQL出现异常, 整个操作就会回滚
回滚: 就是事务运行的过程中发生了某种故障, 或者SQL出现了异常, 事务不能继续执行, 系统将事务中对数据库的所有已完成的操作全部取消, 回滚到事务开始时的状态
比如: 银行的转账业务,张三给李四转账500元 , 至少要操作两次数据库, 张三 -500, 李四 + 500,这中 间任何一步出现问题,整个操作就必须全部回滚, 这样才能保证用户和银行都没有损失. |
---|
2.2 模拟转账操作
- 创建数据库表
| — 创建账户表
create table account(
id int primary key auto_increment, — 主键
name varchar(10), — 姓名
money double — 余额
);
— 添加两个用户
insert into account (name, money) values (‘tom’, 1000), (‘jack’, 1000); | | —- |
2) 模拟tom给jack转钱500, 完成转账的操作至少需要以下两步
— tom账户 -500 update account set money = money-500 where name = ‘tom’; — jack账户 +500 update account set money = money+500 where name = ‘jack’; |
---|
注意事项:
·假设当tom账户 -500 元, 服务器崩溃了。jack 的账户并没有+500 元,数 据就出现问题了
·我们要保证整个事务执行的完整性,要么都成功, 要么都失败. 这个时候我们就要 学习如何操作事务
2.3 MySQL事务操作
MySQL 中可以有两种方式进行事务的操作:
1) 自动提交事务(MySQL默认)
2) 手动提交事务
2.3.1 手动提交事务
语法格式
1) START TRANSACTION 这个语句显式地标记一个事务的起始点。
2) COMMIT 表示提交事务,即提交事务的所有操作,具体地说,就是将事务中所 有对数据库的更新都写到磁盘上的物理数据库中,事务正常结束。
3) ROLLBACK 表示撤销事务,即在事务运行的过程中发生了某种故障,事务不 能继续执行,系统将事务中对数据库的所有已完成的操作全部撤销,回滚到事务开始时的状态
2.3.2 手动提交事务的流程
- 执行成功的情况: 开启事务 -> 执行多条 SQL 语句 -> 成功提交事务执行
2) 失败的情况: 开启事务 -> 执行多条 SQL 语句 -> 事务的回滚
2.3.3 手动控制事务的转账案例
手动开启事务 start transaction
·手动开启事务后, 事务不再是自动提交
提交事务 commit
·没有出现任何问题, 提交事务 数据就会持久化到本地磁盘
回滚事务 rollback
· 出现问题回滚事务, 回滚事务要在提交事务之前操作,一旦事务完成了提交, 就不能 再回滚. 如出现宕机等情况事务会自动回滚
— 开启事务 start transaction; — tom账户 -500 update account set money = money-500 where name = ‘tom’; — jack账户 +500 update account set money = money+500 where name = ‘jack’; — 提交事务(没发生任何问题提交) commit; — 回滚事务(出现问题回滚, 如果出现宕机等原因事务会自动回滚) rollback; |
---|
2.3.4 事务的四大特性 ACID
原子性(Atomicity)
一个事务要么全部提交成功,要么全部失败回滚,不能只执行其中的一部分操作,这就是事务的原子性
一致性(Consistency)
指事物必须是数据库从一个一致性状态到另一个一致性状态。也就是说一个事物执行之前和执行之后都必须处于一致性状态
隔离性(Isolation)
事务的隔离性是指在并发环境中,并发的事务时相互隔离的,一个事务的执行不能不被其他事务干扰。不同的事务并发操作相同的数据时,每个事务都有各自完成的数据空间,即一个事务内部的操作及使用的数据对其他并发事务时隔离的,并发执行的各个事务之间不能相互干扰
持久性(Durability) 一旦事务提交,那么它对数据库中的对应数据的状态的变更就会永久保存到数据库中。即使发生系统崩溃或机器宕机等故障,只要数据库能够重新启动,那么一定能够将其恢复到事务成功结束的状态
2.4 MySQL事务的隔离级别
2.4.1数据并发访问
一个数据库可能拥有多个访问客户端, 这些客户端都可以并发方式访问数据库. 数据库的相同数据可能被多个事务同时访问, 如果不采取隔离措施, 就会导致各种问题, 破坏数据的完整性
2.4.2 并发访问产生的问题
事务在操作时的理想状态:所有的事务之间保持隔离,互不影响。因为并发操作,多个用户同时访问同一个数据, 可能引发并发访问的问题
2.4.3 四种隔离级别
通过设置隔离级别,可以防止上面的三种并发问题. MySQL数据库有四种隔离级别上面的级别最低,下面的级别最高
✔ 会出现问题
✘ 不会出现问题
注意事项:
隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大
2.5.4 隔离级别相关命令
- 查看隔离级别
select @@transaction_isolation;
2) 设置隔离级别
— 设置隔离级别语法格式 set session transaction isolation level 隔离级别名称; — 如: 设置为读未提交 set session transaction isolation level read uncommitted; read uncommitted 读未提交 read committed 读已提交 repeatable read 可重复读 serializable 串行化 |
---|
2.5 脏读、不可重复度、幻读示例
2.5.1 事务A
-- 查看mysql的事务隔离级别 默认 rr REPEATABLE-READ
#-- 读未提交 脏读 不可重复读 幻读 ru
SET session TRANSACTION ISOLATION LEVEL read UNCOMMITTED;
SELECT @@transaction_isolation;
BEGIN
update account set money=money+100 where id = 1;
rollback;
# ------------------------------------------------
#-- 读已提交 解决脏读 不可解决不可重复读 幻读
SET session TRANSACTION ISOLATION LEVEL read COMMITTED;
START TRANSACTION
update account set money=money+100 where id = 1;
COMMIT
# ------------------------------------------------
-- 可重复读 解决脏读 不可重复 不解决幻读 rr
/*
MySQL8: MVCC机制(版本控制) 快照读
MySQL8可以解决读取的幻读情况
查询过程中, 涉及到部分数据的变更, 再次查询会出现幻读
*/
set session transaction isolation level repeatable read;
begin;
select * from account;
update account set money=money+100;
commit;
# ------------------------------------------------
# 串行化 解决脏读、不可重复读、幻读
set session transaction isolation level SERIALIZABLE;
begin;
select * from account;
commit
2.5.2 事务B
#-- 读未提交 脏读 不可重复读 幻读
SET session TRANSACTION ISOLATION LEVEL read UNCOMMITTED;
SELECT @@transaction_isolation;
BEGIN
select * from account;
commit;
# ------------------------------------------------
#-- 读已提交 解决脏读 不可解决不可重复读 幻读
SET session TRANSACTION ISOLATION LEVEL read COMMITTED;
START TRANSACTION
update account set money=money+1000 where id = 1;
select * from account;
ROLLBACK
# ------------------------------------------------
-- 可重复读 解决脏读 不可重复 不解决幻读
/*
MySQL8: MVCC机制(版本控制) 快照读
MySQL8可以解决读取的幻读情况
查询过程中, 涉及到部分数据的变更, 再次查询会出现幻读
*/
set session transaction isolation level repeatable read;
begin
-- update account set money=money+1000 where id = 1;
insert into account values(4,234);
commit;
# ------------------------------------------------
# 串行化 解决脏读、不可重复读、幻读
set session transaction isolation level SERIALIZABLE;
begin;
insert into account values(6,50);
commit;
三. 索引
3.1 什么是索引
对于任何的数据库管理系统,索引都是进行优化的最主要因素。对于少量的数据,即使没有合适的索引对数据库性能的影响并不是很大,但是随着数据量的增加,数据库性能会急剧下降, 所以索引目的在于提高检索数据的效率
索引就是用于快速找到记录的一种数据结构, 可以简单理解为:排好序的快速查找数据结构
3.2 索引的作用
MySQL的data(数据)是存放在磁盘, 读取数据时需要将磁盘中的数据读取到内存中,所以频繁的查询会导致频繁的磁盘IO, 所谓磁盘IO,简单来讲就是就是将磁盘中的数据读取到内存或者是从内存写入磁盘。在系统开发与设计过程中,磁盘IO的瓶颈往往不可忽略,因为这是一个相对比较耗时的操作
在数据库表中,对字段建立索引可以大大提高查询速度。通过善用这些索引,可以令MySQL的查询运行更加高效。 如果合理的设计且使用索引的MySQL是一辆兰博基尼的话,那么没有设计和使用索引的MySQL就是一个人力三轮车。 拿汉语字典的目录页(索引)打比方,我们可以按拼音、笔画、偏旁部首等排序的目录(索引)快速查找到需要的字
3.3 索引的底层实现
索引底层实现使用的就是数据结构, 将操作的索引放到数据结构中, 借助于数据结构实现快速的查询
3.3.1 Hash结构
Hash底层实现是由Hash表来实现的,是根据键值
缺点:
- 哈希表是把索引字段映射成对应的哈希码然后再存放在对应的位置,这样的话,如果我们要进行模糊查找的话,显然哈希表这种结构是不支持的,只能遍历这个表
3.3.2 Tree结构
3.3.3 BTree结构
BTree分为B-Tree, B+Tree, MySQL数据库索引采用的B+Tree, B+Tree是在B-Tree上做了优化改造
1) B-Tree
·索引值和data(数据)分布在整棵树结构中
·每个节点可以存放多个索引值以及对应的data(数据)·树节点中的多个索引值从左到右升序排列
缺点:
所有的节点都存放数据, 数据会占用空间, 导致存放的索引变少
2) B+Tree
·非叶子节点不存储data数据,只存储索引值,这样便于存储更多的索引值
·叶子节点包含了所有的索引值和data数据
·叶子节点用指针连接,提高区间的访问性能
相比B-树,B+树进行范围查找时,只需要查找定位两个节点的索引值,然后利用叶子节点的指针进行遍历即可。而B-树需要遍历范围内所有的节点和数据,显然B+Tree效率高
3.4 常见的索引的分类
3.4.1 主键索引
特点:
· 它是一种特殊的唯一索引,不允许有空值
· 在创建或修改表时添加主键约束即可,添加了主键约束就会自动创建主键索引
· 每个表只能有一个主键约束, 所以一张表只能有一个主键索引
创建主键索引的方式:
添加主键约束就会自动创建主键索引
#1 创建表时指定主键约束 create table 表名( 字段1 类型 primary key, … ); #2 为创建好并未指定主键约束的表添加主键约束 alter table 表名 add primary key(字段名); #3 查看某张表中的所有索引 show index from表名; |
---|
3.4.2 普通索引
特点:
这是最基本的索引类型,基于普通字段建立的索引,没有任何限制
创建索引的方式:
需要手动添加普通索引
#1 创建表时并创建普通索引 create table 表名( 字段1 类型, … , index [索引名称] (字段名) — 不指定索引名称, 自动生成 ); #2 为创建好的表添加普通索引 alter table 表名 add index [索引名称] (字段名); — 不指定索引名称, 自动生成 #3 为创建好的表添加普通索引 create index <索引名称> on 表名 (字段名); — 必须指定索引名称 |
---|
3.4.3 唯一索引
特点:
与”普通索引”类似,不同的就是:索引字段的值必须唯一,但允许有空值。在创建或修改表时追加唯一约束,就会自动创建对应的唯一索引
创建索引的方式:
1) 创建表时指定了唯一约束会自动创建唯一索引
2) 建表时未指定唯一约束, 需要手动添加唯一索引
#1 创建表时指定唯一约束, 会自动创建唯一索引 create table 表名( 字段1 类型 unique, … ); #2 为创建好的表添加唯一索引 alter table 表名 add unique index [索引名称] (字段名); #3 为创建好的表添加唯一索引 create unique index <索引名称> on 表名 (字段名); |
---|
3.4.4 全文索引
查询操作在数据量比较少时,可以使用like模糊查询,但是对于大量的文本数据检索,效率很低。如果使用全文索引,查询速度会比like快很多倍。从MySQL 5.6开始支持全文索引
创建索引的方式:
需要手动添加全文索引
#1 创建表时创建全文索引 create table 表名( 字段1 类型, … , fulltext key [索引名称] (字段名) — 字段只能为字符串或者大文本类型 ); #2 为创建好的表添加唯一索引 alter table 表名 add fulltext index [索引名称] (字段名); #3 为创建好的表添加唯一索引 create fulltext index <索引名称> on 表名 (字段名); |
---|
和常用的like模糊查询不同,全文索引有自己的语法格式,使用 match 和 against 关键字,比如
select * from user where match(name) against(‘hello’); |
---|
全文索引使用注意事项:
全文索引必须在字符串、文本字段上建立。 使用全文索引搜索(搜索的词)必须在最小字符和最大字符之间的才会有效。(innodb:3-84;myisam:4- 84) 全文索引字段值要进行切词处理,例如hello world,切分成hello和world全文索引匹配查询,默认使用的是等值匹配
使用中文进行切分,FULLTEXT(字段) with parser ngram,默认最少两个字的切分
3.5 索引优缺点的总结
·添加索引首先应考虑在 where 及 order by 涉及的列上建立索引
·索引的优点
- 大大的提高查询速度
- 可以显著的减少查询中分组和排序的时间
·索引的缺点
创建索引和维护索引需要时间,而且数据量越大时间越长
当对表中的数据进行增加,修改,删除的时候,索引也要同时进行维护,降低了数据的维护速度
四. 视图
4.1 什么是视图
视图是一种虚拟表
2. 视图建立在已有表的基础上, 视图建立依赖的这些表称为基表
3. 向视图提供数据内容的语句为 SELECT 语句, 可以将视图理解为存储起来的 SELECT 语句
4. 视图向用户提供基表数据的另一种表现形式4.2 视图的作用
简化复杂的多表查询
视图本身就是一条查询SQL, 我们可以将一次复杂的查询构建成一张视图, 用户只要查询视图就可以获取想要得到的信息(不需要再编写复杂的SQL), 可以理解为查询视图就相当于将创建视图的SQL再次执行一次
- 视图主要就是为了简化多表的查询
4.3 视图的使用
创建视图
语法格式
— 创建视图 create view 视图名称 as select语句; — view: 表示视图 — as: 表示视图要执行的操作 — select: 向视图提供数据内容 — 查询视图和查询表一样 select * from 视图名称; — 删除视图 drop view 视图名称; |
---|
创建一张视图(所有类别名称以及其对应的商品名称, 商品价格)
#1 显示所有类别名称以及其对应的商品名称, 商品价格 select from category c left join products p on c.cid = p.cid; #2 把上边的查询语句, 创建为一张试图 create view category_products_view as select c.cname, p.pname, p.price from category c left join products p on c.cid = p.cid; #3 查询视图(相当于执行 #1的操作) select from category_products_view; |
---|
需求: 将查询各个分类下的商品平均价格和类别名称, 创建为一张视图
SQL实现
create view cate_dep_view as select c.cname, avg(price) from category c left join products p on c.cid = p.cid group by c.cname; |
---|
4.4 视图与表的区别
·视图是建立在表的基础上,表存储数据库中的数据,而视图只是做一个数据的展示
·通过视图不能改变表中数据(一般情况下视图中的数据都是表中的列 经过计算得到的结果,不允许 更新)
·删除视图,表不受影响,而删除表,视图不再起作用
五. 数据控制语言(DCL)
5.1 用户管理
MySQL中可创建不同的用户,并分配不同的权限,保证MySQL中数据的安全性
MySQL用户主要包括两种:
1) root用户为超级管理员
拥有MySQL提供的所有权限
2) 普通用户:
权限取决于该用户在创建时被赋予的权限有哪些
用户表存在于mysql库中的user表中, 要先选择mysql库再进行操作
语法格式
— 查询所有用户 use mysql; select * from user; — 创建用户 create user ‘用户名’@’主机名’ identified by ‘密码’; — 主机名: localhost 只能本机连接 % 所有都可以连接 — 修改用户密码 ater user ‘用户名’@’主机名’ identified by ‘密码’; — 删除用户 drop user ‘用户名’@’主机名’; |
---|
需求1: 创建用户 zs1 111111 zs2 222222 zs3 333333 使用zs1用户登录MySQL
create user ‘zs1’@’localhost’ identified by ‘111111’; create user ‘zs2’@’localhost’ identified by ‘222222’; create user ‘zs3’@’%’ identified by 333333; |
---|
需求2: 将用户zs1密码改为123456, 删除用户zs3 并测试
— 修改用户 zs1 密码 alter user ‘zs1’@’localhost’ identified by ‘123456’; — 删除用户 zs3 drop user ‘zs3’@’%’; |
---|
5.2 权限管理
·MySQL通过权限管理机制可以给不同的用户授予不同的权限,从而确保数据库中数据的安全性
·只能给存在的用户授权
·新创建的用户只有登录的权限(USAGE)
语法格式
— 为某个用户授权 grant 权限1, 权限2 … on 数据库名.表名 to ‘用户名’@’主机名’; — 查看某个用户有哪些权限 show grants for ‘用户名’@’主机名’; — 撤销用户权限 revoke 权限1, … on 数据库名.表名 from ‘用户名’@’主机名’; — 刷新权限(添加, 撤销授权之后一定要刷新权限) ![]() |
---|
授权的操作
SQL实现
#1 给用户zs1授予对bjsxt02 中的products表查询的权限 grant select on bjsxt02.products to ‘zs1’@’localhost’; #2 给用户zs2 授予所有库所有表的所有权限 grant all on . to ‘zs2’@’localhost’; #3刷新权限(授权操作之后一定要刷新权限) flush privileges; #4查看用户有哪些权限 show grants for ‘zs1’@’localhost’; show grants for ‘zs2’@’localhost’; |
---|
5.3角色管理
MySQL 数据库中通常都会出现多个拥有相同权限集合的用户,在之前版本中只有分别向用户授予权限和撤销权限. 在用户数量比较多的时候,这样的操作是非常耗时的
MySQL 8.0 为了用户权限管理更容易,提供了一个角色管理的新功能。角色是指定的权限集合,和用户帐户一样可以对角色进行权限的授予和撤销。
如果用户被授予角色,则该用户就拥有了该角色的权限
语法格式
— 创建角色 create role ‘角色名称’, …; — 为角色授权 grant 权限1, 权限2, … on 数据库名.表名 to ‘角色名称’; — 为用户分配角色 grant ‘角色名称’ to ‘用户名’@’主机名’; — 撤销角色的权限 revoke 权限1, … on 数据库名.表名 from ‘角色名称’; |
---|
授权的操作
SQL实现
#1 创建两个角色 create role ‘role1’, ‘role2’; — 角色名字自定义 #2 为这角色role1 role2授权 grant select on bjsxt02.products to ‘role1’; — bjsxt02库products表 查询权限 grant all on . to ‘role2’; — 所有库所有表全部权限 #3 查看角色的权限 show grants for ‘role1’; show grants for ‘role2’; #4 创建两个用户 ls1 ls2 create user ‘ls1’@’localhost’ identified by ‘111111’; create user ‘ls2’@’localhost’ identified by ‘222222’; #5 用户分配角色 grant ‘role1’ to ‘ls1’@’localhost’; grant ‘role2’ to ‘ls2’@’localhost’; #6 查看用户的角色 show grants for ‘ls1’@’localhost’; — 拥有了该角色, 变用于了该角色的权限 show grants for ‘ls2’@’localhost’; #7 使角色在账号登陆后自动被激活 set global activate_all_roles_on_login=ON; |
---|
5.4 开启远程连接
# 创建用户 create user ‘cy’@’%’ identified by ‘123456’; #授权 grant all on . to ‘cy’@’%’; grant select on . to ‘cy’@’%’; #刷新权限 flush privileges; |
---|
六. 数据库设计
6.1 数据库三范式(空间最省)
·概念: 三范式就是设计数据库的规则
- 为了建立冗余较小、结构合理的数据库,设计数据库时必须遵循一定的规则。在关系型数据 库中这种规则就称为范式。范式是符合某一种设计要求的总结。要想设计一个结构合理的关系型数据库,必须满足一定的范式
- 满足最低要求的范式是第一范式(1NF)。在第一范式的基础上进一步满足更多规范要求的 称为第二范式(2NF) , 其余范式以此类推。一般说来,数据库只需满足第三范式(3NF)就 行了
6.2 第一范式 1NF
·概念: 原子性, 做到列不可拆分
1) 第一范式是最基本的范式。数据库表里面字段都是单一属性的,不可再分, 如果数据表中每个字段都是不可再分的最小数据单元,则满足第一范式
·示例: 地址信息表中, contry这一列,还可以继续拆分,不符合第一范式
6.3 第二范式 2NF
·概念:
1) 在第一范式的基础上更进一步,目标是确保表中的每列都和主键相关
2) 一张表只能描述一件事
·示例: 学员信息表中其实在描述两个事物 , 一个是学员的信息,一个是课程信息 如果放在一张表中,会导致数据的冗余,如果删除学员信息, 成绩的信息也被删除了
6.4 第三范式 3NF
·概念:
消除传递依赖
表的信息,如果能够被推导出来,就不应该单独的设计一个字段来存放
·示例:通过number 与 price字段就可以计算出总金额,不要在表中再做记录(空间最省)