事务的4个隔离级别以及对应的三种异常
读未提交(Read uncommitted)
读已提交(Read committed):脏读
可重复读(Repeatable read):不可重复读
串行化(Serializable):幻读
在MySQL中,默认的隔离级别是可重复读,可以解决脏读和不可重复读的问题,但不能解决幻读的问题。如果我们需要解决幻读的问题,就需要采用串行化的方式,也就是将隔离级别提升到最高,但这样依赖就会大幅降低数据库的事务并发能力。
而MVCC就是通过乐观锁的方式来解决不可重复读和幻读的问题,它可以在大多数情况下替代行级锁,降低系统的开销。
MySQL并发事务会引起更新丢失问题,解决办法是锁,主要分两类:
其实就如它的名字一样,非常乐观,总是假设比较好的情况。
每次取数据的时候都认为他人不会对其修改,所以不会上锁,但是会在更新的时候进行判断,看看在此期间内有没有人去更新这个数 据,可以使用版本号机制和CAS算法实现。
与乐观锁完全相反,非常悲观,总是假设非常糟糕的情况。
每次取数据的时候都认为他人会对其进行修改,所以每次拿数据的时候都会加上锁,这样别人想拿数据的话就会阻塞,除非它拿到 锁。
Multi-Version Concurrency Control 多版本并发控制,MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问;在编程语言中实现事务内存。
多版本并发控制(MVCC) 是通过保存数据在某个时间点的快照来实现并发控制的,也就是说,不管事务执行多长时间,事务内部看到的数据是不受其它事务影响的,根据事务开始的时间不同,每个事务对同一张表,同一时刻看到的数据可能是不一样的。
简单来说,多版本并发控制的思想就是保存数据的历史版本,通过对数据行的多个版本管理来实现数据库的并发控制。这样我们就可以通过比较版本号决定数据是否显示出来,读取数据的时候不需要加锁也可以保证事务的隔离级别。
可以认为多版本并发控制是行级锁的一个变种,但是它在很多情况下避免了加锁操作,因此开销耕地。虽然实现机制有所不同,但大都实现类非阻塞的读操作,写操作也只锁定必要的行。
MySQL的大多数事务性存储引擎实现的都不是简单的行级锁。基于提升并发性能的考虑,它们一般都同时实现了多版本并发控制。不仅是MySQL,包括Oracle、PostgreSQL等其它数据库系统也都实现了MVCC,但各自的实现机制不尽相同,因为MVCC没有一个统一的实现标准,典型的有乐观并发控制和悲观并发控制。
解决了在REPEATABLE READ和READ COMMITTED两个隔离级别下读同一行和写同一行的两个事务的并发。
读写直接阻塞的问题:通过 MVCC 可以让读写互相不阻塞,即读不阻塞写,写不阻塞读,这样就可以提升事务并发处理能力。
提高并发的演进思路:
降低了死锁的概率:因为 InnoDB 的 MVCC采用了乐观锁的方式,读取数据时并不需要加锁,对于写操作,也只锁定必要的行。
解决一致性读的问题:一致性读也被称为快照读,当我们查询数据库在某个时间点的快照时,只能看到这个时间点之前事务提交更新的结果,而不能看到这个时间点之后事务提交的更新结果。
举个简单的例子:
注意,这里B不论是Read Committed,还是Repeatable Read,都不会被锁,都能立刻拿到结果。这也就是MVCC存在的意义。
快照读(SnapShot Read)是一种一致性不加锁的读,是InnoDB并发如此之高的核心原因之一。
这里的一致性是指,事务读取到的数据,要么是事务开始前就已经存在的数据,要么是事务自身插入或者修改过的数据。
不加锁的简单SELECT属于快照读,例如:
select * from users where id = '1';
与快照读相对应的则是当前读,当前读就是读取最新数据,而不是历史版本的数据。加锁的SELECT就属于当前读,例如:
select * from users where id = '1' lock in share mode;
select * from users where id = '1' for update;
MVCC就是为了实现读-写冲突不加锁,而这个读指的就是快照读,而非当前读,当前读实际上是一种加锁的操作,是悲观锁的实现
当查询一条记录的时候,执行流程如下:
事务版本号:
每开启一个事务,我们都会从数据库中获取一个事务ID(也就是事务版本号),这个事务ID是自增长的,通过ID大小,我们就可以判断事务的时间顺序。
行记录的隐藏列:
InnoDB的叶子段储存了数据页,数据页中保存了行记录,而在行记录中有一些重要的隐藏字段:
Undo Log
InnoDB将行记录保存在了Undo Log,我们就可以在回滚段中找到它们,如下图所示:
从图中能看到回滚指针将数据行的所有快照记录都通过链表的结构串联了起来,每个快照的记录都保存了当时的db_trx_id,页就是那个时间点操作这个数据的事务ID。这样如果我们想找历史数据快照,就可以通过遍历回滚指针的方式进行查找。
什么是Read View,说白了Read View就是事务进行快照读操作的时候生产的读视图(Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID(当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大)
所以我们知道Read View主要是用来做可见性判断的,即当我们某个事务只想快照读的时候,对该记录创建一个Read View读视图,把它比作条件用来判断当前事务是否能够看到哪个版本的数据,即可能是当前最新的数据,也有可能是该行记录的undo log里面的的某个版本数据。
- trx_list 未提交事务ID列表,用来维护Read View生成时刻系统正活跃的事务ID
- up_limit_id 记录trx_list列表中事务ID最小的ID
- low_limit_id ReadView生成时刻系统尚未分配的下一个事务ID,也就是目前已出现过的事务ID的最大值+1
InnoDB会根据以下两个条件检查每行记录:
InnoDB未新插入的每一行保存当前系统号作为行版本号。
InnoDB为删除的每一行保存当前系统版本号作为行删除标识。
删除在内部被视为更新,行中的一个特殊位会被设置为已删除。
InnoDB为插入一行新记录,保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为行删除标识。
MySQL - MySQL InnoDB的MVCC实现机制
MySQL的多版本并发控制(MVCC)是什么?
值得收藏,揭秘 MySQL 多版本并发控制实现原理
MVCC解决了什么问题?
MVCC实现原理之ReadView(一步到位)
MVCC百度百科
脏读、不可重复读的最终解决方案——MVCC
RR有幻读问题吗?MVCC能否解决幻读?