事务可以分成 4 个隔离级别:读未提交、读已提交、可重复读、串行化。而事务隔离是为了解决脏读、不可重复读、幻读问题,下表展示了 4 种隔离级别对这三个问题的解决程度:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ UNCOMMITTED | 可能 | 可能 | 可能 |
READ COMMITTED | 不可能 | 可能 | 可能 |
REPEATABLE READ | 不可能 | 不可能 | 可能 |
SERIALIZABLE | 不可能 | 不可能 | 不可能 |
下面就来介绍下。
脏写
先补充一个:脏写。
原来有一个数据时null值,事务A去写数据,将这行数据改成了A,之后又有事务B去改动,将数据从A变成了B,但是这时事务A发现出错了,于是回滚。回滚到事务 A 执行之前的那个值,也就是 NULL。这种变化对于B来说就是什么,数据传进去,本身没出错,但是最后数据是null。这就是脏写。
脏读
脏读:一个事务读取到了另外一个事务没有提交的数据
事务A对数据进行操作,将一个值进行修改为A,但是没有提交,这时有一个事务B来读数据,读到了A没有提交的数据。这就是脏读。注意是事务A未提交的数据。
不可重复读
不可重复读:在同一事务中,两次读取同一数据,得到内容不同
事务A先去读数据,读到了A,之后事务B对数据进行修改提交,将数据变成了B。这之后事务A又一次来读数据,发现数据不一样了。这就是不可重复读,注意这里数据是提交过后的,这也是与脏读的区别。
幻读
幻读:同一事务中,用同样的操作读取两次,得到的记录数不相同
前面的脏写、脏读、不可重复读,都是针对一行数据来说的,幻读不一样,幻读是指查到了之前没有的一批数据:
事务A通过查询语句查到了5行数据,之后事务B增加或删除了1行数据,然后事务A再来读,发现两次读的数据的行数不一致,这个事务 A 以为自己出现幻觉了,这就是幻读。
解决方案
脏写、脏读
原本情况下,这是读未提交的事务隔离等级。解决脏写、脏读问题的方法就很简单,不允许一个事务读取还没有提交的事务,于是提高事务的隔离等级,读已提交。
在读已提交这个隔离级别下,事务只能读到已经提交的事务,也就避免了事务回滚带来的脏写、脏读问题。
脏读:修改时加排他锁,直到事务提交后才释放,读取时加共享锁,之后事务如果有更新操作,那么会转换为排他锁,其他事务无权参与进来读写,这样就防止了脏读问题。
但是当事务读取数据过程中,有可能其他事务也读取了该数据,读取完毕后共享锁释放,此时原来事务修改数据,修改完毕提交事务,其他事务再次读取数据时候发现数据不一致,就会出现不可重复读问题,所以这样不能够避免不可重复读问题。
不可重复读
那就可以将隔离级别从读已提交提升到可重复读。
不可重复读:读取数据时加共享锁,写数据时加排他锁,都是事务提交才释放锁。读取时候不允许其他事物修改该数据,不管数据在事务过程中读取多少次,数据都是一致的,避免了不可重复读问题
为了解决不可重复读,MySQL 采用了 MVVC (多版本并发控制) 的方式。
可重复读是在事务开始的时候生成一个当前事务全局性的快照,而读提交则是每次执行语句的时候都重新生成一次快照。而快照要遵循以下原则:
- 当前事务内的更新,可以读到;
- 版本未提交,不能读到;
- 版本已提交,但是却在快照创建后提交的,不能读到;
- 版本已提交,且是在快照创建前提交的,可以读到。
那什么是版本呢?
在数据库中一行记录可能实际上有多个版本,它有一个版本的字段记为 row trx_id,它在事务开始的时候向事务系统申请,按时间先后顺序递增。
幻读
将隔离级别提高到串行化。正如名称一样,所有事务串行,一次只能执行一个事务,完全禁止并发。
幻读问题:采用的是范围锁RangeS RangeS_S模式,锁定检索范围为只读,这样就避免了幻影读问题
MySQL 已经在可重复读隔离级别下解决了幻读的问题,用的是间隙锁。MySQL 把行锁和间隙锁合并在一起,解决了并发写和幻读的问题,这个锁叫做 Next-Key锁。