MySQL 从 5.7 版本开始支持 Generated Column, 并在最近的 8.0 版本中支持了 Functional index, 以及 default 值支持表达式, 这几个特性都通过创建使用表达式进行描述的列来实现的. 笔者之前满好奇这些表达式信息都是怎么存储的, 本文主要记录了涉及到的相关函数, 主要是做个笔记, 不会深入解读.
本文以 Generated Column 为例进行描述, 代码基于 8.0.15
使用
我们创建一个简单的表, 表上包含两种类型的 generated column: 物理存储和虚拟列; 并在虚拟列上创建索引
- root@information_schema 05:38:49>show create table test.t1\G
- *************************** 1. row ***************************
- Table: t1
- Create Table: CREATE TABLE `t1` (
- `a` int(11) NOT NULL,
- `b` int(11) DEFAULT NULL,
- `c` int(11) DEFAULT NULL,
- `v1` int(11) GENERATED ALWAYS AS ((`a` + `b`)) VIRTUAL,
- `g1` int(11) GENERATED ALWAYS AS ((`a` * `v1`)) STORED,
- PRIMARY KEY (`a`),
- KEY `v1` (`v1`)
- ) ENGINE=InnoDB DEFAULT CHARSET=latin1
- 1 row in set (0.00 sec)
- root@information_schema 05:40:58>SELECT * FROM INNODB_VIRTUAL WHERE table_id = (SELECT TABLE_ID FROM INNODB_TABLES WHERE NAME LIKE 'test/t1')\G
- *************************** 1. row ***************************
- TABLE_ID: 1354
- POS: 65539
- BASE_POS: 0
- *************************** 2. row ***************************
- TABLE_ID: 1354
- POS: 65539
- BASE_POS: 1
- 2 rows in set (0.00 sec)
- root@information_schema 05:41:04>
POS 值实际上是一个 encode 的值, 所以看起来很大, 他包含了 virtual column 的序列和在所有列上的序列:
- ((nth virtual generated column for the InnoDB instance + 1) <<16)
- + the ordinal position of the virtual generated column
如上例, column v1, (0 + 1) << 16 + 3 = 65539
Generated column 的表达式信息可以通过 i_s 表来查询:
- root@information_schema 06:15:09>SELECT COLUMN_NAME, ORDINAL_POSITION,COLUMN_TYPE,EXTRA, GENERATION_EXPRESSION FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME LIKE 't1';
- +-------------+------------------+-------------+-------------------+-----------------------+
- | COLUMN_NAME | ORDINAL_POSITION | COLUMN_TYPE | EXTRA | GENERATION_EXPRESSION |
- +-------------+------------------+-------------+-------------------+-----------------------+
- | a | 1 | int(11) | | |
- | b | 2 | int(11) | | |
- | c | 3 | int(11) | | |
- | g1 | 5 | int(11) | STORED GENERATED | (`a` * `v1`) |
- | v1 | 4 | int(11) | VIRTUAL GENERATED | (`a` + `b`) |
- +-------------+------------------+-------------+-------------------+-----------------------+
- 5 rows in set (0.00 sec)
相关代码
存储表达式
和其他元数据信息一样, 表达式也以字符串的形式存储到 MySQL 库下面的 columns 表中, 注意这个表是隐藏的, 你只能通过 information_schame.columns 来查询.
系统掉定义在文件定义在文件 sql/dd/impl/tables/columns.h 中
ref: dd::Column_impl::store_attributes
读取表达式
通过 dd 接口 (dd::Column_impl::restore_attributes), 存储于系统表的表达式字符串被读取出来, 并被存储到 TABLE_SHARE 的 field 成员的 gcol_info 中, 类型为类型为 Value_generator, 字符串存储于类型为 Value_generator::expr_str 中
ref: fill_column_from_dd
当会话打开自己的 TABLE 对象时, 会基于上述的字符串信息构建 item 树, 存储于自己的 Value_generator 的 item 树中.
ref: open_table_from_share --> unpack_value_generator
show create table 时, 通过 TABLE 对象, 从 generated column 列的 gcol_info 中中构建出表达式信息
ref: store_create_info()
读和更新表达式
当 generated column 需要被更新时 (TABLE::is_field_used_by_generated_columns), 或者产生新的插入时, 需要计算其结果值
ref: update_generated_write_fields()
当读取列时, 如果 virtual generated column, 需要去计算其真正的值. 当然如果 virtual column 上创建了 innodb 索引, 实际上其值是被存储到物理索引上的, 那么就无需去计算列值
ref: update_generated_read_fields()
InnoDB 内计算表达式
当 InnoDB 选择使用 virtual column 上的索引来进行查询时, 如果需要读取之前的版本, 需要 sec record 和 clust record 检查是否匹配时 (row_sel_sec_rec_is_for_clust_rec), 也需要基于 clust record, 根据表达式去构建出 virtual column 的值, 这时候就需要去回调 server 层的计算函数, 因为 clust record 中并不存在 virtual column 的值, 相应堆栈:
- row_search_mvcc
- |--> Row_sel_get_clust_rec_for_mysql::operator()
- |--> row_sel_sec_rec_is_for_clust_rec
- |--> innobase_get_computed_value
- |--> handler::my_eval_gcolumn_expr
参考文档:
来源: https://yq.aliyun.com/articles/696916