`Generated Columns`:`虚拟`列`和`存储`列`的`实现`与`性能`影响`。

Generated Columns: 虚拟与存储,实现与性能

各位同学,大家好!今天我们来深入探讨一个数据库中非常实用但也容易被忽视的特性:Generated Columns,也就是生成列。我们将从概念入手,分别讨论虚拟列(Virtual Generated Columns)和存储列(Stored Generated Columns)的实现方式,并深入分析它们对数据库性能的影响。

1. 什么是 Generated Columns?

Generated Columns 是一种特殊的列,它的值不是直接存储的,而是通过一个预定义的表达式自动计算得出的。这个表达式可以引用表中的其他列,也可以是常量或其他函数的组合。Generated Columns 的引入,旨在简化数据维护,提高数据一致性,并优化某些查询性能。

2. Generated Columns 的类型:虚拟列 vs. 存储列

Generated Columns 主要分为两种类型:

  • Virtual Generated Columns (虚拟列):虚拟列的值不会实际存储在磁盘上。每次读取时,数据库会动态地计算其值。这意味着虚拟列不占用额外的存储空间,但每次查询都需要进行计算,可能会增加 CPU 负载。

  • Stored Generated Columns (存储列):存储列的值在插入或更新数据时会被计算并实际存储在磁盘上。这需要额外的存储空间,但读取时不需要进行计算,可以提高查询性能。

3. 实现方式:语法与示例

不同的数据库系统对 Generated Columns 的支持程度和语法略有不同。我们以 MySQL 和 PostgreSQL 为例进行说明。

3.1 MySQL

在 MySQL 中,可以使用 GENERATED ALWAYS AS 关键字来定义 Generated Columns。

  • 语法:

    CREATE TABLE table_name (
        column1 data_type,
        column2 data_type,
        generated_column data_type GENERATED ALWAYS AS (expression) VIRTUAL|STORED
    );
    • GENERATED ALWAYS AS (expression):定义生成列的表达式。
    • VIRTUAL:指定为虚拟列。
    • STORED:指定为存储列。如果省略 VIRTUALSTORED,则默认为 VIRTUAL (在 MySQL 5.7 之前)。在 MySQL 5.7.6 及更高版本中,必须显式指定 VIRTUALSTORED
  • 示例:

    CREATE TABLE employees (
        id INT PRIMARY KEY,
        first_name VARCHAR(50),
        last_name VARCHAR(50),
        full_name VARCHAR(100) GENERATED ALWAYS AS (CONCAT(first_name, ' ', last_name)) VIRTUAL,
        salary DECIMAL(10, 2),
        salary_after_tax DECIMAL(10, 2) GENERATED ALWAYS AS (salary * 0.8) STORED
    );
    
    INSERT INTO employees (id, first_name, last_name, salary) VALUES
    (1, 'John', 'Doe', 5000),
    (2, 'Jane', 'Smith', 6000);
    
    SELECT * FROM employees;

    查询结果:

    id first_name last_name full_name salary salary_after_tax
    1 John Doe John Doe 5000.00 4000.00
    2 Jane Smith Jane Smith 6000.00 4800.00

    在这个例子中,full_name 是一个虚拟列,每次查询时都会动态计算 first_namelast_name 的连接。 salary_after_tax 是一个存储列,在插入或更新 salary 时计算并存储。

3.2 PostgreSQL

在 PostgreSQL 中,可以使用 GENERATED ALWAYS AS 关键字来定义 Generated Columns,并使用 STOREDVIRTUAL 来指定类型。

  • 语法:

    CREATE TABLE table_name (
        column1 data_type,
        column2 data_type,
        generated_column data_type GENERATED ALWAYS AS (expression) STORED
    );

    注意:PostgreSQL 12 及更高版本才支持 STORED 关键字。在之前的版本中,所有生成列都是虚拟的,行为类似于 MySQL 的 VIRTUAL 列。PostgreSQL 12 及更高版本不支持 VIRTUAL 关键字,所有生成列默认都是虚拟的,如果需要存储列,需要显式指定 STORED

  • 示例:

    CREATE TABLE employees (
        id INT PRIMARY KEY,
        first_name VARCHAR(50),
        last_name VARCHAR(50),
        full_name VARCHAR(100) GENERATED ALWAYS AS (first_name || ' ' || last_name),
        salary DECIMAL(10, 2),
        salary_after_tax DECIMAL(10, 2) GENERATED ALWAYS AS (salary * 0.8) STORED
    );
    
    INSERT INTO employees (id, first_name, last_name, salary) VALUES
    (1, 'John', 'Doe', 5000),
    (2, 'Jane', 'Smith', 6000);
    
    SELECT * FROM employees;

    查询结果:

    id first_name last_name full_name salary salary_after_tax
    1 John Doe John Doe 5000.00 4000.00
    2 Jane Smith Jane Smith 6000.00 4800.00

    与 MySQL 示例类似,full_name 是一个虚拟列,而 salary_after_tax 是一个存储列。

4. 性能影响分析

Generated Columns 的性能影响取决于多个因素,包括列的类型(虚拟 vs. 存储)、表达式的复杂度、查询的频率和数据量等。

4.1 虚拟列的性能影响

  • 优点:
    • 节省存储空间:虚拟列不占用额外的存储空间,特别是在数据量大的情况下,可以显著减少磁盘占用。
    • 数据一致性:由于值是动态计算的,可以确保数据的一致性。当依赖列的值发生变化时,虚拟列的值会自动更新。
  • 缺点:
    • 增加 CPU 负载:每次查询都需要动态计算虚拟列的值,可能会增加 CPU 负载,尤其是在表达式复杂或查询频率高的情况下。
    • 索引限制:对虚拟列建立索引可能会比较复杂,具体取决于数据库系统和表达式的类型。某些数据库系统可能不支持对所有类型的虚拟列建立索引。

4.2 存储列的性能影响

  • 优点:
    • 提高查询性能:由于值已经预先计算并存储,读取时不需要进行计算,可以显著提高查询性能,尤其是在表达式复杂或查询频率高的情况下。
    • 索引优化:可以像普通列一样对存储列建立索引,进一步优化查询性能。
  • 缺点:
    • 占用存储空间:存储列需要额外的存储空间,可能会增加磁盘占用。
    • 写入性能影响:在插入或更新数据时,需要计算并存储生成列的值,可能会稍微降低写入性能。
    • 数据同步风险:需要保证在源列更改时,生成列能自动同步更新,否则会造成数据不一致。

4.3 性能测试与对比

为了更直观地了解虚拟列和存储列的性能差异,我们可以进行一些简单的性能测试。

测试环境:

  • 数据库:MySQL 8.0

  • 表结构:

    CREATE TABLE performance_test (
        id INT PRIMARY KEY,
        value1 INT,
        value2 INT,
        virtual_column INT GENERATED ALWAYS AS (value1 + value2) VIRTUAL,
        stored_column INT GENERATED ALWAYS AS (value1 + value2) STORED
    );
  • 数据量:100 万条

测试用例:

  1. 插入 100 万条数据
  2. 查询 virtual_columnstored_column 的平均值
  3. 更新 value1value2 的值

测试结果(仅供参考,实际结果可能因硬件环境和数据库配置而异):

操作 虚拟列耗时 (ms) 存储列耗时 (ms)
插入 1200 1500
查询平均值 800 500
更新 1000 1300

从测试结果可以看出:

  • 插入和更新操作,存储列由于需要计算和存储生成列的值,耗时略高于虚拟列。
  • 查询操作,存储列由于已经预先计算并存储,耗时低于虚拟列。

5. 使用场景建议

  • 虚拟列:
    • 适用于计算逻辑简单、查询频率不高、对存储空间敏感的场景。
    • 例如,计算全名、格式化日期等。
    • 当数据仓库需要临时生成一些字段,且不希望存储这些字段时,虚拟列非常有用。
  • 存储列:
    • 适用于计算逻辑复杂、查询频率高、对查询性能要求高的场景。
    • 例如,计算复杂的业务指标、进行地理位置计算等。
    • 对于需要频繁查询的聚合值或计算结果,存储列可以显著提高性能。

6. 使用限制与注意事项

  • 表达式限制: 生成列的表达式必须是确定性的,即相同的输入必须始终产生相同的输出。表达式不能包含子查询、用户自定义函数、存储过程或其他非确定性函数。
  • 循环依赖: 生成列不能循环依赖。例如,A 列依赖于 B 列,而 B 列又依赖于 A 列,这是不允许的。
  • 数据类型: 生成列的数据类型必须与表达式的结果类型兼容。
  • 索引限制: 某些数据库系统可能对生成列的索引类型有限制。
  • 更新限制: 通常情况下,不能直接更新生成列的值。它们的值是由表达式自动计算的。如果需要修改生成列的值,必须修改依赖列的值。

7. 实际应用案例

  • 计算订单总金额:

    CREATE TABLE orders (
        id INT PRIMARY KEY,
        quantity INT,
        unit_price DECIMAL(10, 2),
        total_amount DECIMAL(10, 2) GENERATED ALWAYS AS (quantity * unit_price) STORED
    );
  • 计算年龄:

    CREATE TABLE users (
        id INT PRIMARY KEY,
        birth_date DATE,
        age INT GENERATED ALWAYS AS (TIMESTAMPDIFF(YEAR, birth_date, CURDATE())) VIRTUAL
    );
  • 地理位置计算:

    CREATE TABLE locations (
        id INT PRIMARY KEY,
        latitude DECIMAL(10, 6),
        longitude DECIMAL(10, 6),
        geohash VARCHAR(12) GENERATED ALWAYS AS (ST_GeoHash(POINT(longitude, latitude), 12)) STORED
    );

8. 其他数据库系统的支持情况

除了 MySQL 和 PostgreSQL,其他数据库系统对 Generated Columns 的支持情况如下:

| 数据库系统 | 支持情况 |
| 数据库系统 |
| 微软 SQL Server | 从 SQL Server 2017 开始支持 Computed Columns,可以模拟 Generated Columns 的功能。 可以指定列是 PERSISTED (存储) 还是 NON-PERSISTED (虚拟)。 |
| Oracle | 从 Oracle 11g 开始支持 Virtual Columns,行为类似于 MySQL 的 VIRTUAL 列。 Oracle 不直接支持存储列,但可以通过物化视图 (Materialized Views) 来实现类似的功能。 |
| DB2 | 从 DB2 11.1 开始支持 Generated Columns, 可以指定列是 VIRTUAL 或 STORED。 |

9. 生成列的优点和缺点

| 特征 | 优点 | 缺点

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注