摘要: MySQL8.0对json进行了比较完善的支持, 我们知道json具有比较特殊的存储格式,通常存在多个key value键值对,对于类似更新操作通常不会更新整个json列,而是某些键值。 对于某些复杂的应用,json列的数据可能会变的非常庞大,这时候一个突出的问题是:innodb并不识别json类型,对它而言这些存储统一都是LOB类型,而在之前的版本中Innodb处理LOB更新的方式是标记删除旧记录,并插入新记录,显然这会带来一些存储上的开销(尽管Purge线程会去后台清理),而写入的redo log和Binlog的量也会偏高,对于超大列,可能会严重影响到性能。
MySQL8.0对json进行了比较完善的支持, 我们知道json具有比较特殊的存储格式,通常存在多个key value键值对,对于类似更新操作通常不会更新整个json列,而是某些键值。
对于某些复杂的应用,json列的数据可能会变的非常庞大,这时候一个突出的问题是:innodb并不识别json类型,对它而言这些存储统一都是LOB类型,而在之前的版本中Innodb处理LOB更新的方式是标记删除旧记录,并插入新记录,显然这会带来一些存储上的开销(尽管Purge线程会去后台清理),而写入的redo log和Binlog的量也会偏高,对于超大列,可能会严重影响到性能。为了解决这个问题,MySQL8.0引入了LOB列部分更新的策略。
官方博客有几篇文章介绍的非常清楚,感兴趣的可以直接跳过本文,直接阅读官方博客:
1: partial update of json values
2: introduces lob index for faster update
3: MVCC of Large Objects
以及相关的开发worklog:
WL#8963: Support for partial update of JSON in the optimizer
WL#8985: InnoDB: Refactor compressed BLOB code to facilitate partial fetch/update
WL#9141: InnoDB: Refactor uncompressed BLOB code to facilitate partial fetch/update
WL#9263: InnoDB: Enable partial access of LOB using multiple zlib streams
WL#8960: InnoDB: Partial Fetch and Update of BLOB
WL#10570: Provide logical diffs for partial update of JSON values
WL#2955: RBR replication of partial JSON updates
本文仅仅是笔者在理解该特性时做的一些简单的笔记,,记录的主要目的是用于以后如果涉及到相关的工作可以快速展开,因此比较凌乱
目前partial update需要通过JSON_SET, 或者JSON_REPLACE等特定接口来进行json列的更新,并且不是所有的更新都能够满足条件:
- 没有增加新的元素
-
空间足够大,可以容纳替换的新值
- 但类似数据长度(10 =>更新成7=>更新成9)是允许的
下面以json_set更新json列为例来看看相关的关键堆栈
检查是否支持partial update
如上所述,需要指定的json函数接口才能进行partial update
这里只是做预检查,对于json列的更新如果全部是通过json_set/replace/remove进行的,则将其标记为候选partial update的列(TABLE::mark_column_for_partial_update
), 存储在bitmap结构TABLE::m_partial_update_columns
设置partial update
入口函数:TABLE::setup_partial_update()
在满足某些条件时,需要设置logical diff(用于记录partial update列的binlog,降低binlog存储开销):
- binlog_row_value_options设置为"partial_json"
- binlog 打开
- log_bin_use_v1_row_events关闭
- 使用row format
然后创建Partial_update_info对象(Table::m_partial_update_info
), 用于存储partial update执行过程中的状态
- Table::m_enabled_logical_diff_columns
- TABLE::m_binary_diff_vectors
- TABLE::m_logical_diff_vectors
创建更新向量
当读入一行记录后,就需要根据sql语句来构建后镜像,而对于partial update所涉及的json列,会做特殊处理:
json_wrapper::attempt_binary_update
: 做必要的数据类型检查(是否符合partial update的条件)后,计算需要的空间,检查是否有足够的空闲空间Value::has_space()
来替换成新值。Value::update_in_shadow
: 进一步将变化的数据存储到binary diff对象中(TABLE::add_binary_diff
),每个Binary_diff
对象包含了要修改对象的偏移量,长度以及一个指向新数据的const指针
如下例,摘自函数Value::update_in_shadow
的注释,这里提取出来,以便于理解json binary的格式,以及如何产生Binary Diff
创建测试表:
json数据的存储格式如下:
更新json列的'abc'为'XY', 则空出一个字节出来:
此时的存储格式为:
此处只影响到一个element,因此 只有一个binary diff
再执行更新:
第二个element从3个字节更新成4个字节,显然原地没有足够的空间,但可以利用其一个element的剩余空间
这里会产生两个binary diff,一个更新offset, 一个更新数据
我们再执行一条update,将字符串修改成整数,这种情况下,原来存储字符串offset的位置被更改成了整数,而原来字符串占用的空间变成Unused状态。这里只
类型从string变成int16,使用之前offset的字段记录int值,而原来string的空间则变成空闲状态, 这里产生一个binary diff。
我们再来看看另外一个相似的函数Value::remove_in_shadow
,即通过json_remove从列上移除一个字段,以下样例同样摘自函数的注释:
json列的值为
将其中的成员$.b移除掉:
这里会产生两个binary diff,一个用于更新element个数,一个用于更新offset。
从上面的例子可以看到,每个Binary diff表示了一段连续更新的数据,有几段连续更新的数据,就有几个binary diff。 binary diff存储到TABLE::m_partial_update_info->m_binary_diff_vectors
中,
写入logical diff
logical diff 主要用于优化写binlog
新的LOB存储格式
相关代码:
storage/innobase/lob/*, 所有的类和函数定义在namesapce lob下面
从上面的分析可以看到,Server层已经提供了所有修改的偏移量,新数据长度,已经判断好了数据能够原地存储,对于innodb,则须要利用这些信息来实现partial update 。
在展开这个问题之前,我们先来看下innodb针对json列的新格式。从代码中可以看到,为了实现partial update, innodb增加了几种新的数据页格式:
我们知道,传统的LOB列通常是在聚集索引记录内留一个外部存储指针,指向lob存储的page,如果一个page存储不下,就会产生lob page链表。而新的存储格式,则引入了lob index的概念,也就是为所有的lob page建立索引,格式如下:
Note: 本文只讨论非压缩表的场景, 对于压缩表引入了更加复杂的数据类型,以后有空再在本文补上。
ref Pointer格式如下(和之前相比,增加了版本号)
BTR_EXTERN_SPACE_ID | 4 | space id |
BTR_EXTERN_PAGE_NO | 4 | 第一个 lob page的no |
BTR_EXTERN_OFFSET/BTR_EXTERN_VERSION | 4 | 新的格式记录version号 |
第一个FIL_PAGE_TYPE_LOG_FIRST页面的操作定义在 lob::first_page_t类中格式如下(参考文件: include/lob0first.h lob/lob0first.cc):
OFFSET_VERSION | 1 | 表示lob的版本号,当前为0,用于以后lob格式改变做版本区分 |
OFFSET_FLAGS | 1 | 目前只使用第一个bit,被设置时表示无法做partial update, 用于通知purge线程某个更新操作产生的老版本LOB可以被完全释放掉 |
OFFSET_LOB_VERSION | 4 | 每个lob page都有个版本号,初始为1,每次更新后递增 |
OFFSET_LAST_TRX_ID | 6 | |
OFFSET_LAST_UNDO_NO | 4 | |
OFFSET_DATA_LEN | 4 | 存储在该page上的数据长度 |
OFFSET_TRX_ID | 6 | 创建存储在该page上的事务id |
OFFSET_INDEX_LIST | 16 | 维护lob page链表 |
OFFSET_INDEX_FREE_NODES | 16 | 维护空闲节点 |
LOB_PAGE_DATA | 存储数据的起始位置,注意第一个page同时包含了lob index 和lob data,但在第一个lob page中只包含了10个lob index记录,每个lob index大小为60字节 |
除了第一个lob page外,其他所有的lob page都是通过lob index记录来指向的,lob index之间链接成链表,每个index entry指向一个lob page,
普通Lob Page的格式如下
OFFSET_VERSION | 1 | lob data version,当前为0 |
OFFSET_DATA_LEN | 4 | 数据长度 |
OFFSET_TRX_ID | 6 | 创建该lob page的事务Id |
LOB_PAGE_DATA | lob data开始的位置 |
lob index entry的大小为60字节,主要包含如下内容(include/lob0index.h lob/lob0index.cc):
OFFSET_PREV | 6 | Pointer to the previous index entry |
OFFSET_NEXT | 6 | Pointer to the next index entry |
OFFSET_VERSIONS | 16 | Pointer to the list of old versions for this index entry |
OFFSET_TRXID | 6 | The creator transaction identifier. |
OFFSET_TRXID_MODIFIER | 6 | The modifier transaction identifier |
OFFSET_TRX_UNDO_NO | 4 | the undo number of creator transaction. |
OFFSET_TRX_UNDO_NO_MODIFIER | 4 | The undo number of modifier transaction. |
OFFSET_PAGE_NO | 4 | The page number of LOB data page |
OFFSET_DATA_LEN | 4 | The amount of LOB data it contains in bytes. |
OFFSET_LOB_VERSION | 4 | The LOB version number to which this index entry belongs. |
从index entry的记录格式我们可以看到 两个关键信息:
- 对lob page的修改会产生新的lob page(“lob::replace()”) 和新的lob index entry
- lob page no及其数据长度,据此我们可以根据修改的数据在json column里的offset,通过lob index快速的定位到其所在的lob page
- 每个lob page的版本号: 为了实现mvcc多版本,用户线程先从undo log中找到对应版本的clust record,找出其中存储的版本号v1,然后在扫描lob index时,如index entry中记录的版本号<= v1,则是可见的,如果> v1, 那么就需要根据OFFSET_VERSIONS链表,找到对应版本的index entry,并
根据这个老的Index entry找到对应的lob page, 如下所示:
多版本读判断参考函数 'lob::read'
lob更新lob::update
: 根据binary diff,依次replace
Note: 不是所有的lob数据都需要partial update, 额外的lob index同样会带来存储开销,因此定义了一个threshold(ref_t::LOB_BIG_THRESHOLD_SIZE),超过2个page才去做partial update; 另外row_format也要确保lob列不存储列前缀到clust index ( ref btr_store_big_rec_extern_fields
)
写入binlog
在更新完一行后,对应的变更需要打包到线程的cache中(THD::binlog_write_row() --> pack_row()
), 这时候要对partial update进行特殊处理,需要设置特定选项:
- binlog_row_image = MINIMAL;
- binlog_row_value_options=PARTIAL_JSON
如上例第一个update产生的binlog如下:
由于存在主键,因此前镜像只记录了主键值,而后镜像也只记录了需要更新的列的内容,对于超大Json列,binlog上的开销也是极小的,考虑到binlog通常会成为性能瓶颈点,预计这一特性会带来不错的吞吐量提升
相关推荐
Prerequisites suggested for using this book might include one semester of partial differential equations and some programming capability. This book will be a good reference text for students." -- ...
partial_differential_equations (Evans).djvu
#### 标题解析:“Numerical Solution of Partial Differential Equations.pdf” 此标题表明文档主要关注于偏微分方程的数值解法,即通过数学算法来求解含有多个自变量的微分方程的方法。在物理学、工程学以及许多...
标题中的"A partial converter of Classic ASP VBScript to JScript"指的是一个转换工具,它能够将经典的ASP(Active Server Pages)中的VBScript代码部分转换为JScript。这个工具可能是为了帮助那些有大量VBScript...
Vue 2.7 的 Vite 插件@vitejs/插件-vue2警告Vue 2 已达到 EOL,并且该项目不再积极维护。注意此插件仅适用于 Vue@^2.7.0。// vite.config.jsimport vue from '@vitejs/plugin-vue2'...: Partial< Pick< SFCT
Direction of Arrival Estimation in Low-Grazing Angle: A Partial Spatial-Differencing Approach
Effect of Promoter MgO on CeO2/Al2O3 Oxygen Carrier Used in Partial Oxidation of Methane to Syngas,刘明春,王华,Promoter MgO on 10%CeO2/Al2O3 oxygen carrier was investigated for direct partial ...
- 在“管理工具”->“数据源 ODBC”中创建一个新的 DSN(数据源名称),配置 MySQL 数据库的信息。 **3. 添加引用 Microsoft.Data.Odbc.dll** 在解决方案资源管理器中添加引用 Microsoft.Data.Odbc.dll。此 DLL ...
The present handbook is written precisely, with a lot of examples illustrating the qualitative theory for all classes of PDEs. Separate parts of the book are written with a great skill thus it may be ...
### DataGridView添加自定义ColumnType 在.NET Framework中,DataGridView是一个非常强大的数据展示与编辑控件,广泛应用于Windows Forms应用程序中。为了满足不同的业务需求,我们常常需要对DataGridView进行...
Lawrence_C._Evans_Partial_Differential_Equations_Second_Edition
【C# partial关键字说明】 C#中的`partial`关键字是一个非常实用的功能,它允许开发者将一个类、结构或接口的定义分散到多个源文件中,从而提高代码组织性和可维护性。局部类型(即使用`partial`关键字声明的类型)...
- Improved : DistinctValues function now returns distinct column values irrespective of hidden rows - Improved : Shift-Enter key handling for moving to the previous cell - Improved : AppendToCSV() ...
《数值方法与偏微分方程》是一本深入探讨如何使用有限差分和有限体积方法解决偏微分方程(PDEs)的专著。作者Sandip Mazumder是俄亥俄州立大学的学者,书中内容由知名的学术出版社Elsevier出版。...
MySQL运算符MySQL创建,配置和管理在Kubernetes上运行MySQL InnoDB集群。 它不适用于MySQL NDB群集。 MySQL Operator对集群的配置方式持保留意见。 我们基于和来为在Kubernetes上运行MySQL提供完整的高可用性解决...
ZYNQ7000 Partial Reconfiguration ZYNQ7000 的 Partial Reconfiguration 是一种动态可重配技术,它允许在不中断系统运行的情况下,重新配置 FPGA 中的某些模块。这种技术可以提高系统的灵活性和可维护性。 在 ...