不可置信!SQL 優化終於幹掉了“distinct”

sql 優化之多表聯合查詢幹掉 “distinct” 去重關鍵字

在我提交了代碼的時候,架構師給我指出我這個sql這樣寫會有問題。因爲在分庫分表的時候,是不支持子查詢的。

所以需要把多表的子查詢的 sql 結構進行優化。

是不是挺恐怖的;(此處爲了脫敏,我把相關的 sql 關鍵詞都給打碼掉了)

這個 sql 的執行步驟如下: 1、查詢出來 d 表中的某個 id 字段包含多個 id 值的所有的數據(因爲此表是 1-n 的關係,所以需要去重,僅需要拿到不重複的 id 纔可以繼續下一個步驟);可以看到此步驟我把查詢出來的多個值的結果給生成的了一個子表名爲 sss;

2、下一個步驟就是需要進行排序(以時間進行倒序排序,因爲要在前臺進行按時間進行展示);

3、第 3 步就是把這些結果與 a 表進行合併,查詢出來排序後的每個 id 的信息;然後進行分頁處理;

其他的可以不必關心,最終要的是去重關鍵字(DISTINCT),拿小本本記號,一會要考哦。
實踐是驗證真理的唯一標準

例如有下表:

可以看到name和product_unit列的值都有可能是重複的。

mysql> SELECT t1.id,t1.name,t1.product_unit  FROM dd_product_category t1;
+
| id | name     | product_unit |
+
| 55 | 飲料     | 瓶           |
| 56 | 飲料     | 箱           |
| 57 | 零食     | 包           |
| 59 | 膨化食品 | 袋           |
| 60 | 方便食品 | 箱           |
| 61 | 自熱火鍋 | 碗           |
| 62 | 方便麪   | 箱           |
| 63 | 礦泉水   | 箱           |
| 64 | 糖果     |              |
| 65 | 酒類     | 箱           |
| 66 | 烈酒     | 箱           |
| 67 | 啤酒     | 箱           |
| 68 | 預調酒   | 箱           |
+
13 rows in set (0.13 sec)

mysql> 
mysql> 

如何我們想只拿到name或者product_unit列的值並且不想要重複的值該怎麼辦?

1、拿到單個值是好拿的,但是是存在重複的數據的,這些重複的數據我們只保留一個就可以了,那麼該怎麼做呢?

mysql> SELECT t1.product_unit  FROM dd_product_category t1;
+
| product_unit |
+
| 瓶           |
| 箱           |
| 包           |
| 袋           |
| 箱           |
| 碗           |
| 箱           |
| 箱           |
|              |
| 箱           |
| 箱           |
| 箱           |
| 箱           |
+
13 rows in set (19.31 sec)

mysql> 

2、去除重複列

mysql> 
mysql> SELECT DISTINCT t1.product_unit  FROM dd_product_category t1;
+
| product_unit |
+
| 瓶           |
| 箱           |
| 包           |
| 袋           |
| 碗           |
|              |
+
6 rows in set (0.11 sec)

mysql> 

是不是很簡單,雖然看着簡單,但是如果多表子查詢的時候,就會出現問題,例如你想要查詢表 a,b,c 三個表的數據,這三個表必然都是有關係的。

a 和 b 是 1-n 的關係。但是你只有 b 表中 id,你需要先查詢出來 b 表的數據,然後利用 b 表的數據去查詢 a 表的數據,然後再去查詢 c 表的數據。

想必肯定是很繞的。

整個過程中你肯定是需要去重的

當整個 sql 寫完,基本上跟我寫的優化前的 sql 也就差不多了。(多表嵌套,多 sql 嵌套 sql,啦啦啦一大堆)。

優化思路還是有很多的,當時能想到的就是把這個複雜的 sql 拆分成多個簡單的 sql 執行,然後使用 Java 後臺代碼進行處理。(對於不甘於現狀的我,想找到一個比這個更友好的解決方案的我,我是不會屈服這個問題的。)

說到這裏,先給大家放上一個鏈接:

  • 1、(Mysql5.7 官方手冊中提及到的關於優化 distinct 的方法) dev.mysql.com/doc/refman/…
  • 2、還有一個優化 group by 的: dev.mysql.com/doc/refman/…

推薦大家閱讀。

Mysql5.7 官方手冊中提及到的關於優化 distinct 的方法,原文如下:

MySQL 5.7 Reference Manual / … / DISTINCT Optimization

8.2.1.16 DISTINCT Optimization

DISTINCT combined with ORDER BY needs a temporary table in many cases.

distinct 與 order by 結合的許多情況下需要建一個臨時表;

Because DISTINCT may use GROUP BY, learn how MySQL works with columns in ORDER BY or HAVING clauses that are not part of the selected columns. See Section 12.20.3, “MySQL Handling of GROUP BY”.

因爲distinct可能使用group by,瞭解MySQL如何處理按order by 列或者具有不屬於所選列的子句。見12.20.3節, “MySQL Handling of GROUP BY”.

In most cases, a DISTINCT clause can be considered as a special case of GROUP BY. For example, the following two queries are equivalent:

在大多數情況下,一個不同的子句可以被認爲是group by 的特殊情況。例如下面這兩個查詢是等價的:
SELECT DISTINCT c1, c2, c3 FROM t1
WHERE c1 > const;
SELECT c1, c2, c3 FROM t1
WHERE c1 > const GROUP BY c1, c2, c3;

Due to this equivalence, the optimizations applicable to GROUP BY queries can be also applied to queries with a DISTINCT clause. Thus, for more details on the optimization possibilities for DISTINCT queries, see Section 8.2.1.15, “GROUP BY Optimization”.

由於這種等價性,適用於group by查詢的優化,也可以應用於具有不同子句的查詢。因此,關於distinct的查詢優化的更多細節可以參考Section 8.2.1.15, “GROUP BY Optimization”.

When combining LIMIT row_count with DISTINCT, MySQL stops as soon as it finds row_count unique rows.

當 row_count 與 distinct 一起使用時,MySQL 一旦發現 row_count 是唯一的行,就會停止。

If you do not use columns from all tables named in a query, MySQL stops scanning any unused tables as soon as it finds the first match. In the following case, assuming that t1 is used before t2 (which you can check with EXPLAIN), MySQL stops reading from t2 (for any particular row in t1) when it finds the first row in t2:

如果在查詢中不適用來自所有表的列,MySQL 一旦找到第一個匹配項就會停止掃描任何未使用的表。

在下面的例子中,假設 t1 在 t2 之前使用(你可以使用 explanin 來檢查),MySQL 在找到 t2 的第一行時停止從 t2 讀取(對於 t1 中的任何特定行)。

SELECT DISTINCT t1.a FROM t1, t2 where t1.a=t2.a;

官方的手冊中寫到的,真是句句扣心呀!!!

總結有以下比較重要的幾點:

  • 1、distinct 與 group by 幾乎等價;
  • 2、distinct 的相關優化與 group by 的查詢優化方法是等價的;

我們抱着試試看的態度,去做個試驗。

就以下列這個效果爲最終目的好了:

mysql> 
mysql> SELECT DISTINCT t1.product_unit  FROM dd_product_category t1;
+
| product_unit |
+
| 瓶           |
| 箱           |
| 包           |
| 袋           |
| 碗           |
|              |
+
6 rows in set (0.11 sec)

mysql> 

使用 group by 去重:

mysql> select  t1.product_unit from dd_product_category t1 group by t1.product_unit;
+
| product_unit |
+
|              |
| 包           |
| 瓶           |
| 碗           |
| 箱           |
| 袋           |
+
6 rows in set (19.46 sec)

mysql> 
可以看到,最終拿到的數據是一模一樣的。
那麼我們試驗是成功的,distinct的效果和group by的效果是一樣的。
那麼我們優化distinct就變向的去優化group by了(我優化前的sql並未使用group by所以談不上優化group by,只能說是把distinct的複雜sql改造成group by 的sql)。

打開我前面提到的這個優化 group by 的官方手冊: dev.mysql.com/doc/refman/…

由於原文比較長,這裏就不在過多贅述。

現在需要做的就是把 distinct 改造成 group by 的 sql 語法的寫法。

怎麼樣,改造後的 sql,是不是還挺清爽的。

1、我們扔掉了多個嵌套sql;

2、也不用去生成一個sss的臨時表了

對於本人而言學到了:

  • 1、distinct 與 group by 幾乎等價;
  • 2、distinct 的相關優化與 group by 的查詢優化方法是等價的;
  • 3、如果 distinct 的不能讓 sql 最優化,那麼可以嘗試着使用 group by 的方式去改造一下。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章