表 t1(id, rank, log_time DATETIME, nickname),log_time 有索引 idx_log_time。查詢「2015-04-09 的數據」:
SELECT * FROM t1 WHERE DATE(log_time) = '2015-04-09';
EXPLAIN 結果:type=ALL,rows=5000,**全表掃描**。索引 idx_log_time 因函數 DATE() 而失效。
SELECT * FROM t1
WHERE log_time >= '2015-04-09 00:00:00'
AND log_time < '2015-04-10 00:00:00';
EXPLAIN 結果:type=range,key=idx_log_time,rows=2,**索引生效**。但 SQL 變長、業務邏輯分散、難維護。
ALTER TABLE t1
ADD COLUMN log_date DATE AS (DATE(log_time)) STORED,
ADD KEY idx_log_date (log_date);
AS (expr):虛擬列表達式,**不可寫入**,讀取時實時計算 (VIRTUAL) 或寫入時計算存儲 (STORED)。STORED:佔用磁盤空間,但查詢不再計算,**可建索引**。VIRTUAL (預設):不佔磁盤,**不可建索引** (5.7 限制,8.0 支援函數索引直接建立)。SELECT * FROM t1 WHERE log_date = '2015-04-09';
EXPLAIN 結果:type=ref,key=idx_log_date,rows=2,**精準索引命中**。
column_name data_type [GENERATED ALWAYS] AS (expression)
[VIRTUAL | STORED] [UNIQUE [KEY]] [[PRIMARY] KEY] [COMMENT 'comment']
INSERT/UPDATE 基列時需計算並寫入虛擬列,批量導入略增耗時。SHOW COLUMNS 顯示 GENERATED ALWAYS,INFORMATION_SCHEMA.COLUMNS 有 GENERATION_EXPRESSION。DATE()、YEAR()、SUBSTRING()、JSON_EXTRACT()、CAST() 等。ADD COLUMN v_name VARCHAR(50) AS (JSON_EXTRACT(data, '$.name')) STORED, ADD INDEX idx_v_name (v_name)。total_amount AS (price * quantity * (1 - discount)) STORED。ALTER TABLE t1 ADD INDEX idx_func ((DATE(log_time))),無需虛擬列,更簡潔。MySQL 5.7 虛擬列 (STORED) 完美解決了函數導致索引失效的歷史痛點,將「寫時計算、讀時直接用」的思想落地。但本質上是**設計妥協的補救手段**,新架構應優先在應用層落地計算結果,或升級 MySQL 8.0 使用原生函數索引。