摘要: 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列部分更新的策略。
官方博客有几篇文章介绍的非常清楚,感兴趣的可以直接跳过本文,直接阅读官方博客:
以及相关的开发worklog:
本文仅仅是笔者在理解该特性时做的一些简单的笔记,,记录的主要目的是用于以后如果涉及到相关的工作可以快速展开,因此比较凌乱
目前partial update需要通过JSON_SET, 或者JSON_REPLACE等特定接口来进行json列的更新,并且不是所有的更新都能够满足条件:
没有增加新的元素
空间足够大,可以容纳替换的新值
但类似数据长度(10 =>更新成7=>更新成9)是允许的
下面以json_set更新json列为例来看看相关的关键堆栈
检查是否支持partial update
如上所述,需要指定的json函数接口才能进行partial update
mysql_execute_command
|--> Sql_cmd_dml::execute
|--> Sql_cmd_dml::prepare
|--> Sql_cmd_update::prepare_inner
|---> prepare_partial_update
|-->Item_json_func::supports_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列,会做特殊处理:
Sql_cmd_update::update_single_table
|--> fill_record_n_invoke_before_triggers
|-->fill_record
|--> Item::save_in_field
|--> Item_func::save_possibly_as_json
|--> Item_func_json_set_replace::val_json
|--> Json_wrapper::attempt_binary_update
|--> json_binary::Value::update_in_shadow
|--> TABLE::add_binary_diff
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
创建测试表:
root@test 10:00:45>create table t (a int primary key, b json);
Query OK, 0 rows affected (0.02 sec)
root@test 10:01:06>insert into t values (1, '[ "abc", "def" ]');
Query OK, 1 row affected (0.07 sec)
json数据的存储格式如下: