快速搞懂mvcc
- [全称 multi-version concurrency control 多版本并发控制。](#全称 multi-version concurrency control 多版本并发控制。)
- 自动开启事务
- [undo log](#undo log)
- 读视图(read_view)
- 结果过滤
- mvcc只在读已提交和可重复读隔离级别下运作
- 读已提交隔离级别下,
- 可重复读隔离级别下,
- 总的来说mvcc是为了提高数据库并发性能而设计的,它实现了读已提交。为了保证并发性能,它本身不加锁。它只在读已提交和可重复读隔离级别下工作。
- 读已提交隔离级别下,
全称 multi-version concurrency control 多版本并发控制。
自动开启事务
MySQL默认每个对数据库进行修改的SQL语句都会自动开启事务:insert into ,update,delete from 另外加锁的SQL语句也会自动开启事务:select * from table_name lock in share mode, select * from table_name where condition for update。
undo log
每个事务在修改某条记录的时候都会先将老数据备份入undo log中,然后新增一条记录,并在隐藏字段db_tx_id中记录新增这个条数据的事务id,在隐藏字段db_roll_pointer中记录刚刚备份的原数据的db_row_id(db_row_id也是隐藏字段)。这样每个事务对数据的修改并没有覆盖原有数据。事务提交后,就处于不活跃状态。
读视图(read_view)
每个事务读取数据时会创建一个读视图(read_view),read_view 包含:活跃的事务id列表,最大事务id,最小事务id,创建读视图的事务id。事务id都是自增长的,值越大,说明创建的时间越晚。
结果过滤
然后用read_view过滤查询结果:
查询结果中db_tx_id>最大事务id,说明该结果是由读视图创建后的事务创建的,应不可见,丢弃;
db_tx_id<最小事务id,说明该结果在读视图创建前就已经提交,应可见;
最小事务id<=db_tx_id<=最大事务id,db_tx_id不在活跃的事务id列表中,说明该结果已经提交,应可见,db_tx_id在活跃的事务id列表中,则说明此创建该结果的事务还没提交,该结果应不可见。
这样保证了每个并发事务都读取已提交的结果,而且整个读取过程没有解锁,并发性能得到提升。
mvcc只在读已提交和可重复读隔离级别下运作
mvcc在读已提交和可重复读模式下运行,mvcc最主要的就是实现多个并发事务读已提交的数据,所以读未提交隔离级别下允许读未提交,跟mvcc向北,mvcc是不会运作的。可序列化隔离级别下,事务都是串行的,不存在并发,所以此隔离级别下,mvcc也是不运作的。
读已提交隔离级别下,
事务每次读取都要重新创建视图,在此期间有新事务创建,也有新事务提交,所以每此创建的视图都不一样,用试图过滤出来的结果以及结果的条数也不一样,所以会出现不可重复读(读出的结果变了)和幻读(读出的结果条数变了)。
可重复读隔离级别下,
只有在刚进入事务时会创建一个视图,由于mvcc的过滤原则时只提取已提交的数据,所以随着时间的推移,进入事务之后的查询,能查出来新增的数据的事务id都是比刚进入事务时查出来的数据事务id大,新的数据不会被筛选出来
。所以筛选结果跟第一次一样。此隔离级别下可以保证可重复读,但有些新增或删除的数据可能已提交(第一次没有筛选出来的数据),而活跃的事务id没有更新,导致本该读出来的数据没有读出来,所以避免不了幻读。
总的来说mvcc是为了提高数据库并发性能而设计的,它实现了读已提交。为了保证并发性能,它本身不加锁。它只在读已提交和可重复读隔离级别下工作。
读已提交隔离级别下,
防止不可重复读:
1、将事务隔离级别设为可重复读(推荐)。
2、使用共享(读锁 select...in share mode)(不推荐)。
3、使用排他锁(写锁 select...for update)(不推荐)。
防止幻读:
1、将事务隔离级别设为可重复读,然后使用排他锁select... for update,会自动应用间隙锁(推荐)
2、使用排他锁select... for update,条件用主键筛选,会应用间隙锁(推荐)
3、应用程序自己确保更新的数据条数没有发生变化。