登录
1 | mysql -u root -p |
基本操作命令
1 |
|
建表约束
主键约束
作为主键的字段,其值不能重复,不能为空。
声明某个属性作为主键,常用有以下方法:
1 | #方法1 |
1 | #方法2 |
如果一开始并没有定义主键,也可以之后再添加(两种方法)
1 | #一开始并没有声明主键 |
当然也可以删除主键
1 | alter table user drop primary key; |
还有一种联合主键,这种主键是由两个或两个以上的字段形成一个元组,把这个元组整体作为主键
在添加一条记录时,只要联合主键中有一个字段的值不一样就能添加成功。
1 | mysql> create table user2( |
1 | mysql> desc user2; |
自增约束
主键由系统自动递增分配
1 | mysql> create table user3( |
这样就可以在添加元组时只添加name字段
1 | mysql> create table user3( |
唯一约束
使用unique来表明某字段必须是唯一的
可以在定义表时就说明
1 | mysql> create table user4( |
也可以在定义完一个表之后再添加唯一约束
1 | mysql> create table user5( |
删除唯一约束可以使用以下命令
1 | alter table user5 drop index name; |
类似联合主键,也可以定义如下
1 | mysql> create table user7( |
非空约束
表明某字段不能为空
1 | mysql> create table user9( |
默认约束
当插入字段值时,如果没有传值,就会使用默认值
1 | mysql> create table user10( |
外键约束
涉及到两个表
子表的操作要参照父表,父表只有在未被子表refer时才能变动
1 | #父表 |
1 | #在父表中插入数据 |
1 | #在子表中插入数据 |
但是当插入(1005,'李四',5)
时会报错,因为父表的id字段中只有1,2,3,4而没有5
1 | mysql> insert into students values(1005,'李四',5); |
总结一下:
父表classes中没有的数据值,在子表中,是不能使用的
父表中的记录若被子表引用,是不可被删除的
数据库的三大设计范式
第一范式
数据包中的所有字段都是不可分割的原子值
第二范式
在满足第一范式的前提下,除主键以外的其他列都必须完全依赖于主键列。如果出现不完全依赖,只可能发生在联合主键的情况下
1 | mysql> create table myoder( |
在表myorder
中,product_name
只依赖于 product_id
,customer_name
只依赖于 customer_id
。也就是说,product_name
和 customer_id
是没有关系的,customer_name
和 product_id
也是没有关系的
这就不满足第二范式:其他列都必须完全依赖于主键列!
可以通过拆分的方法使其满足第二范式
1 | mysql> create table myorder( |
这三个表都分别满足第二范式
第三范式
在满足第二范式的前提下,除了主键列之外,其他列之间不能有传递依赖关系
也就是要确保数据表中的每一列数据都和主键直接相关,而不能间接相关 ,这样能减小数据冗余
1 | CREATE TABLE myorder ( |
表中的 customer_phone
有可能依赖于 order_id
(主键)、 customer_id
(非主键) 两列,也就不满足第三范式的设计:其他列之间不能有传递依赖关系。
同样可以通过拆分来使其满足第三范式
1 | CREATE TABLE myorder ( |
修改后就不存在其他列之间的传递依赖关系,其他列都只依赖于主键列,满足了第三范式的设计!
查询练习
准备数据
1 | #创建一个新的数据库 |
1-10
1 | #1.查询student表的所有记录 |
1 | # 11.查询每门课的平均成绩 |
1 | #12.查询score表中至少有2名学生选修的并以3开头的课程的平均分数 |
注释 :having(过滤的意思) 用于分组后,where用于分组前
1 | # 13. 查询分数大于70,小于90的sno列 |
多表查询
1 | # 14.查询所有学生的sname、cno和degree列 |
1 | # 15.查询所有学生的sno、cname和degree列 |
1 | #16. 查询所有学生的sname、cname和degree列 |
子查询
1 | # 17.查询“95031”班学生每门课的平均分 |
1 | # 18.查询选修“3-105”课程的成绩高于“109”号同学“3-105”成绩的所有同学的记录 |
1 | # 19.查询成绩高于学号为“109”、课程号为“3-105”的成绩的所有记录 |
1 | # 20. 查询和学号为108、101的同学同年出生的所有学生的sno、sanme和sbirthday列 |
1 | # 21.查询“张旭”教师任课的学生成绩 |
1 | # 22. 查询选修某课程的同学人数多于5人的教师姓名 |
1 | # 23.查询95033班和95031班全体学生的记录 |
1 | # 24查询存在有85分以上成绩的课程cno |
1 | # 25. 查询出“计算机系”教师所教课程的成绩表 |
union(求并集) 和not in
1 | # 26. 查询“计算机系”与“电子工程系”不同职称的教师的tname和prof(取交集之外的记录) |
any:至少一个(只要有一个就行)
1 | # 27. 查询选修编号为“3-105”课程且成绩至少高于选修编号为“3-245”的同学的成绩中的任意一个的cno、sno和degree |
all:全部(都要满足)
1 | # 28.查询编号为‘3-105’且成绩高于编号为‘3-245’课程的同学的cno、sno和degree |
as取别名
1 | # 29.查询所有教师和同学的name、sex和birthday |
union求并集
1 | # 30.查询所有“女”教师和“女”同学的name,sex和birthday |
1 | # 31. 查询成绩比该课程平均成绩低的同学的成绩表 |
子查询
1 | # 32.查询所有任课教师的tname和depart |
1 | # 33. 查询至少有两名男生的班号 |
not like
1 | # 查询student表中不姓“王”的同学记录 |
year和now函数
1 | # 35.查询student表中每个学生的姓名和年龄 |
max与min
1 | # 36. 查询student表中最大和最小的sbirthday日期值 |
多字段排序
1 | # 37.以班号和年龄从大到小的顺序查询student表中的全部记录 |
子查询
1 | # 38.查询“男”教师及其所上的课程 |
max函数与子查询
1 | # 39.查询最高分同学的sno、cno和degree列 |
继续子查询
1 | # 40.查询和“李军”同性别的所有同学的sname |
1 | # 41.查询和"李军"同性别且同班级的同学的sname |
1 | # 42.查询所有选修“计算机导论”课程的“男”同学的成绩 |
按等级查询
1 | # 43.假设建立一个 grade 表代表学生的成绩等级,并插入数据: |
SQL的四种连接查询
内连接:inner join 或者join
外连接:
左连接:left join 或者left outer join
右连接:right join 或者right outer join
完全外连接:full join 或者full outer join
1 | # 先准备一些数据,用于连接查询练习 |
注意:
我们并没有创建外键
王五的cardID不能对应id
1 | # inner join (内连接)查询(求交集,其实就是两张表中的数据通过某个字段相等查询出相关记录) |
事务
在 MySQL 中,事务其实是一个最小的不可分割的工作单元。事务能够保证一个业务的完整性。
在实际项目中,假设只有一条 SQL 语句执行成功,而另外一条执行失败了,就会出现数据前后不一致。
因此,在执行多条有关联 SQL 语句时,事务可能会要求这些 SQL 语句要么同时执行成功,要么就都执行失败。
如何控制事务
在MySQL中,事务的自动提交默认开启
1 | mysql> select @@autocommit; |
开启之后,不能回滚(无法反悔),可以设置autocommit为0来关闭自动提交
1 | mysql> set autocommit=0; |
此时可以通过rollback回滚来回到上一次提交的状态,通过commit来手动提交,举个例子
1 | CREATE DATABASE bank; |
手动开启一个事务(局部)
可以使用 begin 或者 start transaction
1 | -- 使用 BEGIN 或者 START TRANSACTION 手动开启一个事务 |
仍然使用 COMMIT
提交数据,提交后无法再发生本次事务的回滚。
1 | #此时已开启自动提交,但是之后又开启了一个事务,当事务结束时,我们可以通过手动提交的方式来结束事务,现在再来测试 |
事务的四大特性
A 原子性
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,这和前面两篇博客介绍事务的功能是一样的概念,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
C 一致性
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。 拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
I 隔离性
事务1和是事务2之间是具有隔离性的
D 持久性
事务一旦结束,就不可返回(比如commit之后就不能rollback)
事务的隔离性可分为4种(性能由低到高):隔离级别越高,性能越低
READ UNCOMMITTED ( 读取未提交 )
如果有多个事务,那么任意事务都可以看见其他事务的未提交数据。
READ COMMITTED ( 读取已提交 )
只能读取到其他事务已经提交的数据。
REPEATABLE READ ( 可被重复读 ),默认是这个
如果有多个连接都开启了事务,那么事务之间不能共享数据记录,否则只能共享已提交的记录。
SERIALIZABLE ( 串行化 )
所有的事务都会按照固定顺序执行,执行完一个事务后再继续执行下一个事务的写入操作。
查看当前数据库的默认隔离级别:
1 | -- MySQL 8.x, GLOBAL 表示系统级别,不加表示会话级别。 |
修改隔离级别:
1 | -- 设置系统隔离级别,LEVEL 后面表示要设置的隔离级别 (READ UNCOMMITTED)。 |
1.脏读(READ UNCOMMITTED)
测试 READ UNCOMMITTED ( 读取未提交的 ) 的隔离性:
1 | INSERT INTO user VALUES (3, '小明', 1000); |
由于小明的转账是在新开启的事务上进行操作的,而该操作的结果是可以被其他事务(另一方的淘宝店)看见的,因此淘宝店的查询结果是正确的,淘宝店确认到账。但就在这时,如果小明在它所处的事务上又执行了 ROLLBACK
命令,会发生什么?
1 | -- 小明所处的事务 |
这就是所谓的脏读,一个事务读取到另外一个事务还未提交的数据。这在实际开发中是不允许出现的。
2.不可重复读:READ COMMITTED ( 读取已提交 的)
把隔离级别设置为 READ COMMITTED :
1 | SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED; |
这样,再有新的事务连接进来时,它们就只能查询到已经提交过的事务数据了。但是对于当前事务来说,它们看到的还是未提交的数据,例如:
1 | -- 正在操作数据事务(当前事务) |
但是这样还有问题,那就是假设一个事务在操作数据时,其他事务干扰了这个事务的数据。例如:
1 | -- 小张在查询数据的时候发现: |
虽然 READ COMMITTED 让我们只能读取到其他事务已经提交的数据,但还是会出现问题,就是在读取同一个表的数据时,可能会发生前后不一致的情况。这被称为不可重复读现象 ( READ COMMITTED )。
3.幻读:REPEATABLE READ ( 可被重复读 )
将隔离级别设置为 REPEATABLE READ ( 可被重复读取 ) :
1 | SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ; |
测试 REPEATABLE READ ,假设在两个不同的连接上分别执行 START TRANSACTION
:
1 | -- 小张 - 成都 |
当前事务开启后,没提交之前,查询不到,提交后可以被查询到。但是,在提交之前其他事务被开启了,那么在这条事务线上,就不会查询到当前有操作事务的连接。相当于开辟出一条单独的线程。
无论小张是否执行过 COMMIT
,在小王这边,都不会查询到小张的事务记录,而是只会查询到自己所处事务的记录:
1 | SELECT * FROM user; |
这是因为小王在此之前开启了一个新的事务 ( START TRANSACTION
) ,那么在他的这条新事务的线上,跟其他事务是没有联系的,也就是说,此时如果其他事务正在操作数据,它是不知道的。
然而事实是,在真实的数据表中,小张已经插入了一条数据。但是小王此时并不知道,也插入了同一条数据,会发生什么呢?
1 | INSERT INTO user VALUES (6, 'd', 1000); |
报错了,操作被告知已存在主键为 6
的字段。这种现象也被称为幻读,一个事务提交的数据,不能被其他事务读取到。
4.串行化:SERIALIZABLE
顾名思义,就是所有事务的写入操作全都是串行化的。什么意思?把隔离级别修改成 SERIALIZABLE :
1 | SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE; |
还是拿小张和小王来举例:
1 | -- 小张 - 成都 |
此时会发生什么呢?由于现在的隔离级别是 SERIALIZABLE ( 串行化 ) ,串行化的意思就是:假设把所有的事务都放在一个串行的队列中,那么所有的事务都会按照固定顺序执行,执行完一个事务后再继续执行下一个事务的写入操作 ( 这意味着队列中同时只能执行一个事务的写入操作 ) 。
根据这个解释,小王在插入数据时,会出现等待状态,直到小张执行 COMMIT
结束它所处的事务,或者出现等待超时。
参考:
https://www.bilibili.com/video/BV1Vt411z7wy?p=64
https://github.com/hjzCy/sql_node/blob/master/mysql/MySQL%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0.md