MySQL基础篇-事务隔离

事务隔离:为什么你改了我还看不见?

隔离性与隔离级别

提到事务,马上联想到ACID(Atomicity、Consistency、Isolation、Durability,即原子性、一致性、隔离性、持久性)。这里主要介绍隔离性

当数据库上有多个事务同时执行的时候,就可能出现脏读(dirty read)、不可重复度(non-repeatable)、幻读(phantom read)的问题,为了解决这些问题,就有了“隔离级别”的概念。

SQL标准的事务隔离界别

  • 读未提交(read uncommitted) 一个事务还没有提交时,它做的变更就能被别的事务看到。
  • 读提交(read committed)一个事务提交之后,它的变更才会被其他事务看到。
  • 可重复读(repeatable read)一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务是不可见的。
  • 串行化(serializable)对于同一行记录,写 会加写锁,读会加读锁。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。

假设数据库表T中只有一列,其中一行的值为1,按照时间顺序执行两个事务的行为。

1
2
3

create table T(c int) engine=InnoDB;
insert into T(c) values(1);

  • 若隔离级别是“读未提交”,则V1的值就是2,这个时候事务B虽然还没有提交,但是结果已经被A看到了。因此V2,V3都是2;

  • 若隔离级别是“读提交”,则V1是1,V2的值是2.事务B的更新在提交之后才能被A看到,所以V3的值也是2。

  • 若隔离级别是“可重复读”,则V1、V2是1,V3是2。之所以V2还是1,遵循的就是这个要求:事务在执行期间看到的数据前后必须是一致的。

  • 若隔离级别是“串行化”,则事务B执行“将1改成2”的时候,会被锁住。知道事务A提交之后,事务B才能继续执行。所以从A的角度看,V1、V2值都是1,V3的值是2。

在实现上,数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。

在“可重复读”隔离级别下,这个视图是在事务启动的时候创建,整个事务存在期间都用这个视图。

在“读提交”隔离级别下,这个视图是在每个SQL语句开始执行的时候创建的。

在“读未提交”隔离级别下直接返回记录上的最新值,没有视图概念;

在“串行化”隔离级别下直接用加锁的方式来避免并行访问。

MySQL 的默认事务隔离级别是可重复读

Oracle默认事务隔离级别是读提交

MySQL配置和Oracle一致的设置:

将启动参数 transaction-isolation的值设置成READ-COMMITTED。你可以用show variables来查看当前的值

1
2
3
4
5
6
7
8
9
10
11
12

mysql> show variables like 'transaction_isolation';

+-----------------------+----------------+

| Variable_name | Value |

+-----------------------+----------------+

| transaction_isolation | READ-COMMITTED |

+-----------------------+----------------+

事务隔离的实现

在MySQL中,实际上每条记录在更新的时候都会同时记录一条回滚操作。记录上的最新值,通过回滚操作,都可以得到前一个状态的值。

假设一个值从1被按照顺讯改成了2、3、4,在回滚日志里面就会有类似下面的记录。

当前值是4,但是在查询这条记录的时候,不同时刻启动的事务会有不通的read-view。

如图中看到的,在视图A、B、C里面,这个记录的值分别是1、2、4,同一条记录在系统中可以存在多个版本,但是数据库的多版本并发控制(MVCC)。对于read-view A,要得到1,就必须要将当前值一次执行图中所有的回滚操作得到。

这些回滚日志会在没有事务需要用到的时候删除,也就是当系统中没有比这个回滚日志更早的read-view的时候。

不建议使用长事务:

长事务意味着系统里面会存在很老的事务视图。由于这些事务随时可能访问数据库里面的任何数据,所以在事务提交之前,数据库里面它可能用到的回滚记录都必须保留,这就会导致占用存储空间

事务的启动方式

MySQL的事务启动方式有一下几种:

  1. 显示启动事务语句,begin或者start transaction。配套的提交语句是commit,回滚语句是rollback
  2. set autocommit = 0,这个命令会将这个线程自动提交关闭。以为着如果你只执行一个select语句,这个事务就启动了,而且并不会自动提交。这个事务持续存在知道你主动执行commit或rollback语句,或者断开连接