【一】隔离机制
【1】数据库读现象的本质
- 是数据库在高并发场景下
- 多个同时执行的事务带来的影响。
【2】数据库三大读现象
- 在数据库中,不同的事务隔离级别可能会导致脏读(Dirty Read)、不可重复读(Non-repeatable Read)和幻读(Phantom Read)等问题的出现。
【3】脏读
(1)概述
- 事务1和事务2并发执行
- 事务1改了数据
- 事务2读取了以后
- 但事务1进行了回滚
- 导致事务2读取的数据有误。
(2)解释
- 脏读是指当一个事务读取了其他事务尚未提交的数据时发生的现象。
- 换句话说,脏读表示读取到的数据并不一定会最终存入数据库中,因此这些数据实际上是不存在的。
- 脏读现象发生在读取到了不一定存在的数据的情况下。
(3)总结
- 脏读指的是读当前事务到了其他事务未提交的数据,未提交意味着这些数据可能会回滚,
- 也就是可能最终不会存到数据库中,也就是不存在的数据。读到了并一定最终存在的数据,这就是脏读
【4】不可重复读
(1)概述
- 事务1读取了数据
- 事务2修改了数据并且提交了
- 接着事务1再次读取
- 发现两次的数据不相同
(2)解释
- 不可重复读是指在一个事务内多次读取同一批数据,但在事务结束之前,这批数据可能发生了变化,导致读取结果不一致的情况。
- 不可重复读的产生通常是由于在事务A多次读取同一数据的过程中,事务B对数据进行了更新并提交。
(3)总结
- 解释:不可重复读指的是在一个事务内,最开始读到的数据和事务结束前的任意时刻读到的同一批数据出现不一致的情况
- 导致的原因:事务 A 多次读取同一数据,但事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致
【5】幻读
(1)概述
- 本质上说是不可重复读的一种现象
- 事务1更改或查询了数据
- 在极短时间内,事务2又插入了一条新的数据
- 导致事务1在接下来的查询中
- 就会发现有⼏列数据是它先前所没有的。
(2)错误的理解
[1]解释
-
有时候,人们错误地将幻读理解为在两次select操作中获得了不同的数据集
-
- 例如第一次select得到10条记录
- 第二次select得到15条记录。
-
实际上,这种情况仍然属于不可重复读而非幻读。
[2]总结
- 幻读是 事务A 执行两次 select 操作得到不同的数据集,即 select 1 得到 10 条记录,select 2 得到 15 条记录。
- 这其实并不是幻读,既然第一次和第二次读取的不一致,那不还是不可重复读吗,所以这是不可重复读的一种。
(3)正确的理解
[1]解释
- 幻读的本质在于某一次select操作得到的结果无法支撑后续的业务操作。
- 具体来说,例如在执行select判断某条记录是否存在时,假设该记录不存在,准备插入该记录,但在执行insert时却发现该记录已经存在,导致无法插入,这即是幻读的发生。
[2]总结
- 幻读,并不是说两次读取获取的结果集不同,幻读侧重的方面是某一次的 select 操作得到的结果所表征的数据状态无法支撑后续的业务操作。
- 更为具体一些:select 某记录是否存在,不存在,准备插入此记录,但执行 insert 时发现此记录已存在,无法插入,此时就发生了幻读
(4)解决办法
- 要解决脏读,不可重复读和幻读的问题
- 我们就要引入几个概念:MVCC机制,事务隔离机制和数据库锁机制。
[总结]
# 【总结】
# 脏读 A 读到了 改了 B 去读 读到了改后的数据 A 回滚了 导致 B 读到的数据是不存在数据
# 不可重复读 A 读了 B 立马读 立马改 A 又读了一遍 发现两次读取到的数据不一致
# 幻读 A 读了 B 立马插入一条数据 A 紧接着插入数据 结果发现数据已经存在插不进去
【6】隔离性的四种级别(解决办法)
(1)引入
-
而隔离性顾名思义指的就是事务彼此之间隔离开
-
- 多个事务在同时处理一个数据时彼此之间互相不影响
- 如如果隔离的不够好就有可能会产生脏读、不可重复度、幻读等读现象
-
为此,隔离性总共分为四种级别
-
由低到高依次为
-
- Read uncommitted(未提交读)
- Read committed (提交读)
- Repeatable read(可重复读)
- Serializable(串行化)
(2)四种级别
[1]Read uncommitted(读未提交)
- 最低的隔离级别,在这个级别下,一个事务可以读取到另一个事务尚未提交的数据,可能导致脏读(Dirty Read)问题,即读取到未经验证的数据。
[2]Read committed(读已提交)
- 在这个级别下,一个事务只能读取到已经提交的数据,避免了脏读问题。
- 但是可能会出现不可重复读(Non-repeatable Read)问题,即同一事务中,两次读取相同的记录可能得到不同的结果,因为其他事务修改了这些记录。
[3]Repeatable read(可重复读取)
- 在这个级别下,事务开始读取数据后,其他事务无法修改这些数据,保证了同一个事务内两次读取相同记录的一致性。
- 但是可能会出现幻读(Phantom Read)问题,即同一查询在同一事务中两次执行可能返回不同的结果,因为其他事务插入或删除了符合查询条件的记录。
[4]Serializable(串行化)
- 最高级别的隔离级别,要求事务串行执行,事务之间完全隔离,避免了脏读、不可重复读和幻读问题。
- 但是这会牺牲并发性能,因为并发事务被限制为顺序执行。
【二】锁机制
【1】什么是锁机制
-
我们可以通过一个很简单的比喻来理解事务的锁机制。
-
比如同一个办公室的同事们
-
- 都想使用打印机打印文件
-
-
- 如果不加以控制
- 可能出现两个人同时打印不同的内容在一个文件里
- 就会引起内容混乱。
-
-
- 于是,我们就引入了锁的概念
-
-
- 当有并发的多个事务同时操作同一份数据时
- 只有“抢到”了锁的事务
- 才能真正去操作数据
- 使得数据的安全性得到保证。
-
【2】为什么要用锁机制
-
锁保证并发的多个事务同一时间只有一个能运行
-
- 会一定程度上降低程序的运行效率
- 但是能大大提升数据的安全性。
【3】数据库锁的分类
(1)按粒度分
-
数据库的锁按粒度分为
-
- 行级锁
- 表级锁
- 页级锁
[1] 什么是行级锁
-
⾏级锁是Mysql中锁定粒度最细的⼀种锁
-
- 表示只针对当前操作的⾏进⾏加锁。
-
⾏级锁能⼤⼤减少数据库操作的冲突。
-
- 其加锁粒度最⼩,但加锁的开销也最⼤。
-
⾏级锁分为共享锁和排他锁。
[2] 行级锁的特点
- 开销⼤,加锁慢;
- 会出现死锁;
- 锁定粒度最⼩,发⽣锁冲突的概率最低,并发度也最⾼
[3] 行级锁解释
-
由于数据库的库和表都是事先建好的
-
- 所以我们针对数据库的操作一般都是针对记录。
- 而对记录进行的四种操作(增删改查)
- 我们可以分为两类
-
-
- 增删改属于读操作
- 而查询属于写操作。
-
-
写操作默认就会加锁,且加的是互斥锁
-
- 很容易理解,在进行写行为的时候一定是必须“排他”的。
- 读操作默认不受任何锁影响
- 但是互斥锁和共享锁都可以加。
-
读操作加互斥锁 for update;
-
读操作加共享锁 lock in share mode;
提示:关于共享锁和互斥锁,我们将在下一小节更详细地讲述
[4] 行级锁锁的是索引
-
行级锁锁的是索引
-
- 命中索引以后才会锁行
- 如果没有命中索引
- 会把整张表都锁起来。
-
命中主键索引就锁定这条语句命中的主键索引
-
- 命中辅助索引就会先锁定这条辅助索引
- 再锁定相关的主键索引
- 考虑到性能,innodb默认支持行级锁
- 但是只有在命中索引的情况下才锁行,
-
否则锁住所有行
-
- 本质还是行锁
- 但是此刻相当于锁表了
[5] 行级锁的三种算法
-
1、Record lock
-
2、Gap lock
-
3、Next-key lock
-
其中 Next-key lock 为MySQL默认的锁机制
-
- 相当于另外两种锁的功能的整合
- 并能够解决幻读问题。
-
提示:
-
- 在RR事务隔离机制下,才会锁间隙
- 而RR机制是mysql的默认事务隔离机制。
- 所以,在默认情况下,其实innodb存储引擎锁的是行以及间隙.
-
我们可以用一个实验来验证上述关于行锁的结论
事务一 | 事务二 |
---|---|
start transaction; | 开启事务start transaction; |
-- 加排他锁select from t1 where id=7 for update; -- 须知-- 1、上述语句命中了索引,所以加的是行锁-- 2、InnoDB对于行的查询都是采用了Next-Key Lock的算法,锁定的不是单个值,而是一个范围(GAP)表记录的索引值为1,5,7,11,其记录的GAP区间如下:(-∞,1],(1,5],(5,7],(7,11],(11,+∞)因为记录行默认就是按照主键自增的,所以是一个左开右闭的区间其中上述查询条件id=7处于区间(5,7]中,所以Next-Key lock会锁定该区间的记录,但是还没完-- 3、*InnoDB存储引擎还会对辅助索引下一个键值加上gap lock**。区间(5,7]的下一个Gap是(7,11],所以(7,11]也会被锁定综上所述,最终确定5-11之间的值都会被锁定 | |
-- 下述sql全都会阻塞在原地insert t1 values(5);insert t1 values(6);insert t1 values(7);insert t1 values(8);insert t1 values(9);insert t1 values(10); -- 下述等sql均不会阻塞insert t1 values(11); insert t1 values(1); insert t1 values(2);insert t1 values(3);insert t1 values(4); | |
-- 提交一下事务,不要影响下一次实验commit; | -- 提交一下事务,不要影响下一次实验commit; |
【4】按级别分
-
数据库的锁按级别分为
-
- 共享锁,排他锁,共享锁
- 又被称作读锁,s锁
-
-
- 含义是多个事务共享同一把锁
- 其中每个事务都能访问到数据
- 但是没有办法进行修改。
-
-
注意:
-
- 如果事务T对数据A加上共享锁后
- 则其他事务只能对A再加共享锁或不加锁(在其他事务里一定不能再加排他锁
- 但是在事务T自己里面是可以加的)
-
排他锁又被称作互斥锁,写锁,x锁
-
- 含义是如果有一个事务获取了一个数据的排他锁
- 那么其它的事务都无法再次获得该数据的任何锁了
- 但是排他锁支持文件读取,修改和写入。
【5】按使用方式分
-
数据库的锁按使用方式分为
-
- 悲观锁、乐观锁
(1)悲观锁(Pessimistic Locking)
- 顾名思义指的是对外界将要进行的数据修改操作持悲观态度
- 因此,在整个数据处理过程中,将数据处于锁定状态。
- 现在由于互联网的高并发架构,即使加上悲观锁也无法保证数据不被外界修改,因此不推荐使用。
(2)乐观锁(Optimistic Locking)
-
相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突
-
所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测
-
如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。
-
通常乐观锁的实现是在表中加一个字段(可能是时间戳或版本号)
-
在写入的时候会查询一下版本号
-
- 如果版本号没有改变,就写入数据库并同时改变版本号。
-
从本质上来说,乐观锁并没有加锁
-
- 所以效率会大大提升
- 但也有一定的缺陷,就是可能导致一部分任务的写入失败。
【6】死锁问题
-
我们举一个例子来形象的说明死锁这个概念。
-
比如你和你的邻居同时被锁在了屋子里
-
- 然而你有你邻居的钥匙
- 你的邻居也有你的钥匙
- 你们互相可以打开对方的房门
- 但是却都被锁在了各自的屋子里
- 这就是一个简单的死锁现象
【三】数据库的三大范式
【1】什么是范式
- 范式就是我们在设置数据库的表时,一些共同需要遵守的规范
- 掌握这些设计时的范式,可以让我们在项目之初,设计库的表结构更加合理和优雅
【2】三大范式之间的关系
-
三大范式之间,是逐级递进的关系,也就是说后一个范式是在前一个范式的基础上推行。
-
我们举一个形象的例子来说
-
- 我们吃饭的时候
- 第一步是买菜
- 第二步是炒菜
- 第三步才是吃菜
-
这三者之间不能颠倒,后者都是建立在前者之上的,顺序不能颠倒。
【1NF】第一范式
(1)什么是第一范式
-
表库设计时,主要是为了确保原子性,也就是存储的数据具有不可再分性。
-
注意
-
- 这里的原子性不等同于MySQL特点中的原子性
-
MySQL特性之原子性
-
- 指事务是操作数据库的基本单位,要么全部执行成功,要么全部失败回滚
- 这样做确保了数据库在任何情况下都能保持一致的状态,不会出现中间数据
-
第一范式的原子性
-
- 指一个字段不可再分割,其中不能包含其他更小的数据单元
- 也就是说,一个字段的数据不能再被进一步分解为更小的数据单元
-
为了更好的理解上述所说的原子性,我们以一个例子来说明
(2)基表
+----------------------+--------+-------+
| student | course | score |
+----------------------+--------+-------+
| 蚩梦,男,185cm | 语文 | 95 |
| 蚩梦,男,185cm | 数学 | 100 |
| 蚩梦,男,185cm | 英语 | 88 |
| 萌萌,女,170cm | 语文 | 99 |
| 萌萌,女,170cm | 数学 | 90 |
| 萌萌,女,170cm | 英语 | 95 |
+----------------------+--------+-------+
(3)基于第一范式优化
+--------------+-------------+----------------+--------+-------+
| student_name | student_sex | student_height | course | score |
+--------------+-------------+----------------+--------+-------+
| 蚩梦 | 男 | 185cm | 语文 | 95 |
| 蚩梦 | 男 | 185cm | 数学 | 100 |
| 蚩梦 | 男 | 185cm | 英语 | 88 |
| 萌萌 | 女 | 170cm | 语文 | 99 |
| 萌萌 | 女 | 170cm | 数学 | 90 |
| 萌萌 | 女 | 170cm | 英语 | 95 |
+--------------+-------------+----------------+--------+-------+
student_name | student_sex | student_height | course |
---|---|---|---|
蚩梦 | 男 | 185cm | 语文 |
蚩梦 | 男 | 185cm | 数学 |
蚩梦 | 男 | 185cm | 英语 |
萌萌 | 女 | 170cm | 语文 |
萌萌 | 女 | 170cm | 数学 |
萌萌 | 女 | 170cm | 英语 |
【2NF】第二范式
(1)什么是第二范式
-
首先第二范式是在满足第一范式的基础上
-
- 第一范式我们已经能够理解了,那么我们再来看看第二范式
-
第二范式要求表中的所有列,其数据依赖于主键
-
- 即一张表只存储同一类型的数据,不能有任何一列数据与主键没有关系
(2)第一范式基表
+--------------+-------------+----------------+--------+-------+
| student_name | student_sex | student_height | course | score |
+--------------+-------------+----------------+--------+-------+
| 蚩梦 | 男 | 185cm | 语文 | 95 |
| 蚩梦 | 男 | 185cm | 数学 | 100 |
| 蚩梦 | 男 | 185cm | 英语 | 88 |
| 萌萌 | 女 | 170cm | 语文 | 99 |
| 萌萌 | 女 | 170cm | 数学 | 90 |
| 萌萌 | 女 | 170cm | 英语 | 95 |
+--------------+-------------+----------------+--------+-------+
(3)基于第二范式优化
+------------+--------+------+--------+--------------+--------------+
| student_id | name | sex | height | department | dean |
+------------+--------+------+--------+--------------+--------------+
| 1 | 蚩梦 | 男 | 185cm | 计算机系 | 竹子老大 |
| 2 | 萌萌 | 女 | 170cm | 金融系 | 熊猫老大 |
+------------+--------+------+--------+--------------+--------------+
student_id | name | sex | height | department | dean |
---|---|---|---|---|---|
1 | 蚩梦 | 男 | 185cm | 计算机系 | 竹子老大 |
2 | 萌萌 | 女 | 170cm | 金融系 | 熊猫老大 |
+-----------+-------------+
| course_id | course_name |
+-----------+-------------+
| 1 | 语文 |
| 2 | 数学 |
| 3 | 英语 |
+-----------+-------------+
+----------+------------+-----------+-------+
| score_id | student_id | course_id | score |
+----------+------------+-----------+-------+
| 1 | 1 | 1 | 95 |
| 2 | 1 | 2 | 100 |
| 3 | 1 | 3 | 88 |
| 4 | 2 | 1 | 99 |
| 5 | 2 | 2 | 90 |
| 6 | 2 | 3 | 95 |
+----------+------------+-----------+-------+
【3NF】第三范式
(1)什么是第三范式
-
首先第三范式是在满足第二范式的基础上
-
- 第二范式我们已经能够理解了,那么我们再来看看第三范式
-
第三范式要求表中每一列数据不能与主键之外的字段有直接关系
-
- 表中的非主键列必须和主键直接相关而不能间接相关
- 非主键列之间不能相关依赖,不存在传递依赖
+------------+--------+------+--------+--------------+--------------+
| student_id | name | sex | height | department | dean |
+------------+--------+------+--------+--------------+--------------+
| 1 | 蚩梦 | 男 | 185cm | 计算机系 | 竹子老大 |
| 2 | 萌萌 | 女 | 170cm | 金融系 | 熊猫老大 |
+------------+--------+------+--------+--------------+--------------+
+------------+--------+------+--------+---------------+
| student_id | name | sex | height | department_id |
+------------+--------+------+--------+---------------+
| 1 | 蚩梦 | 男 | 185cm | 1 |
| 2 | 萌萌 | 女 | 170cm | 2 |
+------------+--------+------+--------+---------------+
+---------------+-----------------+-----------------+
| department_id | department_name | department_dean |
+---------------+-----------------+-----------------+
| 1 | 计算机系 | 竹子老大 |
| 2 | 金融系 | 熊猫老大 |
+---------------+-----------------+-----------------+
(2)小结
-
第三范式,我们通常也叫 3NF
-
- 第三范式(3NF)是在第二范式(2NF)的基础上建立起来得
- 满足第三范式(3NF)必须先满足第二范式(2NF)
-
第一范式要求我们必须遵守原子性
-
第二范式要求表中的所有列,其数据依赖于主键
-
- 即一张表只存储同一类型的数据,不能有任何一列数据与主键没有关系
-
-
- 如果表是单主键,那么主键以外的列必须完全依赖于主键,其它列需要跟主键有关系
- 如果表是复合主键,那么主键以外的列必须完全依赖于主键,不能仅依赖主键的一部分
-
-
第三范式要求表中每一列数据不能与主键之外的字段有直接关系
-
- 表中的非主键列必须和主键直接相关而不能间接相关
- 非主键列之间不能相关依赖,不存在传递依赖