文章目录
- 四种隔离级别的实现
- 行锁
- MVCC实现
四种隔离级别的实现
结论:
- 对于RR和RC级别隔离,InnoDB使用MVCC+行锁实现。
- 对于Serializable,使用表锁实现。
具体实现:
- 在可重复读(RR)的隔离级别下,事务启动时得到一个事务ID,整个事务存在期间只能看到小于等于这个事务ID的版本数据。(MVCC实现)
- 在读提交(RC)的隔离级别下,每个SQL执行时,得到一个事务ID,这个SQL只能看到小于等于这个事务ID的版本数据。(MVCC实现)
- 在串行化(Serializable)的隔离级别下,使用的加表锁的方式实现,应该是悲观锁,读写冲突的时候,一个事务必须等待另一个事务的完成。因此并发性降低。(表锁实现)
- 在读未提交(RU)的隔离级别下,直接返回记录的最新值。(不用实现)
补充说明:在MVCC的多版本中,肯定是看不到其他事务未提交的数据,但是自己事务中更新的数据,可以实时看到。
行锁
InnoDB使用行锁来实现并发,多个事务可以并发修改不同的行。
当多个事务修改同一行的时候,即多个事务要获得某一行的行锁,后获得的就要等待先获得的事务释放锁。
行锁是在SQL开始执行的时候获取,并不是事务开始的时候获取。而且行锁一旦获得,是在事务结束后才释放的,并不是在SQL结束后释放。
MVCC实现
每个事务启动时,得到一个事务ID trx_id,这个值是全局分配的,而且递增。
保存多个版本: 当多个事务更新同一行记录的时候,每个事务更新的数据记录都会保留,但是事务ID不同,记录中会有一个隐藏字段记录这个事务ID。这就是多版本实现的机制。
**读取特定的版本:**假设一个事务启动,事务ID为1000,那么它只能读到事务ID<=1000的版本中最新版本数据,这叫做快照读,实现了RR和RC级别的隔离。数据多版本肯定不是保存无限多个版本,当一些事务提交后,就可以删除更早的版本了。
使用行锁更新:但是,写的时候就不一样了,由于写数据是当前读,即直接读某一行在内存或者数据库文件中数据,不读快照数据了,这里需要加行锁。这个最开始,我也不能理解,既然是MVCC实现,我写我的,你写你的,有什么关系呢?假设某个字段 money = 100,A事务对它加100,B事务也对它加100,如果不加锁,最后值可能是200。这样话,其中一个更新就丢失了。因此,写操作必须串行,而且拿到锁后,必须读某一行的书,不读副本,这就是当前读,这样保证A、B事务结束后,money的值为300。这样就存在一个现象,事务ID小的那个版本可以是当前数据的最新版本。
参见下图案例:
假设:事务隔离级别为RR,k初始值为1,事务ID分别为A 110,B 120,C 130。其中 start transaction with consistent snapshot 表示立即开启一个事务,begin/start transaction 表示执行到第一语句才开始一个事务。
结论:那么A读到的为1,B读到的为3。
分析:C更新后,B的更新会阻塞,直到C的事务提交。B会当前读数据,读出C更新的k值为2,然后更新k为3。假设有一个ID为121的事务,它读的k值应该是2。此时B更新后的k是当前最新版本的数据。