MySQL深入学习---(2) 事务隔离:为什么你改了我还看不见?

一、什么叫事务隔离

  • 简单来说,事务就是要保证一组数据库操作,要么全部成功,要么全部失败。在MySQL中,事务支持是在引擎层实现的。对于InnoDB存储引擎而言,其默认的事务隔离级别完全遵循和满足事务的ACID(原子性、一致性、隔离性、持久性)特性。
1、隔离性与隔离级别
  • 当数据库上有多个事务同时执行的时候,就可能出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题,为了解决这些问题,就有了“隔离级别”的概念。
  • 脏读:当数据库中一个事务A正在修改一个数据但是还未提交或者回滚, 另一个事务B 来读取了修改后的内容并且使用了, 之后事务A提交了,此时就引起了脏读,此情况仅会发生在读未提交的的隔离级别。
  • 不可重复读:在一个事务A中多次操作数据,在事务操作过程中(未最终提交), 事务B也才做了处理,并且该值发生了改变,这时候就会导致A在事务操作 的时候,发现数据与第一次不一样了。
  • 幻读:个事务按相同的查询条件重新读取以前检索过的数据, 却发现其他事务插入了满足其查询条件的新数据。
SQL标准定义的四个隔离级别:
  • 读未提交(read uncommitted):一个事务还未提交,它所做的变更就可以被别的事务看到。
  • 读已提交(read committed):一个事务提交之后,它所做的变更才可以被别的事务看到。
  • 可重复读(repeatable read):一个事务执行过程中看到的数据是一致的。未提交的更改对其他事务是不可见的。
  • 串行化(serializable ):对应一个记录会加读写锁,出现冲突的时候,后访问的事务必须等前一个事务执行完成才能继续执行。

下面用一个例子说明这几种隔离级别,假设数据表T中只有一列数据,其中一行的值为1:

insert into T(c) values(1);

MySQL深入学习---(2) 事务隔离:为什么你改了我还看不见?_第1张图片

在上面几种隔离级别下,事务A会有哪些不同的返回结果呢?

  • 若隔离级别是“读未提交”, 则 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。

对于这四种隔离级别的实现,是依靠视图来实现的:
1、读未提交:没有视图的概念,直接返回记录的最新值;
2、读已提交:每次执行sql语句之前创建视图;
3、可重复读:每次创建事务的时候创建视图;
4、串行化:通过加锁来避免并行访问;

注意
在不同的隔离级别下,数据库行为是有所不同的。Oracle 数据库的默认隔离级别其实就是“读已提交”,因此对于一些从 Oracle 迁移到 MySQL 的应用,为保证数据库隔离级别的一致,你一定要记得将 MySQL 的隔离级别设置为“读已提交”。配置方式是将启动参数transaction-isolation 的值设置成 READ-COMMITTED

2、事务隔离级别的实现(针对“可重复读”)
  • 假设一个值从 1 被按顺序改成了 2、3、4,在回滚日志里面就会有类似下面的记录。

MySQL深入学习---(2) 事务隔离:为什么你改了我还看不见?_第2张图片

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

解析

  • 1、将2改成1 ,将3改成2 ,将4改成3 ,这三个叫做回滚日志;
  • 2、如果当前值是1,这个时候有一个大事务A,他进行了一系列操作: 在这个大事务A开启的时候,会创建一个视图A,这个视图A中的数据其实并不是1,而是针对1的回滚段。
  • 3、为什么是回滚段?因为mysql在读数据的时候,即使数据没有提交,读到的也是最新的值。只不过是通过回滚段将数据再回滚回初始的值。这样保证“可重复读”隔离级别中读到的永远是最初的值。
  • 4、在这个大事务A还没有结束的时候,如果有另一个事务B把1改了,此时数据变成了2,那么视图A中看到的数据其实也是2;
  • 5、但是视图A中存了事务B的回滚段;当我们从视图A取数据的时候,取得是2,然后执行回滚段,得到1返回;
  • 6、回滚日志什么时候删除呢? 当系统中没有比回滚日志创建时更早的视图了。
  • 7、同时你会发现,即使现在有另外一个事务正在将 4 改成 5,这个事务跟 read-view A、B、C 对应的事务是不会冲突的。

:基于上面的说明,我们来讨论一下为什么建议你尽量不要使用长事务?

:长事务导致对应的事务视图长时间存在,那么对应的回滚日志也是会一直存在的,除了对回滚段的影响,长事务还占用锁资源,也可能拖垮整个库。

3、事务的启动方式
  • MySQL 的事务启动方式有以下几种:
  • 显式启动事务语句, begin 或 start transaction。配套的提交语句是 commit,回滚语句是 rollback。
  • set autocommit=0,这个命令会将这个线程的自动提交关掉。意味着如果你只执行一个 select 语句,这个事务就启动了,而且并不会自动提交。这个事务持续存在直到你主动执行 commit 或 rollback 语句,或者断开连接。

但是建议使用set autocommit=1,通过显式语句的方式来启动事务。因为在 autocommit 为 1 的情况下,用 begin 显式启动的事务,如果执行 commit 则提交事务。如果执行 commit work and chain,则是提交事务并自动启动下一个事务,这样也省去了再次执行 begin 语句的开销。同时带来的好处是从程序开发的角度明确地知道每个语句是否处于事务中。

你可能感兴趣的:(Mysql,数据库,mysql)