事务隔离
什么是事务?
事务就是保证一组数据库操作,要么全部成功,要么全部失败。在MySQL中,事务支持是在引擎层实现的。
提到事务,在每本介绍数据库的书上都会有ACID的概念。今天,先学习一下隔离性。
当数据库上有多个事务同时执行的时候,就可能出现脏读、不可重复读、幻读的问题。
脏读
(针对未提交数据)如果一个事务中对数据进行了更新,但事务还没有提交,另一个事务可以“看到”该事务没有提交的更新结果,这样造成的问题就是,如果第一个事务回滚,那么,第二个事务在此之前所“看到”的数据就是一笔脏数据。
(针对其他提交前后,读取数据本身的对比)不可重复读取是指同一个事务在整个事务过程中对同一笔数据进行读取,每次读取结果都不同。如果事务1在事务2的更新操作之前读取一次数据,在事务2的更新操作之后再读取同一笔数据一次,两次结果是不同的。
(针对其他提交前后,读取数据条数的对比) 幻读是指同样一笔查询在整个事务过程中多次执行后,查询所得的结果集是不一样的。幻读针对的是多笔记录。 不管事务2的插入操作是否提交,事务1在插入操作之前和之后执行相同的查询,取得的结果集是不同的。
为了解决这些问题,就有了隔离级别的概念。
首先,隔离的越严实,并发效率就会越低。因此,我们需要找一个平衡点。
SQL标准的事务隔离级别有:读未提交、读提交、可重复读、串行化。
读未提交指:一个事务还未提交,它做的变更能被其他事务看到。
读提交是指:一个事务提交之后,它做的变更才会被其他事务看到。
可重复读是指:一个事务执行过程中看到的数据,总是跟事务在启动时看到的数据是一致的。
串行化:对同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
其中“读提交”和“可重复读”比较难以区分。用一个例子来举例。
首先初始化数据表,插入一条数据,值为一。
mysql> create table T(c int) engine=InnoDB;
insert into T(c) values(1);
有两个事务,看看在不同隔离级别下的返回结果。
如果隔离级别是“读未提交”,则v1的值是2,v2和v3也是2.
如果隔离级别是“读提交”,则v1是1,事物B的更新在提交后才能被A看到,v2,v3是2.
如果隔离级别是“可重复读”,则v1 、 v2 是 1 , v3 是 2 。之所以 V2 还是 1 ,遵循的就是这个要求:
事务在执行期间看到的数据前后必须是一致的。
如果隔离的级别是“串行化”,则在事务b执行“将1改成2”的时候,会被锁住。直到事务a提交后,事务b才可以继续执行。
在实现上,数据库会创建一个视图,访问的时候以视图的逻辑结果为准。再可重复读隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。在读提交隔离级别下,这个视图是在每个SQL语句开始执行的时候创建的,执行过程中不受其他事务的影响。注意,读未提交直接返回记录上的最新值,没有视图的概念。串行化是通过加锁来避免并行访问。
注意Oracle数据库的默认隔离级别为"读提交"。
可以使用show variables查看当前的值。
事务隔离的实现
在MySQL中,实际上每条记录在更新的时候都会记录一条回滚操作,记录上的最新值可以通过回滚操作,得到前一个状态的值。
不同时刻启动的事务会有不同的read-view.
同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC)。
同时你会发现,即使现在有另外一个事务正在将 4 改成 5 ,这个事务跟 read-view A 、 B 、 C 对应的
事务是不会冲突的。
当系统里没有比这个回滚日志更早的read-view的时候,回滚日志会被删除。
长事务意味着系统里面存在很老的事务视图。由于这些事务随时可能访问数据库的任何数据,并且这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,必然会大量占用存储空间。
事务的启动方式
1. 显式启动事务语句, begin 或 start transaction 。配套的提交语句是 commit ,回滚语句是
rollback 。默认自动提交。
2. set autocommit=0,这个命令将这个线程的自动提交关掉。意味着如果你只执行一个select语句,这个事务就启动了,而且不会自动提交。