MySQL 事务以及 MVCC 机制一
一、事务及其 ACID 属性
事务是由一组 SQL 语句组成的逻辑处理单元,事务具有以下 4 个属性,通常简称为事务的 ACID 属性。
1. 原子性(Atomicity)
事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。
由
undo log控制。
2. 一致性(Consistency)
在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性。
一致性由其他三个特性共同保证。
3. 隔离性(Isolation)
数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的"独立"环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。
事务隔离级别
| 隔离级别 | 说明 | 解决的问题 |
|---|---|---|
| Read Uncommitted | 一个事务查询时,可以读到另一个未提交事务的值 | — |
| Read Committed (RC) | 只能读事务已提交的数据,只要 commit 就能查询到 | 解决脏读 |
| Repeatable Read (RR) | 执行第一条 SQL 时会把整个库当时的数据生成快照,后续操作不受其他事务影响 | 解决脏读、部分幻读 |
| Serializable | 事务串行排队处理,性能最低 | 解决脏读、幻读、不可重复读 |
Read Uncommitted:一个事务查询时,可以查到另一个未提交事务的值。俗称脏读。
Read Committed:只能读事务已经提交的数据,只要 commit 就能查询到,导致查同一个数据可能值都不一样,俗称不可重复读。
Repeatable Read:执行第一条 SQL 时会把整个库当时的数据生成快照,后续操作不受其他事务影响,俗称可重复读。
它解决了幻读吗?解决了部分:
- 查询操作:已解决
- 涉及修改操作(如另一个事务把张三改成 2000,当前事务执行
balance = balance + 1):它读的是数据库最新的数据,并且对于快照之后新加的数据,做查询查不到,但做修改会被修改。那这些数据对于当前事务来说是凭空出现的,这就是幻读。
Serializable:顶级隔离能力,事务串行排队处理,解决了脏读、幻读/不可重复读。
4. 持久性(Durability)
事务完成之后,它对数据的修改是永久性的,即使出现系统故障也能够保持。
二、事务隔离级别配置
数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上"串行化"进行,这显然与"并发"是矛盾的。同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对"不可重复读"和"幻读"并不敏感,可能更关心数据并发访问的能力。
查看当前隔离级别:
sql
复制
SHOW VARIABLES LIKE 'tx_isolation';
设置事务隔离级别:
sql
复制
SET tx_isolation = 'REPEATABLE-READ';
注意: MySQL 默认的事务隔离级别是可重复读(Repeatable Read) 。用 Spring 开发程序时,如果不设置隔离级别,默认使用 MySQL 设置的隔离级别;如果 Spring 设置了就使用已经设置的隔离级别。
那串行模式是怎么做到一个事务没提交、另一个事务执行时被挂起等待的呢?锁。
三、MVCC 多版本并发控制
3.1 什么是 MVCC
公司用的最多的是 RC(Read Committed) 和 RR(Repeatable Read) ,它们是怎么实现的呢?
MVCC(Multi-Version Concurrency Control) ,即多版本并发控制,就可以做到读写不阻塞,且避免了类似脏读这样的问题,主要通过 undo 日志链来实现。
3.2 MVCC 原理
我们创建一张表,除了业务字段以外,MySQL 会同步帮我们维护事务 ID 字段和回滚指针。
undo log 的工作流程:
- INSERT 操作:当 insert 一条数据,在 undo log 里会同步生成一个 delete 回滚记录,这时候回滚指针指向的就是这条记录。当 insert 出现异常,就是通过执行这条回滚记录来删除数据的。
- UPDATE 操作:当一条数据 commit 后,另一个 update 事务进来,这个时候 undo 日志会生成一条新的日志,回滚指针指向的是上一条 commit 的数据。
MySQL 通过这种日志版本链实现多版本并发控制机制,实现了读写不阻塞。
四、事务优化实践原则
- 将查询等数据准备操作放到事务外
- 事务中避免远程调用,远程调用要设置超时,防止事务等待时间太久
- 事务中避免一次性处理太多数据,可以拆分成多个事务分次处理
- 更新等涉及加锁的操作尽可能放在事务靠后的位置
- 能异步处理的尽量异步处理
- 应用侧(业务代码)保证数据一致性,非事务执行