在上一篇文章中,我们知道了可重复读的隔离级别采用 MVCC (multi-version concurrency control——多版本并发控制) 机制实现较高的隔离性,确保事务之间的隔离性和一致性。另外,MySQL 在读已提交的隔离级别下也实现了MVCC 机制。
那么什么是MVCC?又该如何实现MVCC?
MySQL 中的 MVCC(Multi-Version Concurrency Control)机制是一种并发控制机制,用于实现在数据库系统中对读写操作的并发控制,但这种并发机制不依靠加锁实现,是基于乐观锁实现的无锁并发机制。
MVCC 机制的主要思想是为每个事务生成一个单独的数据快照,这个快照包含了事务开始时数据库的所有数据。当事务对数据进行更新时,会在磁盘上创建新的数据版本,而不是直接修改原有数据,同时在事务提交时,存储引擎会为每一条数据记录将事务的版本信息记录下来。
MVCC 机制的实现主要依赖于以下三个重要的元素:
小鱼先给各位同学讲解下版本链和一致性视图(read-view),先建立一个基本认识。
- 在可重复读隔离级别下,当事务开启并**执行查询语句**时,MySQL 会生成当前事务的一致性视图,并且**在事务结束之前,该视图不会变化**。在事务中任何查询结果都需要从相应的版本链中的最新数据开始逐条与read-view 进行对比,从而得到最终的快照结果。
- 在**读已提交**隔离级别下,每次查询都会生成新的一致性视图。
一致性视图组成:由执行查询时所有未提交事务id 的数组和最大的事务id 组成,其中数组里事务 id 最小的为 min_id,最大的事务 id 为max_id。
实例数据表以 MySQL事务(一)MySQL事务隔离级别、锁机制 为例。
隔离级别默认为可重复读隔离级别。
客户端A:
begin; -- 事务开启,但未生成事务id
update mylock set name = 'a1' where id=1; -- 修改后会写入undo log,生成事务id。
-- trx_id=100
客户端B:
begin;
update mylock set name = 'b1' where id=2;
-- trx_id=200
客户端C:
begin;
update userlock set name = 'lilei10' where id=1;
-- trx_id=300
commit;
客服端D:事务D 第一查询,生成事务D 的一致性视图(read view)
begin;
select * from userlock where id=1; -- 查询不生成事务id
-- read view:[100, 200](未提交的事务id数组), 300(max_id)
-- +----+---------+---------+
-- | id | name | balance |
-- +----+---------+---------+
-- | 1 | lilei10 | 200 |
-- +----+---------+---------+
客户端E:只开启事务,暂不操作
begin;
客户端A:
update userlock set name='lilei1' where id=1;
update userlock set name='lilei2' where id=1;
客户端D:事务D 的第二次查询,可以看到一致性视图不更改。
select * from userlock where id=1;
-- read view:[100, 200](未提交的事务id数组), 300(max_id)
-- +----+---------+---------+
-- | id | name | balance |
-- +----+---------+---------+
-- | 1 | lilei10 | 200 |
-- +----+---------+---------+
客户端A:
commit;
客户端B:
update userlock set name='lilei3' where id=1;
update userlock set name='lilei4' where id=1;
客户端D:事务 D 的第三次查询,同样可以看到一致性视图不更改。
select * from userlock where id=1;
-- read view:[100, 200](未提交的事务id数组), 300(max_id)
-- +----+---------+---------+
-- | id | name | balance |
-- +----+---------+---------+
-- | 1 | lilei10 | 200 |
-- +----+---------+---------+
客户端B:
commit;
客户端E:事务 E 第一次查询,生成事务E 的一致性视图(read view)
select * from userlock where id=1;
-- read view:[](非提交的事务id数组), 300(max_id)
-- +----+--------+---------+
-- | id | name | balance |
-- +----+--------+---------+
-- | 1 | lilei4 | 200 |
-- +----+--------+---------+
版本链实例如下图所示:
版本链对比的规则:
对于删除数据的情况,我们可以将删除语句看出特殊的修改(update),同样会复制一份版本链的最新数据,并将trx_id 修改为删除操作的trx_id,同时需要在本条记录的头信息(record header)中将删除标志(deleted_flag)设置为true,表示当前记录已经被删除。在查询时也是按照上述规则进行,只是如果头信息中的删除标志(deleted_id)为true 时,不返回数据。
特别注意:
begin
和 start transaction
命令执行后,trx_id不会立即生成,在执行第一个修改操作时,事务才真正开始,此时生成trx_id,trx_id 由mysql 按照事务的启动顺序分配。