深入浅出MySQL事务(一)事务隔离
一、为什么需要事务?
事务有什么作用?简单的说,事务是确保一组操作要么全部成功,要么全部失败
一个最经典的例子就是转账,假设A要给B转账,需要进行一下步骤
-
检查A的余额是否足够
-
减少A的余额
-
增加B的余额
如果由于意外,只执行了前两步,第3步没有执行成功,那会是怎样的结果
结果就是A的余额减少了,但是B的余额没有增加,我想这样的系统没有人敢使用
所以我们需要将这三个步骤使用一个事务来执行,要么全成功,要么全失败
二、隔离级别
事务有四个特性ACID(Atomicity:原子性;Consistency:一致性;Isolation:隔离性;Durability:持久性)
这里我们主要讲解一下事务的隔离性
当数据库上有多个事务同时执行的时候,就可能出现脏读、不可重复读、幻读的问题,为了解决这些问题,就有了隔离级别的概念。
隔离级别有四个级别
- 读未提交(read uncommitted):一个事务还没提交,它做的更改其他就可以被其他事务看到
- 读提交(read committed):一个事务提交后,它做的更改才可以被其他事务看到
- 可重复读(repeatable read):一个事务启动后,总是无法看到其他事务的更改
- 串行化 (serializable ):对于同一行记录,写会加写锁,读会加读锁,当事务之间读写冲突时,必须等待上一个事务执行完后才能继续执行,使得事务编程了串形执行
下面使用一个例子来说明这四种隔离界别
首先创建一张表并插入一个值
create table T(
id int,
) engine=innodb;
insert into T(id) value(4);
以下面的顺序来执行两个事务
读未提交的隔离条件下
- T1:事务A读到id等于4
- T2:事务A读到id等于5
- T3:事务A读到id等于5
- T4:事务A读到id等于5
读提交的隔离条件下
- T1:事务A读到id等于4
- T2:因为事务B未提交,所以其更改对事务A不可见,事务A读到的id等于4
- T3:由于事务B提交了,所以其更改对事务A可见,事务A读到的id等于5
- T4:读到id等于5
可重复读的隔离条件下
- T1:事务A读到id等于4
- T2:由于可重复读情况下,事务无法见到其他事务的操作,所以事务A未提交前读到id都等于4
- T3:事务A读到id等于4
- T4:由于事务A提交了,所以已提交的事务对其是可见的,此时读到的id=5
串形化的隔离条件下
- T1:事务A读取时,会给数据行加读锁,此时id等于4
- T2:事务A为提交,还继续拥有读锁,事务B的操作会被阻塞,所以此时id等于4
- T3:事务A为提交,还继续拥有读锁,事务B的操作会被阻塞,所以此时id等于4。之后提交事务后,释放读锁,事务B开始运行,修改数据的时候,为行加上写锁
- T4:等待事务B执行完成后,开始执行,此时id等于5
可以通过启动参数 transaction-isolation 来设置事务的隔离级别,使用下面的语句可以查看当前的隔离级别
mysql> show variables like 'transaction_isolation';
+-----------------------+----------------+
| Variable_name | Value |
+-----------------------+----------------+
| transaction_isolation | READ-COMMITTED |
+-----------------------+----------------+
三、事务隔离的实现
在MySQL中,每条记录在更新的时候都会记录一条回滚操作,当前最新记录可以通过回滚操作得到上一状态的值
举个例子,如果将1依次改为2、3、4,那么在回滚日志中会有如下的记录
我们可以将当前值4,通过回滚日志的回滚操作,获取之前状态下的值。也就是将4通过回滚操作,我们知道了上一状态是3,再通过回滚操作可以知道上一状态是2
你看,这样我们同一条记录存在多个版本,当然只有最新版本的数据是存在的,而其他版本需要通过回滚操作来获取
事务隔离的实现是通过创建一个视图,根据视图的逻辑来判断当前哪个版本的数据对事务是可见的
以可重复读的隔离级别举例,事务A启动的时后创建了视图A,事务B启动的时候创建了事务B,事务C启动的时候创建了视图C,如下所示
根据视图的逻辑判断,事务A看到的数据是1,事务B看到的数据是2,事务C看到的数据是4
如果将当前值修改为5,那就会变成这样
可以看到事务读到的数据依旧没有改变。同一记录数据可以存在多个版本,就是数据库的多版本并发控制(MVCC)
对于可重复读的隔离级别,这个视图在事务启动的时候就会创建一个视图,并且整个事务执行中都会使用这个视图,所以在事务中看到的数据都是一样的
对于读提交的隔离级别,在事务中每次执行sql操作都会创建一个新的视图
对于读未提交的隔离级别,总是读到最新的数据,因此没有视图的概念
对于串形化的隔离级别,直接通过加锁来避免并发访问,也没有视图的概念
需要注意的是,回滚日志并不是一直保留下来的,当它不需要被使用的时候就会被删除,那么什么时候不需要呢?就是没有比这个回滚日志更早的视图
因此避免使用长事务,因为长事务会产生很老的视图,这样会导致回滚日志无法被删除而大量占用存储空间
四、事务的启动方式
其实很多时候并不是有意使用长事务,通常是由于误用所致。MySQL的事务启动方式有以下几种
-
显式启动事务语句, begin 或 start transaction。配套的提交语句是 commit,回滚语句是 rollback
-
sett autocommit=0,这个命令会将这个线程的自动提交关掉。意味着如果你只执行一个 select 语句,这个事务就启动了,而且并不会自动提交。这个事务持续存在直到你主动执行 commit 或 rollback 语句,或者断开连接
可以看到第二种方式如果在长连接的情况下,会不经意地启动了一个长事务,所以建议将使用set autocommit=1
五、小结
本文讲述了不同事务隔离级别的现象以及事务隔离的原理,同时也讲述了长事务带来的风险,希望通过本文,你可以更加正确地使用事务