En règle générale, vous ne pouvez pas indexer directement un champ JSON ni utiliser une valeur dérivée (comme les deux derniers caractères d'une chaîne) comme clé de partition. Les colonnes générées résolvent ces deux problèmes. Une colonne générée stocke le résultat d'une expression appliquée à d'autres colonnes de la même ligne ; la base de données maintient cette valeur automatiquement. Vous ne devez jamais y insérer ni y mettre à jour de données directement.
Cette rubrique explique comment créer des colonnes générées dans PolarDB-X et comment y définir des index.
Types de colonnes
PolarDB-X prend en charge trois types de colonnes générées. Leur fonctionnement s'apparente à la différence entre une vue et une vue matérialisée, mais appliqué au niveau de la colonne :
| Type | Stockage | Calculé par | Moment du calcul | Clé de partition ? |
|---|---|---|---|---|
| VIRTUAL | Non stocké | Nœud de données | À chaque lecture | Non |
| STORED | Stocké sur le nœud de données | Nœud de données | INSERT ou UPDATE | Non |
| LOGICAL | Stocké sur le nœud de données | Nœud de calcul | INSERT ou UPDATE | Oui |
Le type VIRTUAL fonctionne comme une vue au niveau de la colonne : la valeur est calculée à la demande et n'occupe aucun espace de stockage. Le type STORED se comporte comme une vue matérialisée au niveau de la colonne : la valeur est précalculée et stockée, ce qui accélère les lectures mais entraîne une légère surcharge lors des écritures. Le type LOGICAL est calculé sur le nœud de calcul, stocké comme une colonne classique, et constitue le seul type pouvant servir de clé de partition ou référencer une fonction définie par l'utilisateur (UDF).
Si vous omettez de spécifier le type, la colonne adopte par défaut le type VIRTUAL.
Syntaxe
col_name data_type [GENERATED ALWAYS] AS (expr)
[VIRTUAL | STORED | LOGICAL] [NOT NULL | NULL]
[UNIQUE [KEY]] [[PRIMARY] KEY]
[COMMENT 'string']
Prérequis
Avant de commencer, assurez-vous de disposer de :
PolarDB-X Enterprise Edition V5.4.17 ou version ultérieure
Cette exigence de version s'applique aux colonnes générées, aux index sur colonnes générées ainsi qu'aux index d'expression.
Créer une colonne générée
Limites
Les colonnes générées dans PolarDB-X partagent les contraintes standard de MySQL et ajoutent plusieurs restrictions spécifiques aux environnements distribués.
Contraintes communes avec MySQL
Les valeurs par défaut ne sont pas prises en charge.
Il est impossible de définir
AUTO_INCREMENTsur une colonne générée, et une colonne générée ne peut pas référencer une colonneAUTO_INCREMENT.Les fonctions non déterministes (telles que
UUID(),CONNECTION_ID()etNOW()) ne peuvent pas être utilisées dans l'expression d'une colonne générée.L'utilisation de variables dans les expressions est interdite.
Les sous-requêtes ne sont pas autorisées dans les expressions.
Vous ne pouvez pas spécifier les valeurs des colonnes générées dans les instructions
INSERTouUPDATE. La base de données les calcule toujours automatiquement.
Spécificités PolarDB-X
Vous ne pouvez pas ajouter de colonnes générées aux tables pour lesquelles l'archivage des données froides est activé.
Les colonnes VIRTUAL et STORED ne peuvent servir ni de clés de partition, ni de clés primaires, ni de clés uniques.
Les colonnes VIRTUAL et STORED ne peuvent pas référencer de fonctions stockées.
Si un index secondaire global (GSI) inclut des colonnes générées VIRTUAL ou STORED, il doit également contenir toutes les colonnes référencées dans les expressions de ces colonnes générées.
Une colonne générée LOGICAL ne peut pas référencer de colonne générée VIRTUAL ou STORED dans son expression.
Vous ne pouvez plus modifier le type d'une colonne générée LOGICAL ni les types des colonnes référencées dans son expression après sa création.
-
Les colonnes générées LOGICAL et les colonnes qu'elles référencent prennent uniquement en charge les types de données suivants :
Types entiers :
BIGINT,INT,MEDIUMINT,SMALLINT,TINYINTTypes date :
DATETIME,DATE,TIMESTAMP(les colonnes avecON UPDATE CURRENT_TIMESTAMPne sont pas prises en charge)Types chaîne :
CHAR,VARCHAR
Exemples : colonnes VIRTUAL et STORED
Exemple 1 : Calcul automatique d'une valeur dérivée
Créez une table où l'hypoténuse d'un triangle rectangle est calculée sous forme de colonne générée :
CREATE TABLE triangle (
sidea DOUBLE,
sideb DOUBLE,
sidec DOUBLE AS (SQRT(sidea * sidea + sideb * sideb))
);
INSERT INTO triangle (sidea, sideb) VALUES (1, 1), (3, 4), (6, 8);
SELECT * FROM triangle;
Résultat :
+-------+-------+--------------------+
| sidea | sideb | sidec |
+-------+-------+--------------------+
| 1.0 | 1.0 | 1.4142135623730951 |
| 3.0 | 4.0 | 5.0 |
| 6.0 | 8.0 | 10.0 |
+-------+-------+--------------------+
Exemple 2 : Colonne générée dans une table partitionnée
Créez une table partitionnée comportant une colonne générée VIRTUAL nommée b :
CREATE TABLE `t1` (
`a` int(11) NOT NULL,
`b` int(11) GENERATED ALWAYS AS (`a` + 1),
PRIMARY KEY (`a`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 dbpartition by hash(`a`);
INSERT INTO t1 (a) VALUES (1);
SELECT * FROM t1;
Résultat :
+---+---+
| a | b |
+---+---+
| 1 | 2 |
+---+---+
Exemples : colonnes LOGICAL
Les colonnes générées LOGICAL constituent le choix idéal lorsque vous avez besoin d'un partitionnement flexible basé sur une valeur dérivée, ou lorsque vous souhaitez utiliser une UDF dans l'expression.
Exemple 1 : Partitionner selon une sous-chaîne d'une colonne texte
Utilisez les deux derniers caractères de la colonne b comme clé de partition :
CREATE TABLE `t2` (
`a` int(11) NOT NULL,
`b` varchar(32) DEFAULT NULL,
`c` varchar(2) GENERATED ALWAYS AS (SUBSTR(`b`, -2)) LOGICAL,
PRIMARY KEY (`a`),
KEY `auto_shard_key_c` USING BTREE (`c`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 dbpartition by hash(`c`);
Exemple 2 : Référencer une UDF dans une colonne générée LOGICAL
Commencez par créer la fonction définie par l'utilisateur my_abs :
DELIMITER &&
CREATE FUNCTION my_abs (
a INT
)
RETURNS INT
BEGIN
IF a < 0 THEN
RETURN -a;
ELSE
RETURN a;
END IF;
END&&
DELIMITER ;
Créez ensuite une table avec une colonne générée LOGICAL qui appelle my_abs :
CREATE TABLE `t3` (
`a` int(11) NOT NULL,
`b` int(11) GENERATED ALWAYS AS (MY_ABS(`a`)) LOGICAL,
PRIMARY KEY (`a`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 dbpartition by hash(`b`);
INSERT INTO t3 (a) VALUES (1), (-1);
Vérifiez que les requêtes sur b sont bien transmises au nœud de données :
EXPLAIN SELECT * FROM t3 WHERE b = 1;
+-----------------------------------------------------------------------------------------------------------+
| LOGICAL EXECUTIONPLAN |
+-----------------------------------------------------------------------------------------------------------+
| LogicalView(tables="TEST_000002_GROUP.t3_WHHZ", sql="SELECT `a`, `b` FROM `t3` AS `t3` WHERE (`b` = ?)") |
+-----------------------------------------------------------------------------------------------------------+
SELECT * FROM t3 WHERE b = 1;
+----+------+
| a | b |
+----+------+
| -1 | 1 |
| 1 | 1 |
+----+------+
Créer un index sur une colonne générée
Limites
| Type d'index | VIRTUAL | STORED | LOGICAL |
|---|---|---|---|
| Index local | Oui | Oui | Oui |
| Index global | Non | Non | Oui |
Exemples
Exemple 1 : Index local sur une colonne VIRTUAL
Cet exemple montre comment indexer un champ JSON en l'extrayant vers une colonne générée VIRTUAL. Les colonnes JSON ne pouvant pas être indexées directement dans PolarDB-X (ni dans MySQL), ce modèle constitue la solution de contournement standard :
CREATE TABLE t4 (
a BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
c JSON,
g INT AS (c->"$.id") VIRTUAL
) DBPARTITION BY HASH(a);
CREATE INDEX `i` ON `t4`(`g`);
INSERT INTO t4 (c) VALUES
('{"id": "1", "name": "Fred"}'),
('{"id": "2", "name": "Wilma"}'),
('{"id": "3", "name": "Barney"}'),
('{"id": "4", "name": "Betty"}');
EXPLAIN EXECUTE SELECT c->>"$.name" AS name FROM t4 WHERE g > 2;
+------+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+------+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t4 | NULL | range | i | i | 5 | NULL | 1 | 100 | Using where |
+------+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
Le plan d'exécution confirme l'utilisation de l'index i.
Exemple 2 : Index local sur une colonne LOGICAL
CREATE TABLE t5 (
a BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
c varchar(32),
g char(2) AS (substr(`c`, 2)) LOGICAL
) DBPARTITION BY HASH(a);
CREATE INDEX `i` ON `t5`(`g`);
INSERT INTO t5 (c) VALUES
('1111'),
('1112'),
('1211'),
('1311');
EXPLAIN EXECUTE SELECT c AS name FROM t5 WHERE g = '11';
+------+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+------+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------+
| 1 | SIMPLE | t5 | NULL | ref | i | i | 8 | NULL | 4 | 100.00 | Using XPlan, Using where |
+------+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------+
Exemple 3 : Index global sur une colonne LOGICAL
CREATE TABLE t6 (
a BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
c varchar(32),
g char(2) AS (substr(`c`, 2)) LOGICAL
) DBPARTITION BY HASH(a);
CREATE GLOBAL INDEX `g_i` ON `t6`(`g`) COVERING(`c`) DBPARTITION BY HASH(`g`);
INSERT INTO t6 (c) VALUES
('1111'),
('1112'),
('1211'),
('1311');
EXPLAIN SELECT c AS name FROM t6 WHERE g = '11';
+---------------------------------------------------------------------------------------------------------------------+
| LOGICAL EXECUTIONPLAN |
+---------------------------------------------------------------------------------------------------------------------+
| IndexScan(tables="TEST_DRDS_000000_GROUP.g_i_J1MT", sql="SELECT `c` AS `name` FROM `g_i` AS `g_i` WHERE (`g` = ?)") |
+---------------------------------------------------------------------------------------------------------------------+
Index d'expression
Un index d'expression permet d'indexer le résultat d'une expression plutôt qu'une colonne directement. Lors de la création de l'index, PolarDB-X convertit automatiquement chaque entrée d'expression en une colonne générée VIRTUAL, puis construit l'index sur cette colonne.
Limites
-
Les index d'expression sont désactivés par défaut. Activez cette fonctionnalité avec la commande suivante :
SET GLOBAL ENABLE_CREATE_EXPRESSION_INDEX = TRUE; Les index globaux ne sont pas pris en charge.
Les index uniques ne sont pas pris en charge.
Vous ne pouvez pas créer d'index d'expression dans une instruction
CREATE TABLE. Créez d'abord la table, puis utilisezALTER TABLEouCREATE INDEX.La suppression d'un index d'expression via
DROP INDEXn'efface pas les colonnes générées créées automatiquement. Supprimez-les manuellement à l'aide deALTER TABLEen mode DRDS ou en mode AUTO.
Exemples
Exemple 1 : Création d'un index d'expression
-
Créez la table :
CREATE TABLE t7 ( a BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, c varchar(32) ) DBPARTITION BY HASH(a); -
Créez un index d'expression sur
substr(c, 2):CREATE INDEX `i` ON `t7`(substr(`c`, 2)); -
Après la création, PolarDB-X réécrit la structure de la table pour ajouter une colonne générée VIRTUAL correspondant à chaque entrée d'expression :
CREATE TABLE `t7` ( `a` bigint(20) NOT NULL AUTO_INCREMENT BY GROUP, `c` varchar(32) DEFAULT NULL, `i$0` varchar(32) GENERATED ALWAYS AS (substr(`c`, 2)) VIRTUAL, PRIMARY KEY (`a`), KEY `i` (`i$0`) ) ENGINE = InnoDB dbpartition by hash(`a`);La colonne
i$0contient le résultat de l'expression, et l'indexiest construit suri$0. -
Vérifiez que l'index est bien sélectionné pour les requêtes filtrant sur l'expression :
EXPLAIN EXECUTE SELECT * FROM t7 WHERE substr(`c`, 2) = '11';+------+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +------+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+ | 1 | SIMPLE | t7 | NULL | ref | i | i | 131 | const | 1 | 100 | NULL | +------+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
Exemple 2 : Index d'expression avec plusieurs expressions
Lorsqu'un index comprend plusieurs expressions, PolarDB-X crée une colonne générée distincte pour chaque entrée d'expression (les entrées sans expression restent inchangées).
Créez l'index :
CREATE INDEX idx ON t8(
a + 1,
b,
SUBSTR(c, 2)
);
Structure de table résultante :
CREATE TABLE `t8` (
`a` int(11) NOT NULL,
`b` int(11) DEFAULT NULL,
`c` varchar(32) DEFAULT NULL,
`idx$0` bigint(20) GENERATED ALWAYS AS (`a` + 1) VIRTUAL,
`idx$2` varchar(32) GENERATED ALWAYS AS (substr(`c`, 2)) VIRTUAL,
PRIMARY KEY (`a`),
KEY `idx` (`idx$0`, `b`, `idx$2`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 dbpartition by hash(`a`);
Les colonnes idx$0 et idx$2 correspondent respectivement à la première et à la troisième entrée de l'index. La colonne b étant une colonne classique, elle ne nécessite pas d'enveloppe de colonne générée.
Les requêtes qui filtrent simultanément sur ces trois conditions tirent parti de l'index :
EXPLAIN EXECUTE SELECT * FROM t8 WHERE a+1=10 AND b=20 AND SUBSTR(c,2)='ab';
+------+-------------+-------+------------+------+---------------+------+---------+-------------------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+------+-------------+-------+------------+------+---------------+------+---------+-------------------+------+----------+-------+
| 1 | SIMPLE | t8 | NULL | ref | idx | idx | 145 | const,const,const | 1 | 100 | NULL |
+------+-------------+-------+------------+------+---------------+------+---------+-------------------+------+----------+-------+