產生列是一種特殊的列,其值是運算式計算的結果。本文介紹了產生列的建立方法,以及在產生列上建立索引的方法。
建立產生列
文法
col_name data_type [GENERATED ALWAYS] AS (expr)
[VIRTUAL | STORED | LOGICAL] [NOT NULL | NULL]
[UNIQUE [KEY]] [[PRIMARY] KEY]
[COMMENT 'string']
產生列有以下三種類型:
VITRUAL:產生列的值不儲存,每次讀取該列時由儲存節點DN計算,不佔用儲存空間。
說明如果不指定關鍵字,預設建立VITRUAL類型的產生列。
STORED:產生列的值在資料行插入或更新時由儲存節點DN計算,並將結果儲存下來,需要佔用儲存空間。
LOGICAL:與STORED類型相似,產生列的值在資料行插入或更新時計算,區別是產生列的值在計算節點CN計算,隨後以普通列的形式儲存到DN中。該類型的產生列可以作為分區鍵使用。
注意事項
5.4.17及以上版本的企業版執行個體支援此功能。
與MySQL類似,相比普通列,使用產生列有如下限制條件:
與MySQL相同的限制
不支援為產生列指定預設值。
不支援為產生列設定AUTO_INCREMENT屬性,或引用包含AUTO_INCREMENT屬性的列。
不支援在產生列運算式中使用非確定性(deterministic)函數,例如UUID()、CONNECTION_ID()、NOW()等。
不支援在產生列運算式中使用變數。
不支援在產生列運算式中使用子查詢。
不支援在INSERT/UPDATE語句中顯式指定產生列的值,產生列值的計算只能由資料庫自動完成。
與MySQL不同的限制
不支援在開啟冷資料歸檔功能的表中添加產生列。
不支援VIRTUAL/STORED類型的產生列作為分區鍵、主鍵或者唯一鍵。
不支援在VIRTUAL/STORED類型的產生列中引用儲存函數。
如果一個全域二級索引中包含了VIRTUAL/STORED類型的產生列,那麼該全域二級索引也需要包含產生列運算式中引用的所有列。
不支援在LOGICAL類型的產生列運算式中引用VIRTUAL/STORED類型的產生列。
不支援修改LOGICAL類型的產生列的類型,以及產生列的運算式中引用列的類型。
僅支援以下類型作為LOGICAL類型的產生列和其運算式中引用的列的類型:
整數類型(BIGINT、INT、MEDUMINT、SMALLINT、TINYINT);
日期類型(DATETIME、DATE、TIMESTAMP),不支援包含ON UPDATE CURRENT_TIMESTAMP屬性的日期類型列;
字串類型(CHAR、VARCHAR)。
樣本——VIRTUAL/STORED類型產生列
樣本1
使用產生列自動計算直角三角形的斜邊(斜邊等於兩個直角邊平方和的平方根):
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);
插入資料:
INSERT INTO triangle (sidea, sideb) VALUES(1,1),(3,4),(6,8);
select * from triangle;
+-------+-------+--------------------+
| sidea | sideb | sidec |
+-------+-------+--------------------+
| 1.0 | 1.0 | 1.4142135623730951 |
| 3.0 | 4.0 | 5.0 |
| 6.0 | 8.0 | 10.0 |
+-------+-------+--------------------+
樣本2
建立分區表t1,其中含有產生列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;
+---+---+
| a | b |
+---+---+
| 1 | 2 |
+---+---+
樣本——LOGICAL類型產生列樣本
LOGICAL類型的產生列可以作為分區鍵使用,可以利用產生列實現更加靈活的分區策略。此外 LOGICAL 類型的產生列也支援引用自訂函數。
樣本1:選取字串欄位的最後兩個字元作為分區鍵。
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`)
樣本2:引用自訂函數的LOGICAL類型的產生列。
建立自訂函數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 ;
建立表t3,該表中b列是一個產生列,運算式中含有自訂函數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);
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 |
+----+------+
在產生列上建立索引
PolarDB-X支援在產生列上建立索引。
注意事項
5.4.17及以上版本的企業版執行個體支援此功能。
支援在所有類型的產生列上建立局部索引。
支援在LOGICAL類型的產生列上建立全域索引。
不支援在VIRTUAL/STORED類型的產生列上建立全域索引。
樣本
樣本1:在VIRTUAL/STORED類型的產生列上建立局部索引。
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 |
+------+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
樣本2:在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 |
+------+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------+
樣本3:在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` = ?)") |
+---------------------------------------------------------------------------------------------------------------------+
運算式索引
建立索引時,如果PolarDB-X發現某個索引項目不是表中的一個列,而是一個運算式,此時PolarDB-X會自動將該運算式轉換為VIRTUAL類型的產生列並添加到表中。所有索引項目處理完成後,PolarDB-X會按照使用者定義繼續建立索引,索引定義中的運算式索引項目將被替換成對應的產生列。
注意事項
5.4.17及以上版本的企業版執行個體支援此功能。
建立運算式索引的功能預設關閉,需要開啟ENABLE_CREATE_EXPRESSION_INDEX開關後才能使用。
SET GLOBAL ENABLE_CREATE_EXPRESSION_INDEX=TRUE;
不支援全域索引。
不支援建立唯一索引。
不支援在建立表語句中建立運算式索引,需要建表後通過ALTER TABLE或者CREATE INDEX語句添加運算式索引。
使用DROP INDEX刪除運算式索引時,預設不會刪除建立運算式索引時自動建立的產生列,需要通過ALTER TABLE(DRDS模式)/ALTER TABLE(AUTO模式)手動刪除。
樣本
樣本1:建立一個運算式索引。
建立表t7。
CREATE TABLE t7 ( a BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, c varchar(32) ) DBPARTITION BY HASH(a);
建立運算式索引i。
CREATE INDEX `i` ON `t7`(substr(`c`, 2));
完成運算式索引建立之後的表結構如下:
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`)
由於索引i的索引項目是運算式,因此在表中添加了一個產生列i$0,這個產生列的運算式就索引項目的運算式。最後建立索引i,其中索引項目被替換成為對應的產生列。
建立完運算式索引之後,以下SQL就可以利用運算式索引加快查詢速度:
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 |
+------+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
執行EXPLAIN EXECUTE
命令擷取在DN上的執行計畫,可以發現確實選擇了索引i:
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 |
+------+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
樣本2:在索引中使用多個運算式。
建立表t8:
CREATE INDEX idx ON t8(
a + 1,
b,
SUBSTR(c, 2)
);
完成運算式索引建立之後的表結構:
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`)
對於索引idx,第一個和第三個索引項目是運算式,因此在表中添加了兩個產生列idx$0和idx$2,這兩個產生列的運算式就是第一個和第三個索引項目的運算式。最後建立索引idx,其中第一個和第三個索引項目被替換成為對應的產生列。
建立完運算式索引之後,以下SQL就可以利用運算式索引加快查詢速度:
SELECT * FROM t8 WHERE a+1=10 AND b=20 AND SUBSTR(c,2)='ab';
使用EXPLAIN EXECUTE擷取在DN上的執行計畫,可以發現確實選擇了索引idx:
EXPLAIN EXECUTE SELECT * FROM t4 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 |
+------+-------------+-------+------------+------+---------------+------+---------+-------------------+------+----------+-------+