MySQL基础(四)
目录
一. 数据库的“约束”
1.1 NOT NULL
1.2 UNIQUE
1.3 DEFAULT
1.4 PRIMARY KEY (主键)
1.5 FOREIGN KEY(外键)
注意事项1: 父表 "制约" 子表
注意事项2: 子表 "制约" 父表
注意事项3: 存在主键/unique约束
注意事项4: 逻辑删除
1.6 CHECK (了解)
二. 表的设计
2.1 明确实体
2.2 明确实体间的关系
1) 一对一关系
2) 一对多关系
3) 多对多关系
一. 数据库的“约束”
数据库中的数据是如此重要,我们希望数据库存储的数据,不要存在“错误”数据,因此要做出一些 “限制” “要求”,对数据库中的数据进行校验检查。
数据库的约束 是数据库管理系统(DBMS)用来保证数据库数据完整性和正确性的一套规则。它们确保了插入、更新或删除数据库中的数据时,数据满足特定的业务规则或条件。
约束类型
1.1 NOT NULL
指示某列不能存储 NULL 值
1.2 UNIQUE
保证某列的每行必须有唯一的值
* unique 如何知道数据存在重复呢? —— 插入 / 修改 之前先进行查询
duplicate --> 重复的 entry --> 条目 ( 与Map遍历的 entrySet 中的 entry 一个意思)
1.3 DEFAULT
规定没有给列赋值时的默认值
1.4 PRIMARY KEY (主键)
NOT NULL 和 UNIQUE 的结合。确保某列(或两个列多个列的结合)有唯一标识( “身份标识”),有助于更容易更快速地找到表中的一个特定的记录。(主键 同样也会在修改 / 插入的时候触发查询)
一个表中 不允许同时存在多个主键(多个“身份标识”)假设有多个主键,主键1相同但主键2不相同,就不是唯一的“身份标识”了。 但数据库允许把多个列共同作为一个主键(“联合主键”),暂时不过多讨论。
* 主键需要分配一个唯一的值,如何进行分配呢?—— MySQL 提供了 自动分配主键的方式 “自增主键” auto_increment 插入数据时,可以不指定 id 列的值,让数据库自动分配,就会按照1,2,3,4 .....依次累加。同时也可以手动指定主键数值,与自增主键并不冲突。自增主键只能针对像 int / bigint 的整数。
大部分情况下都是用数字作为主键,但也有可能使用字符串作为主键,比如 数据量特别大,不是一个 mysql 数据库,而是多个 mysql 构成的 分布式“集群”。
像大公司,每天处理的数据量很大,一台机器是无法存储完成的(即使我们使用的机器配置很高,可以加很多个硬盘),此时就可以引入多个机器,每个机器上只存储一部分数据。
应用程序插入数据的时候,有一个计算规则,来对数据进行区分,判断这个数据是存储在哪个机器上。虽然在多个数据服务器上存储数据,但仍然希望这些数据的 id 这一列,是唯一的,此时 mysql 自增主键就难以胜任了(自增主键 只能在一个机器上的 mysql 生效,不能跨主机生效)
此时,就会把 id 指定成字符串类型,然后通过应用程序里面的一系列算法,生成一个 “唯一的字符串 id” (生成方式有很多,比如 uuid/雪花算法....)
“分布式系统唯一id生成算法” —— 引入各种信息,综合这些信息,最终得到一个唯一的值(这些信息包括不限于时间戳,主机的机房编号,主机的编号,随机因子....)
当删除插入的数据,再次插入新的数据,默认分配的主键会从曾经指定过的最大数值开始自增 (在 MySQL 中,给每个表都记录了一个 “自增主键的最大值”,后续想插入数据,无论之前的最大值是否存在,都是根据之前保存的最大值,继续往后分配的)
1.5 FOREIGN KEY(外键)
两个表之间的关联关系,保证一个表中的数据匹配另一个表中的数据。(例如,有 商品表( 商品id, 商品名字....) 和 订单表( 订单id, 商品id...) , 订单表中的商品 id 必须在商品表中的商品 id 存在 )
注意事项1: 父表 "制约" 子表
学生表的 classId 列 引用自 班级表的 classId 列,即 班级表(制约的一方)中的数据,约束/制约 了学生表(被制约的一方)的数据,称 制约的一方 为父表,被制约的一方 为子表。再次往 student 中插入数据,就务必要确保插入的数据的 classld 值,得在 class 表中存在。
mysql 是如何 判断插入的 classId 数据是否在 class 表中的 classId 中呢?—— 往 student 插入的时候,会触发针对 class 表的查询。
注意事项2: 子表 "制约" 父表
父表是约束了子表,同时 子表 也会对父表 反向进行约束,必须保证 子表与父表 的关联关系始终存在。如果父表中的某个记录被子表引用了,就不可以随便删除父表中对应的记录。
删表时,要确保先删除子表,再删除父表,确保依赖顺序正确。
注意事项3: 存在主键/unique约束
指定外键的时候,后续往子表中插入数据,就要查询父表。因此,插入成功 是建立在 查询成功 的基础之上,为了加快查询速度,就要对查询的列指定 主键。
主键本身也是有 “唯一性要求” 的,频繁触发查询,数据库就会对主键约束,给出特殊的处理(数据库会针对 主键 列,创建索引)加快了查询速度。
如果不指定主键,是一个 unique 也能达成类似的效果。因为 unique 与 主键都是要自动建立索引,以便于后续的快速查询。
注意事项4: 逻辑删除
考虑以下场景:
商品表(goodId, goodName, price....)
订单表(orderId, goodId....)
如果过了一段时间,店铺不卖铅笔,改卖圆珠笔了,把铅笔下架了~~
下架操作该如何完成呢??
- 直接删除,不行的(因为 order 子表中存在 good 中的值,无法直接删除父表good的值)
- 如果不用外键,就可以删除了;但针对其他插入的数据,缺少了校验过程,不合适
此处真正的解决方案——逻辑删除!!
在商品表中添加额外的一列
商品表(goodId, goodName, price,isOk int....):
- isOk 设为 0 表示无效商品
- isOk 设为 1 表示有效商品
后续如果查询商品列表 --> select * from good where isOk = 1; (添加查询条件,确保用户看到的商品都是在线的商品)
同时,用户也可以看到之前的订单信息
和顺序表尾删是类似的效果,尾删直接 size--
采取逻辑删除,硬盘空间并没用真正释放,随着数据越来越多,硬盘是不是就被用完了?—— 如果用完了,就继续加硬盘,如果加了硬盘还不够,加机器就好。因为硬盘成本低,如果因为为了节省硬盘空间,写了一堆复杂代码,出了问题,造成的损失比硬盘成本要大得多!!
逻辑删除 在很多地方都会涉及到,比如 电脑删除文件 本身就是一个逻辑删除。执行这个删除,对应文件中的内容其实在硬盘上仍然存在,只不过把对应的硬盘空间,标记成“未使用”,直到后续有其他文件 写入硬盘的时候,恰好用到了这块空间,才会把这里的数据覆盖掉。
彻底删除文件 --> "物理删除",销毁硬盘。
1.6 CHECK (了解)
可以指定一个详细的条件, 保证列中的值符合指定的条件。( 例如 有一列 "性别" varchar(1), 此时要求填入的性别只能是 '男' 或者 '女' , check 可以写一个条件( name = '男' or name = '女'), 此时进行插入 / 修改操作, 就能够带入到上述条件中, 检查是否成立 )
MySQL数据库不支持check,检查条件不会生效,但是写了 check 不会报错;Oracle 对于 check 有很好的支持。实际开发中,如果也想进行类似的校验,MySQL 只能通过搭配 C++ / Java 这样的代码,在插入数据之前,先进行判断。
* 每次修改一个约束,都需要重新创建表吗? alter table .... 可以修改表的结构,但是不建 议使用 alter 。表规模比较小就好办,直接 alter 就行 ;表规模比较大 (百万级别),alter 操作很容易把数据库搞挂了的~~
在数据库管理中,arter 语句是用来修改数据库中已存在表(table)的结构的一个SQL命令。它可以用来添加、删除或修改表中的列(字段),也可以用来添加或删除各种约束,如主键、外键、唯一性约束等。
虽然一般是尽可能在最初的时候,就把表结构设计好,但也无法避免可能需要调整。对于服务器系统来说,稳定性可用性是非常重要的,对数据库进行修改表结构操作,就相当于给一个正在飞的飞机 更换发动机一样。一般会使用 “偷梁换柱” 的方式来完成:
- 申请一个新的机器,搭建好数据库环境,把数据库里面的表结构都创建好(已经添加好新的列)
- 把旧的数据库中的数据,导入到新的机器中 (导入的过程中注意,对于旧的数据库,不要读的太快,千万不要把旧数据搞挂)
- 当所有数据同步完毕之后,就把应用程序中,访问旧数据库的代码,切换成访问新数据库的代码
二. 表的设计
表的设计,比较抽象,依赖一定的经验。如何创建表,要根据实际需求场景,梳理清楚应该创建几个表,每个表有哪些列,以及多个表之间存在的关系。 在实际项目的环节,会存在更多的实体,更多的表,以及更复杂的关系....
2.1 明确实体
基本切入点:梳理清楚需求中的 “实体”(对象) 和 “关系”。
如何“找对象”?
- 分析需求场景,提取出一些 “关键的名词”
- 确定对象的属性和方法:对象有哪些信息需要保存,以及需要针对这些信息进行哪些 “加工操作”
找对象的过程,本身也就是把现实中的东西,使用代码的方式 “抽象” 出来。现实中的东西,通常是非常“具体”的,涉及到的信息量非常多,但编程写代码解决问题的时候,不需要把所有的信息都使用到,只需要关注用到的信息即可(提取关键信息)。信息量越少,就越抽象;信息量越多,就越具体。
确定 “实体” --> 找到关键名词,提取出一些需要用到的信息。有几个实体,一般就会有几个表,每个实体使用一个表来表示(也不绝对)。梳理实体之间的关系,根据这里关系需要对于表做出一些特殊的设定。
2.2 明确实体间的关系
1) 一对一关系
- 一个学生,只能有一个账号
- 一个账号,只能被一个学生持有
设计表的做法:
- 一个表 同时包含学生和账号的信息:student_account(studentld, name, accountName, password....)
- 两个表 使用 id 来进行联系:
student(studentld, name, accountld) account(accountld, accountName, password)
或者
student(studentld, name) account(accountld, accountName, password, studentld)
具体如何设计表,要根据实际开发的需要,怎么方便简单怎么来。
2) 一对多关系
- 一个学生只能属于一个班级
- 一个班级可以包含多个学生
3) 多对多关系
- 一个学生,可以选择多门课程学习
- 一门课程,也可以被多个学生选择
引入关联表,通过关联表把多对多关系表示出来