事务的隔离级别
读未提交
事务中的修改,即使没有提交,其他事务也可以看得到,会导致“脏读”、“幻读”和“不可重复读取”。
读已提交
大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”,但不能避免“幻读”和“不可重复读取”。该级别适用于大多数系统。
可重复读
保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但不能避免“幻读”,但是带来了更多的性能损失。
串行化
最严格的级别,事务串行执行,资源消耗最大
脏读、不可重复读、幻读
脏读
当前事务读到的数据是别的事务想要修改成为的但是没有修改成功的数据,比如事务B执行过程中修改了数据X,在未提交前,事务A读取了X,而事务B却回滚了,这样事务A就形成了脏读。
不可重复读
当前事务先进行了一次数据读取,然后再次读取到的数据是别的事务修改成功的数据,导致两次读取到的数据不匹配,也就照应了不可重复读的语义。
幻读
幻读是指一次事务多次查询结果个数不一致,多出来的数据行叫做幻行。
高并发系统需要保证事务间的隔离性和数据本身的一致性,因此需要解决幻读问题。
InnoDB解决幻读的方法
- 多版本并发控制(MVCC,快照读)
- next-key锁(当前读)
MVCC
mvcc(Multi-Version Concurrency Control)多版本并发控制实现,通过保存数据在某个时间点的快照来实现的。一个事务无论运行多长时间,在同一个事务里能够看到数据一致的视图。根据事务开始的时间不同,同时也意味着在同一个时刻不同事务看到的相同表里的数据可能是不同的。
在每一行数据中额外保存两个隐藏的列:当前行创建时的版本号和删除时的版本号(可能为空,其实还有一列称为回滚指针,用于事务回滚,不在本文范畴)。这里的版本号并不是实际的时间值,而是系统版本号。每开始新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询每行记录的版本号进行比较。
每个事务又有自己的版本号,这样事务内执行CRUD操作时,就通过版本号的比较来达到数据版本控制的目的。
next-key
InnoDB是一个支持行锁的存储引擎,有三种排它锁(X锁):
- Record Lock:行锁,单个行记录上的锁。
- Gap Lock:间隙锁,锁定一个范围,但不包括记录本身。GAP锁的目的,是为了防止幻读、防止间隙内有新数据插入、防止已存在的数据更新为间隙内的数据。
- Next-Key Lock:1+2,锁定一个范围,并且锁定记录本身。对于行的查询,都是采用该方法,主要目的是解决幻读的问题。InnoDB默认加锁方式是next-key 锁。
next-key锁是mysql默认的锁,是记录锁和在此索引记录之前的gap上的锁的结合,这个锁的作用是为了防止幻读,导致主从复制的不一致。
比如有一个表,id列上有90,100,102。
当我们执行select * from order where id=100 for update 时,mysql会锁住90到102这个区间。当我们执行 select * from order where id>100 for update时,这时next-key锁就派上用场了。
索引扫描到了100和102这两个值,但是仅仅锁住这两个值是不够的,因为当我们在另一个会话插入id=101的时候,就有可能产生幻读了。所以mysql必须锁住[100,102)和[102,无穷大)这个范围,才能保证不会出现幻读。