MVCC并发控制【1】-MySQL

一、什么是MVCC?

MVCC,全称Multi-Version Concurrency Control,即多版本并发控制。MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。

MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读

《高性能MySQL》书中对MVCC的部分介绍

  • MySQL的大多数事务型存储引擎实现的其实都不是简单的行级锁。基于提升并发性能的考虑, 它们一般都同时实现了多版本并发控制(MVCC)。不仅是MySQL, 包括Oracle,PostgreSQL等其他数据库系统也都实现了MVCC, 但各自的实现机制不尽相同, 因为MVCC没有一个统一的实现标准。
  • 可以认为MVCC是行级锁的一个变种, 但是它在很多情况下避免了加锁操作, 因此开销更低。虽然实现机制有所不同, 但大都实现了非阻塞的读操作,写操作也只锁定必要的行。
  • MVCC的实现方式有多种, 典型的有乐观(optimistic)并发控制 和 悲观(pessimistic)并发控制。
  • MVCC只在 READ COMMITTED (读已提交)和 REPEATABLE READ(可重复读) 这两个隔离级别下工作。其他两个隔离级别够和MVCC不兼容, 因为 READ UNCOMMITTED(读未提交) 总是读取最新的数据行, 而不是符合当前事务版本的数据行。而 SERIALIZABLE(可串行化) 则会对所有读取的行都加锁。

二、涉及到的基本概念

当前读

        像select lock in share mode(共享锁), select for update ; update, insert ,delete(排他锁)这些操作都是一种当前读。

        为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁

快照读

        像不加锁的select操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;

        之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本。

        说白了MVCC就是为了实现读-写冲突不加锁,而这个读指的就是快照读, 而非当前读,当前读实际上是一种加锁的操作,是悲观锁的实现。

三、MVCC能解决什么问题?

在解决问题前,我们先看看数据库并发场景存在的问题有哪些?

  • 读-读:不存在任何问题,也不需要并发控制
  • 读-写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,不可重复读,幻读
  • 写-写:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失

MVCC带来的好处是?

多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。 所以MVCC可以为数据库解决以下问题

  • 在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能。
  • 同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题

四、MVCC的实现原理

MVCC是通过保存数据在某个时间点的快照来实现的。不同存储引擎的MVCC实现是不同的,典型的有乐观并发控制和悲观并发控制。

当我们创建表完成后,mysql会自动为每个表添加几个版本号,其中事务id由mysql数据库自动生成,且递增。

版本号意义
db_trx_id:数据版本号最后更新数据的事务id
db_roll_pt :删除版本号数据删除的事务id

下面通过具体的用户表来介绍MVCC的运行机制,对应在数据中的表如下(后面两列是隐藏列,我们通过查询语句并看不到)

idnamesexdb_trx_iddb_roll_pt

4.1 MVCC逻辑流程-插入(insert)

idnamesexdb_trx_iddb_roll_pt
1张三1null
2李四1null

        同一事务中(假设事务id=1)向用户表插入两条记录,记录的数据版本号为事务id=1,删除版本号为null。

start transaction;(事务id为1)
INSERT INTO user (name,sex) VALUES ('张三','男');
INSERT INTO user (name,sex) VALUES ('李四','男');
commit;

4.2 MVCC逻辑流程-查询(select)

  查询时需要同时满足以下两个条件
  1、查找数据版本号,早于(小于等于)当前事务id的数据行。 这样可以确保事务读取的数据是事务之前已经存在的。或者是当前事务插入或修改的。


  2、查找删除版本号为null 或者大于当前事务版本号的记录。 这样确保取出来的数据在当前事务开启之前没有被删除。

  如下图,假如有一个事务中执行查询(假设事务id=2)

start transaction;(事务id为2)
select * from user where sex = '男';  --(1)
select * from user where sex = '男';  --(2)
commit;

        假设在执行这个事务ID为2的过程中,刚执行到(1),这时有另一个事务(假设事务id=3)往这个表里插入了一条数据; 

start transaction;(事务id为3)
INSERT INTO user (name,sex) VALUES ('王五','男');
commit;

        此时的表中数据如下:

idnamesexdb_trx_iddb_roll_pt
1张三1null
2李四1null
3王五3null

        然后id=2的事务接着执行第(2)步骤中的查询语句,而InnoDB只会查找事务ID小于等于当前事务ID的数据行,所以 id=3 的数据行并不会在执行事务 id=2 中的 (2) 被检索出来。检索结果:

idnamesexdb_trx_iddb_roll_pt
1张三1null
2李四1null

4.3 MVCC逻辑流程-删除(delete)

        假如有一个事务中执行查询(假设事务id=4)

start transaction;(事务id为4)
select * from user where sex = '男';  --(1)
select * from user where sex = '男';  --(2)
commit;

        假设事务 id=4 刚执行到(1),此时有另外一个事务 id=5 执行了删除语句,会更新数据的删除版本号为当前事务id = 5

start transaction;(事务id为5)
DELETE FROM user WHERE id = 1;
commit;

        此时表中数据如下:

idnamesexdb_trx_iddb_roll_pt
1张三15
2李四1null
3王五3null

        接着事务id=4的接着执行步骤(2)中的查询,根据SELECT 检索条件可以知道,它会检索创建时间(创建事务的ID)小于当前事务ID的行和删除时间(删除事务的ID)大于当前事务的行,表中id=1的行由于删除时间(删除事务的ID)大于当前事务的ID,所以事务 id=4 的(2)在执行的时候也会把表中 id=1 的数据检索出来,所以事务4中的两条select 语句检索出来的数据都如下:

idnamesexdb_trx_iddb_roll_pt
1张三15
2李四1null
3王五3null

4.4 MVCC逻辑流程-修改(update)

        可以理解为,当一个事务中修改一条记录时, 是先复制该数据,新数据数据版本号为当前事务id,删除版本号为 null 。然后更新原来数据的删除版本号为当前事务id。如下:

  假如一个事务 id=6 执行了一条update语句

start transaction;(事务id为6)
UPDATE user SET name='李四1' WHERE id = 2
commit;

        执行结果如下:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值