1. 程式人生 > >新特性解讀 | MySQL 8.0 通用表示式

新特性解讀 | MySQL 8.0 通用表示式

原創: 楊濤濤


 

通用表示式在各個商業資料庫中比如ORACLE,SQL SERVER等早就實現了,MySQL到了8.0 才支援這個特性。這裡有兩個方面來舉例說明WITH的好處。

第一,易用性。

第二,效率。

 

舉例一 WITH表示式的易用性

我們第一個例子, 對比檢視的檢索和WITH的檢索。我們知道檢視在MySQL裡面的效率一直較差,雖說MySQL5.7 對檢視做了相關固化的優化,不過依然不盡人意。考慮下,如果多次在同一條SQL中訪問檢視,那麼則會多次固化檢視,勢必增加相應的資源消耗。

MySQL裡之前對這種消耗的減少只有一種,就是動態處理,不過一直語法較為噁心,使用不是很廣。

MySQL8.0後,又有了一種減少消耗的方式,就是WITH表示式。我們假設以下表結構:

| CREATE TABLE `t1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`rank1` int(11) DEFAULT NULL,
`rank2` int(11) DEFAULT NULL,
`log_time` datetime DEFAULT NULL,
`prefix_uid` varchar(100) DEFAULT NULL,
`desc1` text,
PRIMARY KEY (`id`),
KEY `idx_rank1` (`rank1`),
KEY `idx_rank2` (`rank2`),
KEY `idx_log_time` (`log_time`)
) ENGINE=InnoDB AUTO_INCREMENT=2037 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

 

有1000行測試記錄。

mysql> select count(*) from t1;
+----------+
| count(*) |
+----------+
| 1024 |
+----------+
1 row in set (0.00 sec)

 

這裡我們建立一個普通的檢視:

CREATE ALGORITHM=merge SQL SECURITY DEFINER VIEW `v_t1_20` AS
select count(0) AS `cnt`,`t1`.`rank1` AS `rank1`
from `t1`
group by `t1`.`rank1`
order by `t1`.`rank1`
limit 20;
  • 檢索語句A:

對視圖裡的最大和最小值欄位rank1進行過濾檢索出符合條件的記錄行數。

mysql> use ytt;
mysql> SELECT
COUNT(*)
FROM
t1
WHERE
rank1 = (SELECT
MAX(rank1)
FROM
v_t1_20)
OR rank1 = (SELECT
MIN(rank1)
FROM
v_t1_20);
+----------+
| count(*) |
+----------+
| 65 |
+----------+
1 row in set (0.00 sec)

我們用WITH表示式來重寫一遍這個查詢。

  • 查詢語句B:
mysql> use ytt;
mysql> with w_t1_20(cnt,rank1) as (
SELECT
COUNT(*), rank1
FROM
t1
GROUP BY rank1
ORDER BY rank1
LIMIT 20
) SELECT
COUNT(*)
FROM
t1
WHERE
rank1 = (SELECT
MAX(rank1)
FROM
w_t1_20)
OR rank1 = (SELECT
MIN(rank1)
FROM
w_t1_20);
+----------+
| count(*) |
+----------+
| 65 |
+----------+
1 row in set (0.00 sec)

我的函式很少,僅作功能性演示, 索引表面上看執行時間差不多, 我們來對比下兩條實現語句的查詢計劃,

  • A的計劃:

  • B的計劃:

從以上圖我們可以看出,B比A少了一次對檢視的固化,也就是說,不管我訪問WITH多少次,僅僅固化一次。有興趣的可以加大資料量,加大併發測試下效能。

 

舉例二 WITH表示式的功能性

我們第二個例子,簡單說功能性。

比如之前MySQL一直存在的一個問題,就是臨時表不能開啟多次。我們以前只有一種解決辦法就是把臨時表固化到磁碟,像訪問普通表那樣訪問臨時表。現在我們可以用MySQL8.0自帶的WITH表示式來做這樣的業務。

比如以下臨時表:

create temporary table ytt_tmp1 as
SELECT
COUNT(*) cnt, rank1
FROM
t1
GROUP BY rank1
ORDER BY rank1
LIMIT 20

我們還是用之前的查詢,這裡會提示錯誤。

mysql> select count(*) from t1 where rank1 = (select max(rank1) from ytt_tmp1) or rank1 = (select min(rank1) from ytt_tmp1)
-> ;
ERROR 1137 (HY000): Can't reopen table: 'ytt_tmp1'

現在我們可以用WITH來改變這種思路。

mysql> use ytt;
mysql> with w_t1_20(cnt,rank1) as (
SELECT
COUNT(*), rank1
FROM
t1
GROUP BY rank1
ORDER BY rank1
LIMIT 20
) SELECT
COUNT(*)
FROM
t1
WHERE
rank1 = (SELECT
MAX(rank1)
FROM
w_t1_20)
OR rank1 = (SELECT
MIN(rank1)
FROM
w_t1_20);
+----------+
| count(*) |
+----------+
| 65 |
+----------+
1 row in set (0.00 sec)

當然WITH的用法還有很多,感興趣的可以去看看手冊上的