一想到并发控制,很多人的第一反应就是加锁。确实,锁定确实是并发问题最常见的解决方案。然而,其实除了锁之外,在数据库领域,还有另一种无锁方案来实现并发控制,那就是大名鼎鼎的MVCC。MVCC是多版本并发控制的缩写。翻译过来就是多版本并发控制,也是一种并发控制方案。我们知道,在数据库中,对数据的操作主要有两种,即读和写,而在并发场景下,可能会出现以下三种情况:read-readconcurrentreadwriteconcurrentwrite-writeconcurrent我们都知道有不写就发送读写是没有问题的,写并发更多的是通过加锁来实现。那么读写并发可以通过MVCC机制来解决。本文将介绍MVCC在MySQL中的实现机制。Snapshotread和currentread如果想了解MVCC的机制,最重要的概念就是snapshotread。所谓snapshotread就是读取快照数据,也就是快照生成那一刻的数据,像我们经常使用的普通SELECT语句,就是不带锁的snapshotread。例如:SELECT*FROMxx_tableWHERE...快照读取对应的另一个概念叫做当前读取。当前读数是读取最新的数据。因此,锁定SELECT,或者增删改查数据都会执行当前读取,如:SELECT*FROMxx_tableLOCKINSHAREMODE;SELECT*FROMxx_tableFORUPDATE;INSERTINTOxx_table...DELETEFROMxx_table...UPDATExx_table...可以说快照读取是MVCC实现的基础,而当前读取是悲观锁实现的基础。那么,快照读取的快照是从哪里读取的呢?换句话说,快照存在于何处?UndoLogundo日志是Mysql中比较重要的事务日志之一。undolog顾名思义就是用来回滚的日志。在事务提交之前,MySQL会先将更新前的数据记录到undolog日志文件中。当事务回滚或者数据库崩溃时,可以使用undolog来回滚。这里所说的undolog中的“更新前的数据”就是我们前面提到的快照。所以,这也是为什么很多人说UndoLog是MVCC实现的重要手段。那么,一个记录可能有多个事务同时执行。那么,undolog就会有一条记录的多个快照。那么此时需要读取一个SELECT时,应该读取哪个snapshot呢?这需要使用其他几条信息。隐式字段其实在数据库的每一行中,除了保存一些我们自己定义的字段外,还有一些重要的隐式字段:db_row_id:隐藏主键,如果我们不为这张表创建主键,那么它使用此字段创建聚簇索引。db_trx_id:对该记录进行最新修改的事务的ID。db_roll_ptr:回滚指针,指向这条记录的前一个版本。其实就是指向UndoLog中上一版本快照的地址。因为在每条记录发生变化之前,undolog中都会存储一个快照,那么这些隐式字段也会随着记录一起保存在undolog中。这样,每个快照都有一个db_trx_id字段记录本次变化的事务ID,还有一个db_roll_ptr字段指向上一个快照的地址。(db_trx_id和db_roll_ptr是重点,后面会用到)这样就形成了一个snapshotlist:有了undolog和几个隐含字段,我们好像还是不知道应该读哪个snapshot,那怎么办该怎么办?这时候ReadView就需要ReadView登场了。ReadView主要是帮助我们解决可见性问题,即它会告诉我们在这个事务中应该看到哪个快照,不应该看到哪个快照。ReadView中有几个重要的属性:trx_ids,系统当前未提交的事务ID列表。low_limit_id,未提交事务中最大的事务ID。up_limit_id,未提交事务中最小的事务ID。creator_trx_id,创建这个ReadView的交易ID。每次我们开始一个事务,我们都会从数据库中获取一个事务ID。这个交易ID是自增的。通过ID的大小,我们可以判断交易发生的时间顺序。那么,如何判断一个事务应该看到哪些快照,哪些快照不应该看到呢?其实原理比较简单,就是交易ID大的交易应该能看到交易ID小的交易的变化结果,反之亦然!例如:如果当前有事务3要对某条记录进行快照读取,他会先创建一个ReadView,记录当前所有未提交事务的信息。比如up_limit_id=2,low_limit_id=5,trx_ids=[2,4,5],creator_trx_id=6我们之前说过,每条记录都有一个隐含的字段db_trx_id来记录最近修改这条记录ID的交易,比如db_trx_id=3;那么接下来,数据库会将这条记录db_trx_id的可见性与ReadView进行比较。如果db_trx_id
