MySQL之查詢性能優化(八)

查詢性能優化

MySQL查詢優化器的局限性

MySQL的萬能"嵌套循環"并不是對每種查詢都是最優的。不過還好,MySQL查詢優化器只對少部分查詢不適用,而且我們往往可以通過改寫查詢讓MySQL高效地完成工作。還有一個好消息,MySQL5.6版本正式發布后,會消除很多MySQL原本的限制,讓更多的查詢能夠以盡可能高的效率完成。

關聯子查詢

MySQL的子查詢實現得非常糟糕。最糟糕的一類查詢是WHERE條件中包含IN()的子查詢語句。例如,我們希望找到Sakila數據庫中,演員Penelope Guinness(他的actor_id為1)參演過的所有影片信息。很自然的,我們會按照下面的方式用子查詢實現:

mysql> SELECT * FROM sakila.film WHERE film_id IN(SELECT film_id FROM sakila.film_actor WHERE actor_id =1);

因為MySQL對IN()列表的選項有專門的優化策略,一般會認為MySQL會先執行子查詢返回所有包含actor_id為1的film_id。一般來說,IN()列表查詢速度很快,所以我們會認為上面的查詢會這樣執行:

-- SELECT GROUP_CONCAT(film_id) FROM sakila.film_actor WHERE actor_id=1;
-- Result :1,23,25,106,140,166,277,361,438,499,506,509,605,635,749,832,939,970,980
SELECT * FROM sakila.film WHERE film_id IN(1,23.....................,980);

很不幸,MySQL不是這樣做的。MySQL會講相關的外層表壓到子查詢中,它認為這樣可以更高效率地查找到數據行。也就是說,MySQL會將查詢改寫成下面的樣子:

SELECT * FROM sakila.film WHERE EXISTS (SELECT * FROM sakila.film_actor WHERE actor_id = 1 AND film_actor.film_id = film.film_id)

這時,子查詢需要根據film_id來關聯外部表film,因為需要film_id字段,所以MySQL認為無法先執行這個查詢。通過EXPLAIN可以看到子查詢是一個相關子查詢(DEPENDENT SUBQUERY)(可以使用EXPLAIN EXTENDED來查看這個查詢被改寫成了什么樣子)


mysql> EXPLAIN SELECT * FROM sakila.film WHERE EXISTS (SELECT * FROM sakila.film_actor WHERE actor_id = 1 AND film_actor.film_id = film.film_id)-> ;
+----+--------------------+------------+------------+--------+------------------------+---------+---------+---------------------------+------+----------+-------------+
| id | select_type        | table      | partitions | type   | possible_keys          | key     | key_len | ref                       | rows | filtered | Extra       |
+----+--------------------+------------+------------+--------+------------------------+---------+---------+---------------------------+------+----------+-------------+
|  1 | PRIMARY            | film       | NULL       | ALL    | NULL                   | NULL    | NULL    | NULL                      | 1000 |   100.00 | Using where |
|  2 | DEPENDENT SUBQUERY | film_actor | NULL       | eq_ref | PRIMARY,idx_fk_film_id | PRIMARY | 4       | const,sakila.film.film_id |    1 |   100.00 | Using index |
+----+--------------------+------------+------------+--------+------------------------+---------+---------+---------------------------+------+----------+-------------+
2 rows in set (0.10 sec)

根據EXPLAIN的輸出我們可以看到,MySQL先選擇對flim表進行全表掃描,然后根據返回的film_id逐個進行子查詢。如果是一個很小的表,這個查詢的糟糕的性能可能還不會引起注意,但是如果外層的表是一個非常大的表,那么這個查詢的性能會非常糟糕。當然我們很容易用下面的辦法來重寫這個查詢:

mysql>SELECT  film.* FROM sakila.film INNER JOIN sakila.film_actor USING(film_id) WHERE actor_id = 1;

另一個優化的辦法是使用函數GROUP_CONCAT()在IN()中構造一個由逗號分割的列表,有時這比上面的使用關聯改寫更快。因為使用IN()加子查詢,性能經常會非常糟,所以通常建議使用EXISTS()等效的改寫查詢來獲取更好的效率。下面是另一種改寫IN()加子查詢的辦法:

mysql>SELECT * FROM sakila.film WHERE EXISTS (SELECT * FROM sakila.film_actor WHERE actor_id = 1 AND film_actor.film_id = film.film_id)

如何用好關聯子查詢

并不是所有關聯子查詢的性能都回很差。如果有人跟你說:“別用關聯子查詢”,那么不要理他。先測試,然后做出自己的判斷。很多時候關聯子查詢是一種非常合理、自然,甚至是性能最好的寫法,看看下面的例子:

mysql> EXPLAIN SELECT film_id,language_id FROM sakila.film-> WHERE NOT EXISTS(SELECT * FROM sakila.film_actor WHERE film_actor.film_id=film.film_id)\G
*************************** 1. row ***************************id: 1select_type: PRIMARYtable: filmpartitions: NULLtype: index
possible_keys: NULLkey: idx_fk_language_idkey_len: 1ref: NULLrows: 1000filtered: 100.00Extra: Using where; Using index
*************************** 2. row ***************************id: 2select_type: DEPENDENT SUBQUERYtable: film_actorpartitions: NULLtype: ref
possible_keys: idx_fk_film_idkey: idx_fk_film_idkey_len: 2ref: sakila.film.film_idrows: 5filtered: 100.00Extra: Using index
2 rows in set, 2 warnings (0.00 sec)

一般回建議使用左外連接(LEFT OUTER JOIN)重寫該查詢,以代替子查詢。理論上,改寫后MySQL的執行計劃完全不會改變。我們來看這個例子

mysql> EXPLAIN SELECT film.film_id,film.language_id-> FROM sakila.film-> LEFT OUTER JOIN sakila.film_actor USING(film_id)-> WHERE film_actor.film_id IS NULL\G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: filmpartitions: NULLtype: index
possible_keys: NULLkey: idx_fk_language_idkey_len: 1ref: NULLrows: 1000filtered: 100.00Extra: Using index
*************************** 2. row ***************************id: 1select_type: SIMPLEtable: film_actorpartitions: NULLtype: ref
possible_keys: idx_fk_film_idkey: idx_fk_film_idkey_len: 2ref: sakila.film.film_idrows: 5filtered: 100.00Extra: Using where; Not exists; Using index
2 rows in set, 1 warning (0.00 sec)

可以看到,這里的執行計劃基本上是一樣,下面是一些微小的區別:

  • 1.表film_actor的訪問類型是一個DEPENDENT SUBQUERY,而另一個是SIMPLE.這個不同是由于語句的寫法不同導致的,一個是普通查詢,一個是子查詢。這對底層存儲引擎接口來說,沒有任何不同
  • 2.對film表,第二個查詢的Extra中沒有"Using where",但這并不重要,第二個查詢的USING子句和第一個查詢的WHERE子句實際上是完全一樣的。
  • 3.在第二個表film_actor的執行計劃的Extra列有"Not exists"。這是前面提到的提前終止算法(early-termination algorithm),MySQL通過使用"Not exists"優化來避免在表film_actor的索引中讀取任何額外的行。這完全等效于直接編寫NOT EXISTS子查詢,這個執行計劃中也是一樣,一旦匹配到一行數據,就立刻停止掃描

所以,從理論上來講,MySQL將使用完全相同的執行計劃來完成這個查詢。現實世界中,建議通過一些測試來判斷使用哪種寫法速度會更快。針對上面的案例,測試結果也是不同的,如表所示在這里插入圖片描述
.測試結果顯示,使用子查詢的寫法要略微慢些!不過每個具體的案例會各有不同,有時候子查詢寫法也會快些。例如,當返回結果中只有一個表中的某些列的時候。聽起來,這種情況對于關聯查詢效率也會更好。具體情況具體分析,例如下面的關聯,我們希望返回所有演員參演的電影,因為一個電影會有很多演員參演,所以可能會返回一些重復的記錄:

mysql> SELECT film.film_id FROM sakila.film INNER JOIN sakila.film_actor USING(film_id);

我們需要使用DISTINCT和GROUP BY來移除重復的記錄:

mysql> SELECT DISTINCT film.film_id FROM sakila.film INNER JOIN sakila.film_actor USING(film_id);

但是,回頭看看這個查詢,到底這個查詢返回的結果集意義是什么?至少這樣的寫法回訪SQL的意義很不明顯。如果使用EXISTS則很容易表達"有演員參演"的邏輯,而且不需要使用DISTINCT和GROUP BY,也不會產生重復的結果集,我們知道一旦使用了DISTINCT和GROUP BY,那么在查詢的執行過程中,通常需要產生臨時中間表。下面我們用子查詢的寫法替換上面的關聯:

mysql> SELECT film_id FROM sakila.film WHERE EXISTS(SELECT * FROM sakila.film_actor WHERE film.film_id = film_actor.film_id);

再一次,我們需要通過測試來比對這兩種寫法,哪個更快一些,測試結果如表所示.在這個案例中,我們看到子查詢速度要比關聯查詢更快些。通過上面這個案例,主要想說明兩點:一時不需要聽取那些關于子查詢的"絕對真理",二十應該用測試來驗證對子查詢的執行計劃和相應時間的假設。我們應該通過測試來驗證猜想在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/23440.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/23440.shtml
英文地址,請注明出處:http://en.pswp.cn/web/23440.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Java開發注意事項

注意:測試類中使用Autowired注解注入Bean,不要使用RequiredArgsConstructor注解注入Bean 正確示范: import org.springframework.boot.test.context.SpringBootTest; import org.springframework.beans.factory.annotation.Autowired;SpringBootTest c…

Ffmpeg安裝和簡單使用

Ffmpeg安裝 下載并解壓 進入官網 (https://ffmpeg.org/download.html),選擇 Window 然后再打開的頁面中下滑找到 release builds,點擊 zip 文件下載 環境變量配置 下載好之后解壓,找到 bin 文件夾,里面有3個 .exe 文件 然后復制…

中國互聯網第一人的故事__許榕生的不平凡的經歷

中國互聯網第一人的故事__許榕生的不平凡的經歷 目錄 零 高考之際談高考成功者 一 幸運的高考考生 二 抓住時機考研上岸 三 當年連接互聯網的經過 四 互聯網進入中國的緣由 五 互聯網一誕生就顯神威 六 互聯網強國之路,我們在路上 零…

優思學院|六西格瑪黑帶官方的報考條件是什么?

經常有人私信問我六西格瑪黑帶證書要如何取得,要學歷證明嗎?要帶項目嗎?要注冊嗎? 首先,直接一點說,和任何學科一樣,取得六西格瑪證書的方法主要是通過上課學習和考試。然而,關于六…

fastapi實例

quick start 安裝 pip install fastapi# ASGI服務器,生成環境可以使用uvicorn pip install uvicorn代碼 from fastapi import FastAPI import uvicorn# 創建一個app實例 app FastAPI()# 編寫一個路徑操作裝飾器 app.get("/") # 編寫一個路徑操作函數 …

【ARM Cache 及 MMU 系列文章 6.2 -- ARMv8/v9 Cache 內部數據讀取方法詳細介紹】

請閱讀【ARM Cache 及 MMU/MPU 系列文章專欄導讀】 及【嵌入式開發學習必備專欄】 文章目錄 Direct access to internal memoryL1 cache encodingsL1 Cache Data 寄存器Cache 數據讀取代碼實現Direct access to internal memory 在ARMv8架構中,緩存(Cache)是用來加速數據訪…

Apple開發者macOS描述文件創建

1.選擇Profiles然后點擊加號創建 2.選擇類型為macOS App Development然后點擊繼續 3.選擇描述類型與App ID 然后點擊繼續 4.選擇證書然后點擊繼續 5.選擇設備,然后點擊繼續 6.輸入描述文件后,點擊生成 生成成功,點擊下載描述文件 下載完成會自動打開描述文件

評判基金的重要指標(一):最大回撤率

評判基金的重要指標(一):最大回撤率 財富奇跡創造者2023-06-14 08:00山西 問:如果要投資一只基金,如何衡量自己可能面對的風險呢? 答:衡量一個策略的風險控制能力,“最大回撤”是…

Facechain系列: 通過代碼進行推理

進行推理時,需要編輯run_inference.py中的代碼。為了避免人物肖像的版權問題,文章中使用的圖片不是由FaceChain實際生成的圖片,特此說明。 1. 以下代碼適用于Linux系統,如果希望在Windows系統中運行, folder_path f…

lm studio 0.2.24國內下載模型

1.修改C:\Users\Admin\AppData\Local\LM-Studio\app-0.2.24\resources\app\.webpack\main中的3個js文件: index.js llmworker.js worker.js 中替換huggingface.co為hf-mirror.com。這樣就能實現搜索模型文件 2.點擊模型,選擇下載,出現下載…

C語言小例程6/100

題目:輸入三個整數x,y,z,請把這三個數由小到大輸出。 程序分析:我們想辦法把最小的數放到x上,先將x與y進行比較,如果x>y則將x與y的值進行交換,然后再用x與z進行比較,如果x>z則將x與z的值…

docker安裝和使用

1. docker-ce Docker Community Edition (CE): 功能: 這是 Docker 的主要組件,用于創建、管理和運行容器。它包括 Docker 守護進程 (dockerd),該守護進程負責處理容器的生命周期,包括創建、啟動、停止和刪除容器。用途: 允許用戶在其系統上…

T-Rex2: Towards Generic Object Detection via Text-Visual Prompt Synergy論文解讀

提示:文章寫完后,目錄可以自動生成,如何生成可參考右邊的幫助文檔 文章目錄 前言一、引言二、文獻綜述1. Text-prompted Object Detection2. Visual-prompted Object Detection3. Interactive Object Detection 三、模型方法1. Visual-Text P…

Docker自定義鏡像實現(SpringBoot程序為例)

?作者簡介:大家好,我是 Meteors., 向往著更加簡潔高效的代碼寫法與編程方式,持續分享Java技術內容。🍎個人主頁:Meteors.的博客💞當前專欄:知識備份?特色專欄:知識分享&#x1f96…

Java核心: 為圖片生成水印

今天干了一件特別不務正業的事,做了一個小程序用來給圖片添加水印。事情的起因是需要將自己的身份證照片分享給別人,手邊并沒有一個趁手的工具來生成圖片水印。很多APP提供了水印的功能,但會把我的圖片上傳到他們的服務器,身份證太…

AdSet通過審核并入駐全國SDK管理服務平臺

SDK、API、H5是三種常見的APP廣告接入方式,目前市面上使用最廣泛的還是SDK對接,通過使用廣告SDK,App開發者可以在App中展示廣告商投放的廣告,進而根據用戶的點擊賺取收益。具備一定規模流量、想快速獲得收益的APP開發者都會考慮接…

使用#sortablejs插件對表格中拖拽行排序#Vue3#后端接口數據

使用#sortablejs對表格中拖拽行排序#Vue3#后端接口數據 *效果&#xff1a; 拖動表格行排序 首先安裝插件sortable npm install sortablejs --save代碼&#xff1a; <template><!-- sortable.js 進行表格排序 --><!-- 演示地址 --><div class"dem…

618值得推薦的洗地機有哪些?附上最全洗地機選購攻略

洗地機的出現&#xff0c;讓家庭清潔變得越來越高效&#xff0c;它省時省力的洗地方式&#xff0c;自帶水箱和除菌模式&#xff0c;還能減輕我們家庭清潔的負擔&#xff0c;但由于目前市面上家用洗地機品牌和種類眾多&#xff0c;讓大家挑選起來比較困難。那么家用洗地機哪個品…

Go微服務: 關于分布式系統中的常見問題,分布式事務,以及常用解決方案

概述 在當今的互聯網時代&#xff0c;分布式系統已成為支撐大規模服務、高并發和高性能應用的基石它們通過網絡連接多臺計算機&#xff0c;協同工作&#xff0c;共同完成任務&#xff0c;但這也引入了諸如數據一致性、網絡延遲、容錯性等挑戰解決這些問題的關鍵在于設計和實施…

String,StringBuffer,StringBuilder的區別?

String是不可變的&#xff0c;StringBuffer和StringBuilder是可變的。StringBuffer是線程安全的&#xff0c;StringBuilder是非線程安全的。 String的 是如何實現的 使用拼接字符串&#xff0c;其實只是Java提供的一個語法糖。 其實String的 底層是new 了一個StringBuilde…