事务
# ❌事务简介
事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账1000元,这个转账会涉及到两个关键操作就是:将小明的余额减少1000元,将小红的余额增加1000元。万一在这两个操作之间突然出现错误比如银行系统崩溃,导致小明余额减少而小红的余额没有增加,这样就不对了。事务就是保证这两个关键操作要么都成功,要么都要失败。
事务是SQL语句组成的逻辑处理单元,要么都执行,要么都不执行。
MySQL事务是默认提交的。
开启事务start transsction;
- 开启之后,增删改需要
commit
提交才能执行成功(默认的隔离级别为可重复读) - 如果发生异常需要
roll back
回滚
# MySQL怎么保证一致性的
【两个层面来考虑】
- 数据库层面:数据库通过原子性、隔离性、持久性来保证一致性。一致性是目的,原子性、隔离性、持久性是手段
- 应用层面:通过代码判断数据库数据是否有效,然后决定回滚还是提交数据!
如果你在事务里故意写出违反约束的代码,一致性还是无法保证的。例如,你在转账的例子中,你的代码里故意不给B账户加钱,那一致性还是无法保证。因此,还必须从应用层角度考虑。
# 保证原子性
InnoDB利用 undo log来保证原子性,undo log中未提交的事务进行回滚,保证事务的原子性
【undo log如何实现回滚】
用户执行了一个INSERT 10W条记录的数据,这个事务会导致分配一个新的段,即表空间会增大,但是事务ROLLBACK时,只是将插入操作的事务进行回滚,并没有缩减表的大小。因此,事务回滚实际做的是和之前相反的工作。对于INSERT,INSERT,对于DELETE,InnoDB存储引擎会完成一个INSERT操作,对于UPDATE,InnoDB存储引擎会完成一个相反的UPDATE操作。
这就实现了InnoDB存储引擎的回滚机制。
undo log的另一个作用:
undo log的另一个作用是实现MVCC(多版本并发控制)。当用户读取被其他事务占用的记录时,可以通过undo读取之前的版本信息(即快照读),以此实现非锁定读。
此外,undo log会产生redo log,因为undo log也需要被记录下来,需要持久性的保护。
# 保证持久性
InnoDB利用 redo log来保证持久性,恢复机制会将redo log中已提交的事务重做,保证事务的持久性
【redo log如何保证持久性】
当做数据修改的时候,不仅在内存中操作,还会在redo log中记录这次操作。当事务提交的时候,会将redo log日志进行刷盘(redo log一部分在内存中,一部分在磁盘上)。当数据库宕机重启的时候,会将redo log中的内容恢复到数据库中,再根据undo log和binlog内容决定回滚数据还是提交数据。
# 保证隔离性
InnoDB利用 锁机制来保证隔离性
四种隔离级别:
- 读未提交(解决丢失修改)
- 读已提交(解决丢失修改,脏读)
- 可重复读(解决丢失修改,脏读,不可重复读)
- 可串行化(解决丢失修改,脏读,不可重复读,幻读)
# 1. 事务ACID属性
- 原子性(Atomicity): 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么全都不执行
- 一致性(Consistency): 事务开始前和结束后,数据库的完整性约束没有被破坏
- 隔离性(Isolation): 同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰
- **持久性(Durability):**事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚
Mysql 的数据库的默认隔离级别为 Repeatable read , 查看方式:
show variables like 'tx_isolation';
# 2. 并发事务带来的问题
同时运行的多个事务 访问 数据库中相同的数据时,如果没有采取隔离级别,会导致并发问题
问题 | 含义 |
---|---|
丢失更新 | 当两个或多个事务选择同一行,后面事务的修改值 覆盖了 前面的事务的修改值 |
脏读 | 一个事务访问到了另一个事务 还未commit 提交的数据 |
不可重复读 | 在同一个事务中执行了两次查询语句,两次查询出的结果不相同 |
幻读 | 一个事务按照 相同的查询条件 重新读取以前查询过的数据,却发现其他事务插入了 满足其查询条件的新数据,再次查询多出了之前 不存在的数据 |
- 脏读(Dirty read): 事务(T1)正在访问数据并且对数据进行了修改,而这种修改还没有提交
commit
到数据库中,这时另外一个事务(T2)也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是临时的“脏数据”,依据“脏数据”所做的操作可能是不正确的。 - 丢失修改(Lost to modify): 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。 例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。
- 不可重复读(Unrepeatableread): 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
- 幻读(Phantom read): 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
脏读和不可重复读区别:
- 脏读:只读取了一次
- 不可重复读:修改之前和修改之后读取了两次
不可重复读和幻读区别:-
- 不可重复读:针对的是数据被修改(数据的数目不变)
- 幻读:针对的是插入删除(数据的数目发生了变化)
# 3. 隔离级别
数据库事务的隔离性:数据库系统必须具有隔离 并发运行 各个事务的能力,使它们不会相互影响,避免各种并发问题。
**隔离级别:**一个事务与其他事务的 隔离程度 称为隔离级别
隔离界别并不是越高越好,隔离级别越高性能越差。
SQL 标准定义了四个隔离级别:
- READ-UNCOMMITTED(读未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
- READ-COMMITTED(读已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
- REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
- SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
✔为可以解决,❌为无法解决
隔离级别 | 丢失更新 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
READ-UNCOMMITTED | ✔ | ❌ | ❌ | ❌ |
READ-COMMITTED | ✔ | ✔ | ❌ | ❌ |
REPEATABLE-READ | ✔ | ✔ | ✔ | ❌ |
SERIALIZABLE | ✔ | ✔ | ✔ | ✔ |
MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读)。
我们可以通过SELECT @@tx_isolation;
命令来查看
mysql> SELECT @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
2
3
4
5
6
# 4. 三大范式
1NF: 有主键,列不可分
2NF: 一个数据表只描述一件事;在符合主键的情况下,非主键不应该依赖部分主键
学号(index) 课程号(index)——> 学分<部分依赖>
3NF: 非主键不应该有传递依赖
X => Y,Y => Z, X != Z
【文章参考】