MYSQL事务的底层原理( 五 )


MVCC 下的幻读解决和幻读现象
REPEATABLE READ 隔离级别下 MVCC 可以解决不可重复读问题,那么幻读呢?MVCC 是怎么解决的?幻读是一个事务按照某个相同条件多次读取记录时,后读取时读到了之前没有读到的记录,而这个记录来自另一个事务添加的新记录 。
可以想想,在 REPEATABLE READ 隔离级别下的事务 T1 先根据某个搜索条件读取到多条记录,然后事务 T2 插入一条符合相应搜索条件的记录并提交,然后事务 T1 再根据相同搜索条件执行查询 。结果会是什么?按照 ReadView 中的比较规则中的第 3 条和第 4 条不管事务 T2 比事务 T1 是否先开启,事务 T1 都是看不到 T2 的提交的 。
但是,在 REPEATABLE READ 隔离级别下 InnoDB 中的 MVCC 可以很大程度地避免幻读现象,而不是完全禁止幻读 。怎么回事呢?来看下面的情况:

MYSQL事务的底层原理

文章插图
?首先在事务 T1 中执行:select * from mvcc_test where id = 30; 这个时候是找不到 id = 30 的记录的 。
在事务 T2 中,执行插入语句:insert into mvcc_test values(30,'luxi','luxi');
此时回到事务 T1,执行:
updatemvcc_test setdomain='luxi_t1'whereid=30;
select*frommvcc_test whereid=30;
事务 T1 很明显出现了幻读现象 。
在 REPEATABLE READ 隔离级别下,T1 第一次执行普通的 SELECT 语句时生成了一个 ReadView,之后 T2 向 mvcc_test 表中新插入一条记录并提交 。
ReadView 并不能阻止 T1 执行 UPDATE 或者 DELETE 语句来改动这个新插入的记录,由于 T2 已经提交,因此改动该记录并不会造成阻塞,但是这样一来,这条新记录的 trx_id 隐藏列的值就变成了 T1 的事务 id 。之后 T1 再使用普通的 SELECT 语句去查询这条记录时就可以看到这条记录了,也就可以把这条记录返回给客户端 。因为这个特殊现象的存在,可以认为 MVCC 并不能完全禁止幻读 。
mvcc 总结
从上边的描述中可以看出来,所谓的 MVCC(Multi-Version Concurrency Control,多版本并发控制)指的就是在使用 READ COMMITTD、REPEATABLE READ 这两种隔离级别的事务在执行普通的 SELECT 操作时访问记录的版本链的过程,这样子可以使不同事务的读写、写读操作并发执行 , 从而提升系统性能 。
READ COMMITTD、REPEATABLE READ 这两个隔离级别的一个很大不同就是:生成 ReadView 的时机不同,READ COMMITTD 在每一次进行普通 SELECT 操作前都会生成一个 ReadView,而 REPEATABLE READ 只在第一次进行普通 SELECT 操作前生成一个 ReadView,之后的查询操作都重复使用这个 ReadView 就好了,从而基本上可以避免幻读现象 。
InnoDB 的 Buffer Pool
对于使用 InnoDB 作为存储引擎的表来说 , 不管是用于存储用户数据的索引,包括:聚簇索引和二级索引,还是各种系统数据,都是以页的形式存放在表空间中的,而所谓的表空间只不过是 InnoDB 对文件系统上一个或几个实际文件的抽象 , 也就是说数据还是存储在磁盘上的 。
但是磁盘的速度慢,所以 InnoDB 存储引擎在处理客户端的请求时,当需要访问某个页的数据时 , 就会把完整的页的数据全部加载到内存中,即使只需要访问一个页的一条记录,那也需要先把整个页的数据加载到内存中 。将整个页加载到内存中后就可以进行读写访问了,在进行完读写访问之后并不着急把该页对应的内存空间释放掉,而是将其缓存起来 , 这样将来有请求再次访问该页面时,就可以省去磁盘 IO 的开销了 。
Buffer Pool
InnoDB 为了缓存磁盘中的页,在 MySQL 服务器启动的时候就向操作系统申请了一片连续的内存,这块连续内存叫做:Buffer Pool,中文名:缓冲池 。
默认情况下 Buffer Pool 只有 128M 大小 。
查看该值:show variables like 'innodb_buffer_pool_size';
可以在启动服务器的时候配置 innodb_buffer_pool_size 参数的值,它表示 Buffer Pool 的大小,配置如下:
[server]
innodb_buffer_pool_size= 268435456
其中,268435456 的单位是字节,也就是指定 Buffer Pool 的大小为 256M,Buffer Pool 也不能太小 , 最小值为 5M,当小于该值时会自动设置成 5M 。
启动 MySQL 服务器的时候,需要完成对 Buffer Pool 的初始化过程,就是先向操作系统申请 Buffer Pool 的内存空间 , 然后把它划分成若干对控制块和缓 存页 。但是此时并没有真实的磁盘页被缓存到 Buffer Pool 中,之后随着程序的运行,会不断的有磁盘上的页被缓存到 Buffer Pool 中 。


推荐阅读