欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

MySQL中大对象的多版本并发控制详解

程序员文章站 2022-04-19 21:55:10
mysql 8.0:innodb中大对象的mvcc 在本文中,我将解释mysql innodb存储引擎中大对象(lob)设计的多版本并发控制(mvcc) 。 mysq...

mysql 8.0:innodb中大对象的mvcc

在本文中,我将解释mysql innodb存储引擎中大对象(lob)设计的多版本并发控制(mvcc) 。 mysql 8.0有一个新功能,允许用户部分更新大型对象,包括json文档 。 使用此部分更新功能,当lob部分更新时,mvcc对lob的工作方式已发生变化。 对于正常更新(完整更新),mvcc将像以前的版本一样工作。 让我们看一下mvcc在不涉及部分更新时的工作原理,然后考虑对lob进行部分更新的用例。

mvcc 常规更新

我使用术语常规更新来指代不是部分更新的更新。 我将通过一个例子解释mvcc如何用于常规更新大对象。 我将为此目的使用以下mtr(1)测试用例:

 create table t1 ( f1 int primary key , f2 longblob ) engine = innodb ; 
 insert into t1 values ( 1 , repeat ( 'a' , 65536 ) ) ; 
 
 start transaction ; 
 update t1 set f2 = repeat ( 'b' , 65536 ) where f1 = 1 ; 
 
 -- echo # connection con1: 
 -- 对于使用mysql客户端的用户,可能需要通过另开一个终端窗口建立新链接, 下同。
 connect ( con1 , localhost , root , , ) ; 
 -- echo # must see the old value 'aaaaaaaaaa' 
 select f1 , right ( f2 , 10 ) from t1 order by f1 ; 
 
 -- echo # connection default: 
 connection default ; 
 disconnect con1 ; 
 commit ; 
 
 drop table t1 ; 

为了理解下面的解释,仔细理解上述测试用例非常重要。

测试场景如下:

最初,表t1包含单个记录(r1)。
事务trx1将记录更新为新值。
当trx1仍处于活动状态时,另一个事务trx2正在读取记录。 它将读取旧值。

表t1仅包含一个记录(r1)。 但是trx1和trx2会看到两个不同的值。 该表实际上只包含最新值(trx1所见的值),而trx2看到的值或记录是从撤消日志记录中获得的。 让我们看下面的图片来更好地理解它。

初始状态:更新操作之前

下图显示了更新操作之前的情况。 撤消日志为空。 表的聚簇索引包含一行。 表中有一个lob。 聚簇索引记录包含对lob的引用。

MySQL中大对象的多版本并发控制详解

最终状态:更新操作后

现在让我们看一下更新操作后的情况。

MySQL中大对象的多版本并发控制详解

以下是一些重要的观察:

用户表空间中有两个lob - 旧的lob和新的lob。 旧的lob只能通过撤消日志访问。 聚集索引记录指向新lob。
更新操作已创建包含更新向量的撤消日志记录。 此撤消日志记录指向旧lob。
聚簇索引记录通过db_roll_ptr系统列指向撤消日志记录。 此滚动指针指向撤消日志记录,该记录可用于构建聚簇索引记录的先前版本。
撤消记录不包含lob本身。 而是它只包含对存储在用户表空间中的lob的引用。
存储在撤消日志记录中的lob引用与存储在聚簇索引记录中的lob引用不同。

事务在连接1中采取的步骤如下:

事务查看r1并确定尚未提交修改聚簇索引记录的事务。 这意味着它无法读取该记录(因为默认隔离级别是repeatable read)。
它查看r1中的db_roll_ptr并找到撤消日志记录。 使用撤消日志记录构建r1的先前版本。
它读取了这个构建的旧版r1。 请注意,此版本在聚簇索引记录中不可用。 但它使用撤消记录即时构建。
当r1指向新的lob时,这个构造的旧版本的r1指向旧的lob。 所以结果包含旧的lob。

这是lob的mvcc在不涉及部分更新时的工作方式。

mvcc部分更新

让我们看另一个例子,了解mvcc在部分更新的情况下是如何工作的。 我们需要另一个例子,因为目前仅通过函数json_set()和json_replace()支持json文档的部分更新。

 create table t2 ( f1 int primary key , j json ) engine = innodb ; 
 set @ elem_a = concat ( '"' , repeat ( 'a' , 200 ) , '"' ) ; 
 set @ elem_a_with_coma = concat ( @ elem_a , ',' ) ; 
 set @ json_doc = concat ( "[" , repeat ( @ elem_a_with_coma , 300 ) , @ elem_a , "]" ) ; 
 
 insert into t2 ( f1 , j ) values ( 1 , @ json_doc ) ; 
 
 start transaction ; 
 update t2 set j = json_set ( j , '$[200]' , repeat ( 'b' , 200 ) ) where f1 = 1 ; 
 
 -- echo # connection con1: 
 connect ( con1 , localhost , root , , ) ; 
 -- echo # must see the old value 'aaaaaaaaaa...' 
 select json_extract ( j , '$[200]' ) from t2 ; 
 
 -- echo # connection default: 
 connection default ; 
 disconnect con1 ; 
 commit ; 

该场景与前面的示例相同。 只是longblob字段已更改为json文档。 加载的数据也略有不同,以符合json格式。

提示 :您可以在上述mtr测试用例(两者中)中添加语句set debug ='+ d,innodb_lob_print' ,以在服务器日志文件中打印lob索引。 lob索引将在插入后立即打印。 lob索引将为您提供存储的lob对象的结构。
在部分更新操作之前

完全或部分更新操作之前的初始条件是相同的,并且已经在上面给出。 但是在下图中,提供了一些附加信息。

MySQL中大对象的多版本并发控制详解

让我们看看图中显示的其他信息:

存储在聚簇索引记录中的lob引用现在包含lob版本号v1。 在初始插入操作期间,将其设置为1,并在每次部分更新时递增。
每个lob数据页面在lob索引中都有一个条目。 每个条目都包含lob版本信息。 每当修改一个lob数据页时,它将被复制到具有新数据的新lob数据页中,并且将创建具有递增的lob版本号的新lob索引条目。

附加信息是lob版本号。 这在聚集索引记录中的lob引用中以及lob索引的每个条目中都可用。

部分更新操作后

下图说明了部分更新操作后的情况。

MySQL中大对象的多版本并发控制详解

这里最重要的优化是用户表空间中仍然只有一个lob。 仅更新需要修改的那些lob数据页。 部分更新操作后的这个单个lob包含旧版本和新版本的lob。 图中lob数据页面上的v1和v2标签说明了这一点。

另一个重要的观察是撤消日志和聚簇索引记录中的lob引用指向同一个lob。 但lob引用包含不同的版本号。 撤消日志记录中的lob引用包含v1(旧版本号),聚簇索引记录中的lob引用包含新版本号v2。

lob版本号的目的

如上所示,具有不同版本号的不同lob引用指向相同的lob。 单个lob包含来自不同版本的部分。 lob版本号用于获取各种lob引用指向的正确版本。 在本节中,我们将了解如何完成此操作。

lob索引包含组成lob的lob页面列表。 它包含lob数据页的页码,每个lob数据页包含的数据量以及版本号。 此列表的每个节点称为lob索引条目。 每个lob索引条目都包含旧版本的列表。 让我们看一个说明上述部分更新测试用例的结构的图。

MySQL中大对象的多版本并发控制详解

最初,在完成部分更新之前,lob索引总共包含4个条目。 四个条目的页码是5,6,7和8.没有lob索引条目具有旧版本。 所有四个条目的版本号均为1。

部分更新完成后,我们注意到页码9已替换页码7,页码7现在被视为页码9的旧版本。页码9的版本号为2,并且页码7的版本号为1。

部分更新完成后,当通过版本号为1的lob引用访问lob时,将查看第5页的第一个索引条目。 它的版本号为1.如果索引条目中的版本号小于或等于 lob引用中的版本号,则将读取该条目。 因此,将读取第5页。 然后将查看页码为6的索引条目。 它的版本号为1,因此将被读取。 然后将查看页码为9的索引条目。 它的版本号为2.但是lob引用的版本号为1.如果索引条目中的版本号大于lob引用中的版本号,则不会读取该条目。 由于页码9的条目具有版本2,因此将查看其旧版本。 将检查页码为7的索引条目。 它的版本号为1,因此将被读取。 在此之后,将检查页码为8的索引条目。 它的版本号为1,因此也将被读取。 这是访问旧版lob的方式。

部分更新完成后,当通过版本号为2的lob引用访问lob时,将查看第5页的第一个索引条目。 它的版本号为1.如果索引条目中的版本号小于或等于lob引用中的版本号,则将读取该条目。 因此它将按顺序读取页码5,6,9,8。 由于版本号始终<= 2,因此无需使用旧版本访问页码7。

需要记住的一点是lob在innodb中不是独立存在的。 它被视为聚簇索引记录的扩展。lob对事务是否可见并不由lob模块处理。 lob模块只是处理聚簇索引记录。 如果事务访问lob,则意味着它已经在聚簇索引记录中的db_trx_id的帮助下确定它可以查看lob(而不是lob的特定版本)。 所以我们不担心lob模块中的那个方面。 我们只专注于为给定的lob版本号提供正确的内容。

结论

在本文中,我们了解了如何在innodb中为大对象完成mvcc。 当对lob进行部分更新时,多个lob引用可以指向同一个lob。 但他们将拥有不同的版本号。 使用这些lob版本号,可以访问正确的lob内容。

希望您发现此信息有用。

谢谢你使用mysql!

注释:

(1) mtr即mini-transaction的缩写,字面意思小事物,相对逻辑事物而言,我们把它称作物理事物。属于innodb存储引擎的底层模块。主要用于锁和日志信息。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。