什么是MVCC:
全称是多版本并发控制.他是为了提升并发能力的技术,解决了数据库操作是的读写和写读问题,注意他不能解决写写问题。如果是写写问题就还是互斥的形式。
在MVCC内部是给予InnoDB存储引擎的undo log 记录的信息实现的,每个事务读到的数据的版本可能是不一样的。在这个过程中,事务只能是看到自己创建事务时酷照的数据,和事务本身操作的数据。
MVCC 只是在RC(读已提交)和RR(读未提交)的隔离级别时使用
MVCC借助于三点来工作的,分别是:隐藏字段,undo log(注意这个日志文件本身是为了在事物的操作时,保证事务的原子性,所以他是在事务提交前就写入,只是和Redo log不同的点,在这里点出一下。),Read view (这个其实就是快照)
详细讲解隐藏字段:
InnoDB为每个数据库的表中添加了三个隐藏字段:
-
DB_TRX_ID :标识最近一次对当前数据进行修改的事务ID,这里面官网特意提出delete操作属于updata(注意概念)。
-
DB_ROLL_PTR :回滚指针,undo中记录多个版本之间的关系,使用指针连接
-
DB_ROW_ID:如果表里没有主键,也没有非空唯一索引,这个隐藏字段就会作为索引存在。
undo log中存储的数据的结构:
其实就是存储的行的数据:(展示的是undo log 的内容)
借助例子开始展示 现在有一个表数据,里面的数据是 id and name
假设这是第一个事务,所以没有回滚指针..............
假设现在第二个事务将id=1的name设置为李四,展示现在undo log的变化..............
之后可以按着这个思路在尝试自己画一下将李四变为王五,如果画完了,可以在评论区发出来奥!
Read View(快照):
再MVCC下维护了几个变量,依靠变量来验证当前事务是否可以查看undo log 中其他的事务的数据:
-
m_creator_trx_id: 当前事务的TRX_ID,也就是事务ID
-
人话:当前事务的ID,当前事务要创建这个Read View快照。
-
-
m_ids: 创建快照时,处于活跃事务的ID集合。
-
人话:未提交事务的事务的集合。因为事务没提交,所以这里的数据是不可见的。并且活跃事务列表不会记录当前事务。
-
-
m_low_limit_id: 读取时不应看到任何trx id>=此值的事务。换句话说,这是“高水位线”
-
人话:当前行数据的事务ID,大于等于m_low_limit_id,数据是不可见的。说白了,当前事务在创建Read View快照时,这个事务他还没开始呢,他的数据必然是不可见的。通过查看m_low_limit_id的赋值,可以得知他是还未被分配的事务的最小事务ID ,其实就是最大活跃事务 + 1。
-
-
m_up_limit_id: 读取应该看到所有严格小于(<)此值的trx id。换句话说,这是低水位线”
-
人话: 他是活跃事务列表中的最小事务ID ,比这个事务ID还要小的值,他事务必然已经提交了,所以如果当前行数据的事务ID 小于 m_up_limit_id,我是可见的。如果活跃事务列表为空,m_up_limit_id = m_low_limit_id。
-
上图中: 首先:我们的当前事务id :12 活跃事务id: 14 15 高水位线:16 滴水位线:14 //如果是13 那就是可见,其他的就是不可见.....
源码展示判断逻辑:
// id参数,是你想查看的那行数据的事务ID
bool changes_visible(
trx_id_t id, const table_name_t& name) const MY_ATTRIBUTE((warn_unused_result)){
ut_ad(id > 0);
// m_up_limit_id 是活跃事务的最小id,如果当前行的事务ID,小于m_up_limit_id,说明这个事务必然已经提交了,这个数据是可见的。
// 如果当前行的事务ID和当前创建ReadView的事务ID相等,说明就是当前事务修改的数据,必然可见。
if (id < m_up_limit_id || id == m_creator_trx_id) {
return(true);
}
check_trx_id_sanity(id, name);
// 当前行的事务ID,大于了m_low_limit_id,必然不可见。创建Read View的时候,m_low_limit_id这个事务还没有呢。
if (id >= m_low_limit_id) {
return(false);
// 没有活跃事务,并且当前行数据的事务ID,还小于m_low_limit_id,那这个数据必然可见。
} else if (m_ids.empty()) {
return(true);
}
const ids_t::value_type* p = m_ids.data();
// 如果上述情况都不满足,无法判断可见还是不可见,此时需要拿着当前行的事务ID,以及活跃事务列表开始判断。
// 1、如果我发现当前行的事务ID,在活跃事务列表中。此时在Read View来说,这个事务没提交,不可见。
// 2、如果我发现当前行的事务ID,不在活跃事务列表中,说明创建Read View时候,你就提交了,可见。
return(!std::binary_search(p, p + m_ids.size(), id));
}
这里面需要注意:
如果是RC隔离级别:事务每次执行select 都会创建快照
对于现在的这个情况,隔离级别是读已提交:
第一次创建快照: 我们的当前事务id :12 活跃事务id: 14 15 高水位线:16 低水位线:14 //如果是13 那就是可见,其他的就是不可见.....‘ 第二次创建快照: 我们的当前事务id :12 活跃事务id: 15 高水位线:16 低水位线:15 //如果是13 ,14 那就是可见,其他的就是不可见.....‘
如果是RR隔离级别:只有第一次执行时会创建快照。
但是如果时RR,他只是会在第一次的查询时创建快照,所以至始至终都是:
我们的当前事务id :12 活跃事务id: 14 15 高水位线:16 低水位线:14 //如果是13 那就是可见,其他的就是不可见.....‘