MySQL中大对象的多版本并发控制详解
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的引用。
最终状态:更新操作后
现在让我们看一下更新操作后的情况。
以下是一些重要的观察:
用户表空间中有两个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对象的结构。
在部分更新操作之前
完全或部分更新操作之前的初始条件是相同的,并且已经在上面给出。 但是在下图中,提供了一些附加信息。
让我们看看图中显示的其他信息:
存储在聚簇索引记录中的lob引用现在包含lob版本号v1。 在初始插入操作期间,将其设置为1,并在每次部分更新时递增。
每个lob数据页面在lob索引中都有一个条目。 每个条目都包含lob版本信息。 每当修改一个lob数据页时,它将被复制到具有新数据的新lob数据页中,并且将创建具有递增的lob版本号的新lob索引条目。
附加信息是lob版本号。 这在聚集索引记录中的lob引用中以及lob索引的每个条目中都可用。
部分更新操作后
下图说明了部分更新操作后的情况。
这里最重要的优化是用户表空间中仍然只有一个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索引条目都包含旧版本的列表。 让我们看一个说明上述部分更新测试用例的结构的图。
最初,在完成部分更新之前,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存储引擎的底层模块。主要用于锁和日志信息。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。