数据一致性与 MVCC 理解
事务隔离级别
传统关系型数据库(事务型数据库),也就是所谓的 OLTP 数据库,很重要的一点就是解决多事务并发时的一致性问题。
多事务并发时主要可能出现一下问题:
- 脏读:读到不存在的数据(也就是读到了未提交的数据)。例:事务A执行了一条修改语句,将用户张三的性别修改为女;此时,事务B恰好正在执行一条查询语句,结果就查询到了事务A还未提交的数据,及张三的性别为女;但事务A之后回滚了该操作,所以这次修改并不应当生效。但是事务B却认为这是一条正确的数据,这就造成了脏读(读到了脏数据)
- 不可重复读:一次事务期间,对同一条数据的查询结果不一致。例:事务A执行期间,第一次查询张三得到张三是男性;结果期间事务B对张三的性别进行了修改,并成功提交;当事务A再一次查询张三性别时,发现和第一次读到的不一致。这就是不可重复读。
- 幻读:幻读与不可重复读主要区别就是,前者是基于全表的,后者是基于行的。例:事务A第一次查询到整个用户表有100位男性,期间事务B删除了1位女性用户,结果事务A再次查询时发现变成99位女性了。
正对上面3种情况,MySQL 的解决方案就是选择合适的隔离级别,从上到下隔离级别依次升高:
- 读已提交:即事务只能查询到其它事务已提交的数据。当事务在查询时它会加一个锁,读完之后会立即释放(区别于可重复读,读已提交是在一次事务的一次读操作之后立即释放锁,而读已提交是整个事务期间都不释放)
- 可重复读:在事务读时加行锁,期间其它事务只可读不可写
- 串行化:简单理解所谓串行就是指把所有事务一条条排好序,一个一个来。串行化会对整张表加一个表锁,这样可以保证一个事务在读的时候整张表都不会被修改,从而保证数据的绝对一致性。
可以理解,事务隔离级别越高,那么付出的代价也是最大的,由于锁的原因,整个数据库的吞吐量和响应速度会受到严重影响。而且这种传统锁的实现方式有一个很大的问题就是读写冲突(即读和写都要加锁,读的时候不能写,写的时候不能读)
MVCC
那么,MVCC 机制又是什么呢,我理解它正式为了提高OLTP数据库并发读写的一种机制。下面主要讲两个概念:
1、版本链
所谓版本链就是对一行数据,记录多个事务操作版本(增删改),然后将这些记录根据发生的先后顺序串起来形成一个“链条”,保存在 undolog 中。
InnoDB 引擎会给表的每行记录添加3个隐藏字段:
- DB_TRX_ID:代表一次事务操作的事务ID
- DB_ROLL_PTR:回滚指针。指向上一个事务操作记录(即上一个版本)
- DB_ROW_ID:如果当前的表没有主键,就会使用这个字段来当作唯一ID,因为生成聚簇索引时必须要有唯一ID
2、ReadView
ReadView 就是一次查询结果记录。当每次事务进行查询时都会创建一个 ReadView ,保存此次查询的结果。
ReadView 只是对下面这两个隔离级别的实现:
- 读已提交:一个事务第一次读某行记录时,创建一个 ReadView 保存结果1。第二次读,依然创建一个 ReadView 保存结果2。这样的好处就是即使是写操作,也不再需要加共享锁了,也就是读的时候可以有其它事务执行写操作。
- 可重复读:一个事务第一次读某行记录时,创建一个 ReadView,后续这个事务执行期间所有对改行记录的读取都会去读该ReadView,从而保证每次读取结果一致。
总结
MVCC 是 MySQL 一个非常重要的并发优化。记得当初在学习的时候痛苦的不行,死活理解不了,今天抽时间好好看了一下,好像也不过如此。