事务就是要保证一组数据库操作,要么全部成功,要么全部失败。在 MySQL 中,事务支持是在引擎层实现的。
MySQL 是一个支持多引擎的系统,但并不是所有的引擎都支持事务。
比如 MySQL 原生的 MyISAM 引擎就不支持事务,这也是 MyISAM 为什么被 InnoDB 取代的重要原因之一。
隔离级别
当数据库上有多个事务同时执行的时候,就可能出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题,为了解决这些问题,就有了“隔离级别”的概念。
SQL 标准的事务隔离级别包括:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable );
- 读提交是指:一个事务开启后更改了数据还未提交,另一个事务是无法读到前一个事务修改的值(一个事务提交之后,它做的变更才会被其他事务看到。);
- 都未提交是指:一个事务没有提交,改动的数据可以被其他数据看到;
- 可重复读是指:一个事务看到的总是跟开启事务之前看到的数据是一样的,如果在可重复读的情况下,事务未提交,其他事务也不见;
- 串行化:必须要等前一个事务提交后,自己才能提交。(“写”会加“写锁”,“读”会加“读锁”)。
分别看看Y1,Y2,Y3在不同的隔离级别下获取到的值是什么:
- 读提交:Y1获取的值是1,因为事务2还未提交,Y2获取的值则是2,因为事务2已经提交(将1改成2了)。事务1提交后Y3还是2。
- 读未提交:Y1获取的值是2,这时事务2还未提交,但是已经被事务1看到了 ,因此Y2,Y3获取的值也是2。
- 可重复读:Y1,Y2是1,因为开启事务看到的跟开启事务之前的数据是一致的,Y3则是2,因为事务1已经提交。
- 串行化:事务 2 执行“将 1 改成 2”的时候,会被锁住。直到事务 A 提交后,事务 B 才可以继续执行。所以从 A 的角度看, Y1、Y2 值是 1,Y3 的值是 2。
可重复读比较时候的场景:就是在对比上个月的报表和这个月的报表时,就是有新的数据产生的话,在可重复的的场景下也是没问题的。
事务启动时的视图可以认为是静态的,不受其他事务更新的影响。
在实现上,数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。在“可重复读”隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。在“读提交”隔离级别下,这个视图是在每个 SQL 语句开始执行的时候创建的。这里需要注意的是,“读未提交”隔离级别下直接返回记录上的最新值,没有视图概念;而“串行化”隔离级别下直接用加锁的方式来避免并行访问。
每一个事务开启的时候都会创建视图:
- 可重复读:这个视图在事务启动时创建,整个事务存在的期间都在用这个视图;
- 读提交:这个视图是在每个SQL语句开始执行的时候创建的;
- 读未提交:没有视图的概念,因为是返回最新的值;
- 串行化:就直接加锁了。
隔离的实现
在更新的时候都会同时记录一条回滚操作。记录上的最新值,通过回滚操作,都可以得到前一个状态的值。
比如一个值从1按顺序的修改成2,3,4,在回滚日记中就记录回滚段,回滚段中包含了1,2,3,4当前值是4,在不同时间启动的事务会有不同的read-view,同一条记录可以有多个版本存在,就是多版本并发控制(MVCC)。
如果4要回滚到1,就得依次的按照顺序回滚。
什么时候删除回滚日志呢?答案是系统会判断,没有事务再使用这些回滚日志时,会被删除。
长事务就意味这系统会存在很老的视图,由于这些事务可能随时要访问,所有这个事务提交之前,回滚日志就必须保存,这就会导致大量占用内存存储空间。