隔离性与隔离级别
ACID
- A:Atomicity,原子性;
- C:Consistency,一致性;
- I:Isolation,隔离性;
- D:Durability,持久性;
多个事务同时执行,可能会出现以下问题:
- 脏读(dirty read)
- 不可重复读(non-repeatable read)、
- 幻读(phantom read)
为了解决以上问题,就有了“隔离级别”的概念。
隔离级别:
- 读未提交:一个事务还没提交,它做的变更就被其他事务看到。
- 读已提交:一个事务提交之后,它做的变更才会被其他事务看到。
- 可重复读:一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。
- 串行化:对于同一行记录,“读”会加“读锁”,“写”会加“写锁”,读写锁冲突时,后一个事务必须等前一个事务执行完成,才能继续执行。
通过一个示例了解不同的隔离级别读取值的效果:
mysql> create table T(c int) engine=InnoDB;
insert into T(c) values(1);
- 读未提交:V1=2, V2=2, V3=2
- 读已提交:V1=1, V2=2, V3=2
- 可重复读:V1=1, V2=1, V3=2(事务在执行期间看到的数据必须是前后一致的,所以v1=v2=1)
- 串行化:在事务B执行“将1改成2”的时候,会被锁住,直到事务A提交后,事务B才可以继续执行。从A的角度看,V1=1, V2=1, V3=2。
【结论】
- 读未提交的隔离级别下,直接返回记录的最新值,没有视图的概念。
- 读已提交的隔离级别下,在SQL语句开始执行的时候创建视图。
- 可重复读的隔离级别下,会在事务启动的时候创建视图,整个事务期间都使用这个视图。
- 串行化的隔离级别下,直接用加锁的方式避免并行访问。
什么时候可以用到可重复读的隔离级别:数据校对
假设你在管理一个个人银行账户表。一个表存了每个月月底的余额,一个表存了账单明细。
做数据校对,也就是判断上个月的余额和当前余额的差额,是否与本月的账单明细一致。
希望在校对过程中,即使有用户发生了一笔新的交易,也不影响你的校对结果。
这时候使用“可重复读”隔离级别就很方便。事务启动时的视图可以认为是静态的,不受其他事务更新的影响。
事务隔离级别的实现
每一条记录在更新时,会同时记录一条回滚操作。
记录上的最新值,通过回滚操作,都可以得到前一个状态的值。
假设一个值从1被按照顺序改成了2、3、4,在回滚日志里会有类似下面的操作:
当前值是4,不同时刻启动的事务会有不同的read-view。如上图,视图A,B,C里面,这一记录的值分别是1,2,4。同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC),对于read-view A,要得到1,必须将当前值一次执行图中的所有回滚操作得到。
为什么避免使用长事务?
长事务意味着系统里面会存在很老的事务视图,由于这些事务随时可能访问数据库的任何数据,所以这个事务提交之前,数据库会保存可能用到的回滚记录,导致大量占用存储空间。
除了对回滚段的影响,长事务还占用锁资源,可能拖垮整个库。
事务的启动方式
- 显式启动事务语句,begin或start transaction,提交是commit,回滚时rollback。
- set autocommit=0。该命令会将这个线程事务自动提交关掉,意味着哪怕执行select语句,事务都会启动,因为设置为0,事务不会自动提交,直到主动执行commit或rollback语句,或者断开连接。
建议:set autocommit=1,通过显式的方式来启动事务。