简介
MVCC(Multi-Version Concurrency Control)是一种多版本并发控制机制,主要用于数据库管理系统中,实现对数据库的并发访问。在编程语言中,MVCC可以实现事务内存。
MVCC的特点是读不加锁,读写不冲突。MVCC将读操作分为快照读和当前读。快照读读取的是记录的可见版本,不需要加锁。当前读读取的是记录的最新版本,并对当前读返回的记录加锁,以保证其他事务不会并发修改该记录。快照读一般用于简单的SELECT操作,当前读一般用于SELECT * FROM table WHERE lock in share mode(允许不同事务之间共享加锁读取S锁,不允许其他事务修改或者加排他锁)、SELECT * FROM table WHERE for update(排他锁X锁)、INSERT INTO table VALUES(插入操作)、UPDATE table SET WHERE(更新操作)、DELETE FROM table WHERE(删除操作)等特殊读操作。
为什么需要MVCC呢?数据库通常使用锁来实现隔离性。最原生的锁,锁住一个资源后会禁止其他任何线程访问同一个资源。但是很多应用的一个特点都是读多写少的场景,很多数据的读取次数远大于修改的次数,而读取数据间互相排斥显得不是很必要。所以就使用了一种读写锁的方法,读锁和读锁之间不互斥,而写锁和写锁、读锁都互斥。这样就很大提升了系统的并发能力。之后人们发现并发读还是不够,又提出了能不能让读写之间也不冲突的方法,就是读取数据时通过一种类似快照的方式将数据保存下来,这样读锁就和写锁不冲突了,不同的事务session会看到自己特定版本的数据。当然快照是一种概念模型,不同的数据库可能用不同的方式来实现这种功能。
补充
- 读锁:也叫共享锁、S锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S 锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
- 写锁:又称排他锁、X锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。
- 表锁:操作对象是数据表。Mysql大多数锁策略都支持,是系统开销最低但并发性最低的一个锁策略。事务t对整个表加读锁,则其他事务可读不可写,若加写锁,则其他事务增删改都不行。
- 行级锁:操作对象是数据表中的一行。是MVCC技术用的比较多的。行级锁对系统开销较大,但处理高并发较好。
MVCC工作过程
InnoDB的MVCC,是通过在每行纪录后面保存两个隐藏的列来实现的。这两个列,一个保存了行的创建版本号,一个保存了行的删除版本号。每开始一个新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行纪录的版本号进行比较。在REPEATABLE READ隔离级别下,MVCC具体的操作如下:
undo log 在不考虑redo log 的情况下利用undo log工作的简化过程为:
1)为了保证数据的持久性数据要在事务提交之前持久化
2)undo log的持久化必须在在数据持久化之前,这样才能保证系统崩溃时,可以用undo log来回滚事务
Innodb中的隐藏列
Innodb通过undo log保存了已更改行的旧版本的信息的快照。 InnoDB的内部实现中为每一行数据增加了三个隐藏列用于实现MVCC。
SELECT
InnoDB会根据以下两个条件检查每行纪录:
-
InnoDB只查找版本早于当前事务版本的数据行,即,行的系统版本号小于或等于事务的系统版本号,这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的。
-
行的删除版本,要么未定义,要么大于当前事务版本号。这样可以确保事务读取到的行,在事务开始之前未被删除。
只有符合上述两个条件的纪录,才能作为查询结果返回。
INSERT
InnoDB为插入的每一行保存当前系统版本号作为行版本号。
DELETE
InnoDB为删除的每一行保存当前系统版本号作为行删除标识。
UPDATE
InnoDB为插入一行新纪录,保存当前系统版本号作为行版本号,同时,保存当前系统版本号到原来的行作为行删除标识。
补充:
- bin log:是mysql服务层产生的日志,常用来进行数据恢复、数据库复制,常见的mysql主从架构,就是采用slave同步master的binlog实现的, 另外通过解析binlog能够实现mysql到其他数据源(如ElasticSearch)的数据复制。
- redo log:记录了数据操作在物理层面的修改,mysql中使用了大量缓存,缓存存在于内存中,修改操作时会直接修改内存,而不是立刻修改磁盘,当内存和磁盘的数据不一致时,称内存中的数据为脏页(dirty page)。为了保证数据的安全性,事务进行中时会不断的产生redo log,在事务提交时进行一次flush操作,保存到磁盘中, redo log是按照顺序写入的,磁盘的顺序读写的速度远大于随机读写。当数据库或主机失效重启时,会根据redo log进行数据的恢复,如果redo log中有事务提交,则进行事务提交修改数据。这样实现了事务的原子性、一致性和持久性。
- undo log: 除了记录redo log外,当进行数据修改时还会记录undo log,undo log用于数据的撤回操作,它记录了修改的反向操作,比如,插入对应删除,修改对应修改为原来的数据,通过undo log可以实现事务回滚,并且可以根据undo log回溯到某个特定的版本的数据,实现MVCC。除了实现MVCC外,还用于事务的回滚。MySQL Innodb中存在多种日志,除了错误日志、查询日志外,还有很多和数据持久性、一致性有关的日志。
MVCC优缺点
MVCC在大多数情况下代替了行锁,实现了对读的非阻塞,读不加锁,读写不冲突。缺点是每行记录都需要额外的存储空间,需要做更多的行维护和检查工作。
补充:
1.MVCC手段只适用于Msyql隔离级别中的读已提交(Read committed)和可重复读(Repeatable Read)。
2.Read uncimmitted由于存在脏读,即能读到未提交事务的数据行,所以不适用MVCC.
原因是MVCC的创建版本和删除版本只要在事务提交后才会产生。
3.串行化由于是会对所涉及到的表加锁,并非行锁,自然也就不存在行的版本控制问题。