MySQL虚拟列的详细讲解

本文介绍了MySQL 5.7中引入的虚拟列特性,包括VirtualGeneratedColumn和StoredGeneratedColumn的区别及应用场景。通过具体示例展示了虚拟列如何提高查询效率,以及如何为JSON数据类型创建索引。

MySQL虚拟列的详细讲解

MySQL 5.7引入了Generated Column,所谓Cenerated Column,就是数据库中的某一列由其他列计算而得。MySQL 5.7中,支持两种Generated Column,即Virtual Generated Column和Stored Generated Column,前者只将Generated Column保存在数据字典中(表的元数据),并不会将这一列数据持久化到磁盘上;后者会将Generated Column持久化到磁盘上,而不是每次读取的时候计算所得。与Virtual Column相比并没有优势,因此,MySQL 5.7中,不指定Generated Column的类型,默认是Virtual Column。

语法:

col_name data_type [GENERATED ALWAYS] AS (expr) [VIRTUAL | STORED] [NOT NULL | NULL] [UNIQUE [KEY]] [[PRIMARY] KEY] [COMMENT 'string']

AS (expr)表示生成列,并定义用于计算列值的表达式。前面可能会有“generate ALWAYS”,以使生成的列的性质更显式。

VIRTUAL或STORED关键字指示如何存储列值,这对列的使用有影响:

VIRTUAL:不存储列值,但在读取行时,在任何BEFORE触发器之后计算列值。虚拟列不占用任何存储空间。

InnoDB支持在虚拟列上建立二级索引。

STORED:在插入或更新行时计算并存储列值。存储的列确实需要存储空间,并且可以被索引。

虚拟列的允许:

允许在一个表中混合使用虚拟列和存储列。

还可以给出其他属性,以指示该列是否被索引,或者可以为空,或者提供注释。

生成的列表达式必须遵守以下规则。如果表达式包含不允许的构造,则会发生错误。

允许使用文字、确定性内置函数和操作符。如果给定表中相同的数据,多个调用独立于连接的用户产生相同的结果,那么一个函数就是确定的。不确定的函数的示例没有实现此定义:CONNECTION_ID(), CURRENT_USER(), NOW()。

允许按生成的列进行分区。

虚拟列限制使用条件:

不允许存储函数和用户定义函数。

不允许使用存储过程和函数参数。

不允许使用变量(系统变量、用户定义变量和存储的程序局部变量)。

子查询是不允许的。

生成的列定义可以引用其他生成的列,但只能引用表定义前面出现的列。生成的列定义可以引用表中的任何基列(非生成的),无论它的定义是早于还是晚于。

AUTO_INCREMENT属性不能在生成的列定义中使用。

在生成的列定义中,不能将AUTO_INCREMENT列用作基列。

在MySQL 中,如果表达式求值导致截断或函数输入错误,CREATE TABLE语句将以错误结束,DDL操作将被拒绝。

对于CREATE TABLE ... LIKE,,目标表保留从原始表生成的列信息。

CREATE TABLE triangle1like triangle

SHOW CREATE TABLE triangle1

CREATE TABLE `triangle1` (
  `sidea` INT(11) DEFAULT NULL,
  `sideb` INT(11) DEFAULT NULL,
  `sidec` INT(11) GENERATED ALWAYS AS (SQRT(((`sidea` * `sidea`) + (`sideb` * `sideb`)))) VIRTUAL
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4

;

可以看到sidec还保留着虚拟列。

对于 CREATE TABLE ... SELECT时,目标表不保留关于所选表中的列是否为生成列的信息。语句的选择部分不能为目标表中生成的列赋值。

如下面应用中的triangle,用它来创建表:

CREATE TABLE triangle1 AS SELECT * FROM triangle;

SHOW CREATE TABLE triangle2

CREATE TABLE `triangle2` (
  `sidea` INT(11) DEFAULT NULL,
  `sideb` INT(11) DEFAULT NULL,
  `sidec` INT(11) DEFAULT NULL
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4

可以看到sidec已经不是虚拟列。

应用:

CREATE TABLE triangle 
(   sidea INT,
    sideb INT,
    sidec INT AS (SQRT(sidea * sidea + sideb * sideb))
);


INSERT INTO triangle
(sidea, sideb)
VALUES(1,1),(3,4),(6,8)
;

生成的列有以下几个用例:

1. 虚拟生成的列可以作为简化和统一查询的一种方法。可以将复杂的条件定义为生成的列,并从表上的多个查询中引用,以确保所有查询都使用完全相同的条件。

2. 存储的生成的列可以作为一个物化缓存,用于动态计算成本很高的复杂条件。

3. 生成的列可以模拟函数索引:使用生成的列定义函数表达式并对其建立索引。这在处理不能直接索引的类型的列时很有用,比如JSON列;

4. 对于存储生成的列,这种方法的缺点是值需要存储两次;一次作为生成列的值,一次作为索引的值。

5. 如果生成的列被索引,优化器将识别与列定义匹配的查询表达式,并在查询执行期间适当地使用来自列的索引,即使查询没有直接按名称引用列。

虚拟列优化例子:

在现实开发中,很多程序员为了方便,喜欢在where条件中应用函数,这样导致该列的索引未被使用,影响查询效率。

SELECT * FROM test_data
 WHERE  DATE_FORMAT(static_date, '%Y-%m-%d') >=  DATE_FORMAT('2021-01-22', '%Y-%m-%d')   
   AND DATE_FORMAT(static_date, '%Y-%m-%d') <=  DATE_FORMAT('2021-01-24', '%Y-%m-%d') 

这个查询要0.997秒。

查看查询计划,没有用到索引,查询比较慢。

EXPLAIN
SELECT * FROM test_data
 WHERE  static_date >=  DATE_FORMAT('2021-01-22', '%Y-%m-%d')   
   AND static_date<=  DATE_FORMAT('2021-01-24', '%Y-%m-%d')   

在原表增加个虚拟列和索引:

ALTER TABLE `test_data` ADD vr_time VARCHAR(10) AS (DATE_FORMAT(static_date, '%Y-%m-%d')) STORED, ADD KEY idx_vr_time (vr_time);

再执行上面的查询,可以看到查询变快了很多。

看看查询计划,用到了static_date列的索引。这类似于oracle的函数索引。

使用虚拟列为Json数据类型创建索引


   
CREATE TABLE t1
(   id INT AUTO_INCREMENT,
    full_name json,
    PRIMARY KEY(id)
)ENGINE = INNODB;   
   
插入数据

INSERT INTO t1 VALUES (NULL,'{"first_name":"Wukong","last_name":"Sun"}');
INSERT INTO t1 VALUES (NULL,'{"first_name":"Bajie","last_name":"Zhu"}');   
   
   
SELECT full_name FROM t1;   

构建姓名的虚拟列
方法1:
ALTER TABLE t1 ADD v_name VARCHAR(20) generated always AS (JSON_EXTRACT(full_name,'$.first_name'));
方法2:
ALTER TABLE t1 ADD v_name VARCHAR(20) generated always AS (full_name->'$.first_name');

构建索引
ALTER TABLE t1 ADD INDEX idx_firstname(v_name);


使用如下查询语句

SELECT * FROM t1 WHERE v_name='Wukong';

并不能查询到结果,需使用

SELECT * FROM t1 WHERE v_name='"Wukong"';

方可查询到结果。

 可以知道构建的虚拟列中的数据多了 "" ,那么如何去掉呢?    可以这样:ALTER TABLE t1 ADD v_name VARCHAR(20) generated always AS (JSON_UNQUOTE(json_extract(full_name,'$.first_name')));

构建last_name的虚拟列并创建索引
构建虚拟列:

ALTER TABLE t1 ADD v_lastname VARCHAR(20) generated always AS (JSON_UNQUOTE(full_name->'$.last_name'));

创建索引:

ALTER TABLE t1 ADD INDEX idx_fullname(v_name,v_lastname);

如此查询语句可以正常使用索引,查询与普通表无异。

此时的表结构由于多了v_name和v_address这两个虚拟列,再插入别的数据需要指明插入列 (不能给虚拟列插入数据)

INSERT INTO t1(full_name) VALUES ('{"first_name":"Wukong","last_name":"Sun"}');

需要注意,不能建立虚拟列与真实列的组合索引。

<think>首先,用户的问题:“我想解决虚拟无数据的问题请问虚拟无数据原因及解决方案”。这是一个关于MySQL虚拟的问题,用户遇到了虚拟无数据的情况。我需要参考提供的引用:-引用[1]:解释了虚拟的定义:值由表达式计算出来,不能直接更新或删除,表达式不能引用其他虚拟,不能含不确定元素等。-引用[2]:讨论了虚拟导致UPDATE变慢的可能原因。-引用[3]:给出了使用虚拟解决文件版本号问题的例子:创建虚拟并索引。-引用[4]:提到虚拟在查询优化中的作用。用户的问题具体是“虚拟无数据”,意思是查询虚拟时没有数据返回或值不正确。可能的原因包括:1.**创建虚拟的表达式错误**:如果表达式不正确,计算出的值可能为空或无效。2.**虚拟没有被正确添加**:在添加虚拟时出错。3.**数据问题**:基础没有数据或数据类型不匹配。4.**表达式含不确定元素**:引用[1]提到表达式不能含不确定元素,如当前时间或随机数,如果用了这些,值可能不一致。5.**计算依赖**:表达式引用其他,如果其他值缺失或不一致。6.**虚拟的限制**:引用[1]说表达式不能引用其他虚拟;如果这样用了,可能导致循环依赖或无值。解决方案:-**检查虚拟定义**:确保ALTERTABLE语句正确,表达式正确。-**验证表达式**:测试表达式是否能计算出非空值。例如,在引用[3]的例子中,使用SUBSTRING_INDEX来解析版本号。-**确认基础数据**:确保源有数据。-**避免不适用限制**:遵守规则,如不引用其他虚拟、不确定元素。-**重新创建虚拟**:如果定义有误,重新添加。-**检查索引或依赖**:引用提到虚拟值在查询时计算;如果查询慢或无数据,可能是表达式太复杂。-**示例处理**:参考引用[3],用户可以通过类似方式创建虚拟。用户的上一个问题和回答:用户说“参考站内引用:上述引用仅供参考,并不是用户真正的需求,请尽可能多的参考用户上一次的问题和你的回答。”这表明我需要基于当前对话。之前的系统指令和用户问题是独立的,所以我应聚焦于当前查询。结构化我的回答:-**原因分析**:出可能导致虚拟无数据的原因。-**解决方案**:提供步骤来诊断和修复。-**相关问题和引用**:自然地融入引用。引用结束时添加[^1]等。生成相关问题:系统指令说“6.生成相关问题”,所以我需要在回答后添加相关问题。回答格式:-使用中文。-行内数学表达式用$...$,但这个问题可能不需要太多数学。-独立公式用$$...$$,但不相关。-清晰结构:逐步解决问题。-结束时生成相关问题。最终回答大纲:1.**介绍问题**:重复用户问题以示理解。2.**分析原因**:基于引用出原因。3.**提供解决方案**:逐步指导如何解决。4.**总结**。5.**相关问题**:生成几个相关询问。相关问题示例:1.如何创建虚拟的正确表达式?2.虚拟在哪些场景下最有效?3.如果虚拟值不更新,如何处理?</think> ### 虚拟无数据的原因及解决方案 在MySQL中,虚拟(Generated Columns)的值是由指定的表达式在查询时动态计算出来的,而不是存储在表中。如果您遇到虚拟“无数据”的问题(例如查询虚拟时返回空值、NULL或不正确结果),这通常源于以下原因。我将基于您的引用资料(如引用[1]、[3]、[4])进行分析,并提供一个逐步的解决方案。请确保您的MySQL版本支持虚拟(如5.7或更高)。 --- ### **原因分析** 虚拟无数据通常是由以下常见原因引起的: 1. **虚拟的表达式错误**: - 如果表达式中包含语法错误、无效的函数引用或不兼容的数据类型,计算出的值可能为NULL或出错。例如,引用[1]强调虚拟的表达式不能引用其他虚拟的值,否则会导致循环依赖或无效结果[^1]。 - 表达式包含不确定元素(如 `CURRENT_TIMESTAMP()` 或 `RAND()` ),这些在计算时可能返回不一致值或 NULL(引用[1]说明表达式不能含不确定元素)[^1]。 2. **基础数据缺失或无效**: - 虚拟的值依赖于其他基础的值。如果这些基础为空、NULL 或数据类型不匹配(如尝试用字符串函数处理数字),表达式计算可能失败,返回空结果。 3. **虚拟创建或定义问题**: - 在添加虚拟时,`ALTER TABLE` 语句可能有误,导致虚拟未被正确创建(例如,表达式写错或存储方式设置不当)。引用[3]中展示了创建虚拟的正确语法,但若操作错误,可能导致虚拟“无数据”[^3]。 - 如果虚拟引用了不存在或不稳定的MySQL 在查询时会忽略计算(引用[4]提到虚拟的定义需要谨慎)[^4]。 4. **查询或索引限制**: - 如果查询条件不匹配或索引问题,可能导致虚拟值未被正确返回。例如,在虚拟上创建了索引(如引用[3]的例子),但索引占用存储空间且计算复杂时,查询性能下降,可能显为空值(引用[2]提到虚拟可能导致 UPDATE 慢)[^2]。 5. **MySQL 的行为限制**: - 虚拟值在每次查询时动态计算,如果表达式复杂或数据量过大,计算可能超时或不完整(引用[1]和[4]均指出这可能影响查询结果)[^1][^4]。 --- ### **解决方案** 针对“虚拟无数据”问题,按照以下步骤诊断和解决。操作前请备份数据库,以避免数据丢失。 #### **步骤1: 检查虚拟定义和表达式** - **诊断方式**: - 使用 `SHOW CREATE TABLE table_name;` 查看虚拟的定义是否包含错误。 - 验证表达式语法是否有效:确保表达式不引用其他虚拟(违反引用[1]规则)、不含不确定函数,并匹配基础数据类型。例如,在引用[3]中,正确使用 `SUBSTRING_INDEX(version_number, '.', 1)` 解析字符串,但如果 `version_number` 为空或格式错误,结果可能为 NULL[^3]。 - **修复方法**: - 重新创建虚拟:先删除问题虚拟(注意:删除时需移除所有依赖,如索引),然后重新添加。语法示例: ```sql -- 删除虚拟和索引(如果存在) ALTER TABLE your_table DROP COLUMN virtual_column_name; ALTER TABLE your_table DROP INDEX index_name; -- 重新添加虚拟,确保表达式正确 ALTER TABLE your_table ADD COLUMN virtual_column_name INT GENERATED ALWAYS AS (your_valid_expression) VIRTUAL; ``` 例如,参考引用[3],创建一个可靠虚拟: ```sql ALTER TABLE your_table ADD COLUMN v_example VARCHAR(50) GENERATED ALWAYS AS (COALESCE(base_column, 'default_value')) VIRTUAL; ``` 这里,`COALESCE()` 函数处理基础为 NULL 的情况[^3]。 #### **步骤2: 验证基础数据** - **诊断方式**: - 查询基础:运行 `SELECT * FROM your_table WHERE base_column IS NULL;` 检查是否有空值影响计算。 - 测试表达式:直接在查询中使用表达式,观察结果。例如: ```sql SELECT your_valid_expression FROM your_table; -- 如果结果非空,表示表达式正确,否则需调整。 ``` - **修复方法**: - 清理数据:如果基础为 NULL 或不一致,更新或填充数据。例如: ```sql UPDATE your_table SET base_column = 'default' WHERE base_column IS NULL; ``` - 修改表达式:添加 NULL 处理(如 `IFNULL()` 或 `COALESCE()`),避免表达式因空值失败。参考引用[1],虚拟不能有默认值,但可在表达式中模拟[^1]。 #### **步骤3: 检查 MySQL 环境和限制** - **诊断方式**: - 确认 MySQL 版本:虚拟需要 MySQL 5.7+。运行 `SELECT VERSION();` 检查。 - 查看错误日志:检查 MySQL 错误日志(通常位于 `/var/log/mysql/error.log`),定位创建或查询时的报错。 - **修复方法**: - 升级或修复环境:如果版本过低,升级 MySQL。 - 优化表达式:简化复杂表达式(如避免嵌套函数),或改用 STORED 类型虚拟(如果数据量允许)。STORED 类型在插入时计算值,但占用存储空间(引用[2]提到 VIRTUAL 类型可能导致性能问题)[^2]。 #### **步骤4: 测试和监控** - 运行查询:验证虚拟值后,创建索引优化性能(引用[3]展示在虚拟上索引的方法)[^3]。 - 例如: ```sql -- 添加索引 ALTER TABLE your_table ADD INDEX idx_virtual (virtual_column_name); -- 测试查询 SELECT * FROM your_table WHERE virtual_column_name IS NOT NULL; ``` - 监控性能:使用 `EXPLAIN SELECT ...` 查看查询计划,确保虚拟计算未超时。 **预防措施**: - 设计时遵循虚拟规则:避免不确定元素、多虚拟引用(引用[1])。 - 在开发环境测试:先在测试表中模拟问题。 如果您的问题仍未解决,请提供更多细节(如表结构、表达式代码和错误消息),我会进一步协助您。 --- ### 相关问题 1. 如何正确创建 MySQL 虚拟的表达式以避免无效值? 2. 虚拟数据库查询优化中有哪些实际应用场景? 3. 如果虚拟导致 UPDATE 操作变慢,该如何处理?
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值