事务
事务概念:事务是一个完整的操作单元,不可分割,事务中的操作要么全部成功,要么全部失败。
1. 事务特性
ACID
1.1 原子性(A)
一个事务中所有操作是不能被分割的,要么所有的操作都成功,要么都失败
1.2 一致性(C)
事务开始前到事务结束后,数据总量保持不变的
-
示例
-
转账
jack 钱数 kucy 钱数 总钱数 开始前 jack:200 lucy:200 400 结束后 jack:100 lucy:300 400 -
买书
书本数量 单价 存款 开始前 2 100 300 结束后 1 100 200
-
1.3 隔离性(I)
两个不同的事务之间是隔离的,互不影响
1.4 持久性(D)
事务一旦提交,数据永久保存磁盘上
2. 隔离级别【不同隔离级别产生的问题】
2.1 读未提交
read uncommitted
会出现脏读、幻读、不可重复读。
脏读:一个事务读到了另一个事务没有提交的数据
例如:A 欠 B 300元
还钱流程:
- A、B 开启事务
- B 查询余额,1000
- A 开始向 B 转账,A 打电话给 B,说我把钱转给你了,你看看
- B 再查询余额,1300,跟 A 说,是的,我收到了
- A 执行了 rollback
- B 再查余额,1000,因为 A 回滚了操作
2.2 读已提交
read committed
:oracle 默认
只解决了脏读,会出现幻读及不可重复读。
不可重复读:在同一个事务中,连续两次读取到的数据不一致
2.3 可重复读
repeatable read
:mysql 默认
只会出现幻读(对于增加)
-
A 事务执行查询,查到 3 条数据
-
B 事务插入一条数据,并提交
-
A 事务执行了更新,发现更新的数量是 4 条,感觉像出现了幻觉
幻读处理:可重复读隔离级别在一定程度上解决了幻读问题,但并非完全避免。它主要通过以下机制来减少幻读的可能性:
- 快照读:对于普通的 SELECT 查询,MySQL 使用多版本并发控制(MVCC)来提供一个数据的一致性快照。事务在执行第一个查询操作时会创建一个一致性视图(Read View),之后的查询都会基于这个视图进行。这意味着即使其他事务在此期间插入了新记录,当前事务也无法看到这些新记录,从而避免了幻读(在大多数情况下)。
- 当前读:对于需要修改数据的操作(如 INSERT、UPDATE、DELETE)或使用锁定读(如 SELECT ... FOR UPDATE),MySQL 使用 Next-Key Locking 机制。这是一种特殊的锁,它包括了记录锁和间隙锁,可以防止其他事务在已锁定的记录附近插入新记录,从而进一步减少幻读的可能性。
- 然而,即使在可重复读隔离级别下,幻读在某些特定场景下仍然可能发生。例如,当事务在更新记录时改变了记录的隐藏 trx_id 值,或者其他事务在间隙锁的范围内插入了新记录时,就可能出现幻读。
2.4 串行化
Serializable
不会有任何问题。相当于锁表,效率极低
2.5 实际开发会用到的隔离级别
-
read committed
:oracle默认缺点:不可重复读
-
repeatable read
:mysql 默认
扩展:read committed
已经可以解决大部分问题,为什么 MySQL 的默认隔离级别还会设置成 repeatable read
?
- 可重复读
SELECT @@transaction_isolation;
3. MVCC
MVCC 是数据库中一种保证并发性与数据安全性的思想
3.1 生效时间
只有在数据事务隔离级别是读已提交或者可重复读时生效
- 读是从缓存中拿数据
- 第一次查询时,查询表,获取数据,写入数据库缓存
- 后期的查询,只要是缓存中有的数据,直接查缓存,不查询表。以此来保证并发性
- 写的时候会把数据库把最新的数据同步到缓存
- 保证安全性
3.2 使用三种日志
- Undolog 日志:在执行更新语句之前,需要先将旧的数据拷贝到 Undo Log 中。Undo 日志在事务提交或回滚的时候用于还原数据。
- Redo 日志:当事务提交时,MySQL 会将事务修改写入到 Redo Log 中。如果 MySQL 突然崩溃并且缓存数据不能恢复,Redo 日志就可以帮助 MySQL 恢复数据。
- Binlog 日志:Binlog 日志主要用于主从复制。当从库需要和主库数据保持一致时,需要从主库中读取 Binlog 的数据进行同步。
在执行插入、更新和删除操作时,MySQL 需要进行两个操作。第一个操作是将旧数据的快照保存到 Undolog 日志中。第二个操作是将新的数据记录到 InnoDB 表空间中,并同时将新数据的变更写入到 Redolog 日志和 Binlog 日志中。当事务提交时,MySQL 只有收到了所有三种类型的日志都写入成功的信号,才会返回成功事务提交的响应。这就实现了 MySQL 的 ACID 事务的特性。