模版網站建設怎么做一個自己的網頁
一般來說一個好的程序:查詢優(yōu)化,索引優(yōu)化,庫表結構要同時進行優(yōu)化。今天我們來講一下查詢優(yōu)化。
我們需要對MySQL的架構有基本認知,所以這里貼一張圖大家看看:
圖片來自于《小林coding》
為什么從查詢會慢?
查詢的生命周期大概可以按照如下順序來看:從客戶端到服務器,然后在服務器上進行語法解析,生成執(zhí)行計劃,執(zhí)行,并給客戶端返回結果。執(zhí)行是整個生命周期中最重要的一個階段,其中包括了大量為了檢索數據對存儲引擎的調用以及調用后的數據處理,包括排序,分組等。在這些過程中有很多地方需要消耗大量時間,例如:網絡,CPU計算,生成統(tǒng)計信息和執(zhí)行計劃,鎖等待(互斥等待)。因為查詢可能會很慢。
慢查詢基礎:優(yōu)化數據訪問
我們需要注意兩個點:
- 確認應用程序是否在檢索大量且不必要的數據。這通常意味著訪問了太多的行,但有時候也可能是訪問了太多的列。
- 確認MySQL服務器層是否在分析大量不需要的數據行。
是否向數據庫請求了不需要的數據
以下是一些典型案例:
- 查詢了不需要的數據,沒有加limit,但是前端一個分頁只能顯示一部分,而你把數據全部查了給前端了。
- 多表聯接時返回全部列,但是實際上只需要返回你需要的列就可以了,沒有必要的全部。
- 總是取出全部的列,每次當你使用
SELECT*
的時候,你都必須思考清楚你是不是真的需要所有列的數據。但是取出全部的列在有的時候也并不是一件壞事,我們可能有緩存機制,取出全部的列可能會對緩存機制有好處,但是你必須知道這樣做的代價是什么。 - 查復查詢相同的數據:遇到常用的數據我們一定要用緩存存起來。
MySQL是否在掃描額外的記錄
在MySQL中有3個最簡單的衡量查詢開銷的指標:
- 響應時間
- 掃描的行數
- 返回的行數
這三個指標會被記錄到MySQL的慢日志中。
響應時間
響應時間 = 服務時間 + 排隊時間
服務時間是真正執(zhí)行查詢使用了多少時間,排隊時間是服務器等待某些資源而沒有真正執(zhí)行查詢的時間 – 可能是等待I/O操作完成。
掃描的行數和返回的行數
掃描的行數和返回的行數大多數情況是相同的,但是在做一個聯接查詢的時候,服務器必須要掃描多行才能生成結果集中的一行。
掃描的行數和訪問類型
我們在評估查詢開銷的時候,有一個語句是非常好的,這個語句叫做EXPLAIN
,EXPLAIN
語句中的type列反應了訪問類型。訪問類型有很多種
- 全表掃描
- 索引掃描
- 范圍掃描
- 唯一索引查詢
- 常數引用等
這里列出的速度是從慢到快。
我們來把這些查詢類型詳細的講解一下,因為這個很重要:
1. ALL
全表掃描(Full Table Scan), MySQL將遍歷全表以找到匹配的行.
mysql> explain select * from film where rating ='G';
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | film | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 20.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
film 表中 rating 字段沒有索引.
2. index
全索引掃描(Full Index Scan), index 與 ALL 區(qū)別為 index 類型只遍歷索引樹. MYSQL 遍歷整個索引來查找匹配的行.
mysql> explain select title from film;
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+------+----------+-------------+
| 1 | SIMPLE | film | NULL | index | NULL | idx_title | 514 | NULL | 1000 | 100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+------+----------+-------------+
雖然 where 條件中沒有用到索引, 但是要取出的列 title 是索引包含的列, 所以只要全掃描 title 索引即可, 直接使用索引樹查找數據.
3. range
索引范圍掃描, 常見于 ‘<’, ‘<=’, ‘>’, ‘>=’, ‘between’ 等操作符.
mysql> explain select * from film where film_id > 100;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | film | NULL | range | PRIMARY | PRIMARY | 2 | NULL | 900 | 100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
因為 film_id 是索引, 所以只要查找索引的某個范圍即可, 通過索引找到具體的數據.
4. ref
使用非唯一性索引或者唯一索引的前綴掃描, 返回匹配某個單獨值的記錄行.
mysql> explain select * from payment where customer_id = 10;
+----+-------------+---------+------------+------+--------------------+--------------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+--------------------+--------------------+---------+-------+------+----------+-------+
| 1 | SIMPLE | payment | NULL | ref | idx_fk_customer_id | idx_fk_customer_id | 2 | const | 25 | 100.00 | NULL |
+----+-------------+---------+------------+------+--------------------+--------------------+---------+-------+------+----------+-------+
customer_id 在 payment 表中是非唯一性索引
5. eq_ref
類似ref, 區(qū)別就在使用的索引是唯一索引. 在聯表查詢中使用 primary key 或者 unique key 作為關聯條件.
mysql> explain select * from film a left join film_text b on a.film_id = b.film_id;
+----+-------------+-------+------------+--------+---------------+---------+---------+------------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+--------+---------------+---------+---------+------------------+------+----------+-------------+
| 1 | SIMPLE | a | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | NULL |
| 1 | SIMPLE | b | NULL | eq_ref | PRIMARY | PRIMARY | 2 | sakila.a.film_id | 1 | 100.00 | Using where |
+----+-------------+-------+------------+--------+---------------+---------+---------+------------------+------+----------+-------------+
6. const/system
當 MySQL 對查詢某部分進行優(yōu)化, 并轉換為一個常量時, 使用這些類型訪問. 如將主鍵置于 where 列表中, MySQL 就能將該查詢轉換為一個常量, system 是 const 類型的特例, 當查詢的表只有一行的情況下使用 system.
mysql> explain select * from film where film_id = 1;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | film | NULL | const | PRIMARY | PRIMARY | 2 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
7. NULL
MySQL 不用訪問表或者索引就直接能到結果.
mysql> explain select 1 from dual where 1;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
dual是一個虛擬的表, 可以直接忽略.
MySQL有三種方式使用WHERE
:
- 在索引中使用
WHERE
條件來過濾不匹配的記錄。這是在存儲引擎層完成的。 - 使用索引覆蓋,直接從索引中過濾不需要的記錄并返回命中結果,這個過程不需要進行回表查詢。
- 從數據表中返回數據,然后過濾不滿足條件的記錄。這是在MySQL服務器層完成的。
如果我們發(fā)現查詢時需要掃描大量數據但只返回少量數行,那么通常可以嘗試下面的技巧去優(yōu)化它。
- 使用索引覆蓋掃描
- 改變庫表結構
- 重寫復雜查詢
重構查詢的方法
切分查詢
有的時候對于一個大查詢,我們需要分而治之,將大查詢切分成小查詢,每個查詢的功能完全一樣,只完成一小部分,每次只返回一小部分查詢結果。
例如刪除舊數據。定期刪除大量數據的時候,如果用一個大的語句一次性完成的話,則可能需要一次鎖住很多數據,占慢整個事務日志,耗盡系統(tǒng)資源,阻塞很多小但是重要的查詢。例如這個:
DELETE FROM messages WHERE created < DATE_SUB(NOW(), INTERVAL 3 MONTH)
可以優(yōu)化成這個樣子:
rows_affected = 0;
do {rows_affected = do_query("DELETE FROM messages WHERE created < DATE_SUB(NOW(), INTERVAL 3 MONTH) LIMIT 10000");// 每次操作完成之后睡一會兒sleep(10000);
} while (row_affected > 0);
分解聯接查詢
對于聯接查詢,可以對每一張表進行一次單表查詢,然后將結果在應用程序中進行聯接。例如下面這個查詢:
SELECT * FROM tag JOIN tag_post ON tag_post.tag_id = tag.id JOIN post ON tag_post.post_id = post.id WHERE tag.tag = 'mysql';
可以分解成下面這些查詢:
SELECT * FROM tag WHERE = 'mysql';
SELECT * FROM tag_post WHERE tag_id = 1234;
SELECT * FROM post WHERE post.id in (123, 465, 78);
為什么要這樣做呢?
- 讓緩存效率更高。許多應用程序可以方便的緩存單表查詢的結果對象。例如上面查詢的tag mysql已經被緩存了,那么應用就可以跳過第一個查詢。
- 在查詢分解后,執(zhí)行單個查詢可以減少鎖的競爭。
- 在應用層做聯接可以更容易對數據庫進行拆分,更容易做到高性能和可拓展。
- 可以減少冗余記錄的訪問。在應用層做聯接查詢,意味著對于某條記錄應用只需要查詢一次,而在數據庫中做聯接查詢則可能需要重復的訪問一部分數據。從這點看,這樣的重構可能會減少網絡和內存的消耗。
- 查詢本身的效率可能會提升。在這個例子中,使用IN()代替聯接查詢,可以讓MySQL按照ID順序進行查詢,這可能比隨機的聯接要更高效。
查詢執(zhí)行的基礎
MySQL的客戶端/服務器通信協(xié)議
MySQL的客戶端和服務器之間的通信協(xié)議是半雙工的,這意味著在同一時刻,要么是服務器向客戶端發(fā)送數據,要么是客戶端向服務器發(fā)送數據,這兩個動作不能同時發(fā)生。這樣帶來的問題就是無法進行流量控制。這里有一個小細節(jié):
多數連接MySQL的庫函數都可以獲得全部結果集并將結果緩存到內存里,還可以逐行獲取需要的數據。默認一般是獲得全部結果集并將他們緩存到內存中。MySQL需要等所有的數據全部發(fā)送完畢才能釋放這條查詢所占用的資源。
查詢狀態(tài)
Sleep
線程正在等待客戶端發(fā)送新的請求。
Query
線程正在執(zhí)行查詢或者正在將結果發(fā)送給客戶端。
Locked
在MySQL服務器層,該線程正在等待表鎖。在存儲引擎級別實現的鎖,例如InnoDB的行鎖,并不會體現在線程狀態(tài)中。對于MyISAM來說這是一個比較典型的狀態(tài),但在其他沒有行鎖的引擎中也經常會出現。
Analyzing and statistics
線程正在收集存儲引擎的統(tǒng)計信息,并生成查詢的執(zhí)行計劃。
Copying to tmp table [on disk]
線程正在執(zhí)行查詢,并且將其結果集都復制到一個臨時表中,這種狀態(tài)一般要么是在做GROUP BY操作,要么是文件排序操作,或者是UNION操作。如果這個狀態(tài)后面還有“on disk”標記,那表示MySQL正在將一個內存臨時表放到磁盤上。
Sorting result
線程正在對結果集進行排序。
Sending data
這表示多種情況:線程可能在多個狀態(tài)之間傳送數據,或者在生成結果集,或者在向客戶端返回數據。
語法解析器和預處理
MySQL通過關鍵字將SQL語句進行解析,并生成一顆對應的解析樹。這里會檢測語法錯誤,是否使用了錯誤的關鍵字,關鍵字的順序是否正確,引號是否能夠前后匹配等。
然后預處理器檢查生成的解析樹,檢查數據表和數據列是否存在,還是解析名字和別名看看是否有歧義等
下一步預處理器會驗證權限。
查詢優(yōu)化器
到這里為止解析樹是已經完全合法的了,優(yōu)化器會將其轉換成查詢執(zhí)行計劃。一條查詢可以有很多種執(zhí)行方式,最后都返回相同的結果。優(yōu)化器的作用就是找到最好的執(zhí)行計劃。
MySQL使用基于成本的優(yōu)化器。它將嘗試預測一個查詢使用某種執(zhí)行計劃時的成本,并選擇其中成本最小的一個。最初成本的最小單位是隨機讀取一個4KB數據頁的成本,后來引入了一些因子來進行估算。巨復雜!!!雖然查詢優(yōu)化是可能有問題的,但是讀者不要為了這個可能產生的問題費盡心思,因為您不一定可以比查詢優(yōu)化器做的更好了。
優(yōu)化策略可以分成兩種:
- 靜態(tài)優(yōu)化
- 動態(tài)優(yōu)化
靜態(tài)優(yōu)化在第一次完成之后就一直有效,即使使用不同的參數重復執(zhí)行查詢頁不會發(fā)生變化,可以認為是一種編譯時優(yōu)化。而動態(tài)優(yōu)化跟上下文有關,可以理解為運行時優(yōu)化。
下面是MySQL能夠處理的一些優(yōu)化類型:
-
重新定義聯接表的順序
-
將外聯接轉換成內聯接
-
使用代數等價變換規(guī)則。例如:
(5 = 5 AND b = c) AND a = 5 可以改寫成 a > 5
就是類似這樣的優(yōu)化。
-
優(yōu)化COUNT(), MIN(), MAX()。例如:我想要找某一列的最小值,只需要查詢對應B-Tree索引最左端的記錄,MySQL可以直接獲取第一行記錄,最大值就可以直接獲取最后一行記錄。
-
預估并轉換為常數表達式。
-
覆蓋索引優(yōu)化
-
子查詢優(yōu)化
-
提取終止查詢。當遇到LIMIT子句的時候,MySQL會自動中止等
-
等值傳播。如果兩列的值可以通過等式聯接,那么MySQL能夠把其中一列的WHERE條件
-
列表IN()的比較。列表IN()的比較。在很多數據庫系統(tǒng)中,IN()完全等同于多個OR條件的子句,因為這兩者是完全等價的。在MySQL中這點是不成立的,MySQL將IN()列表中的數據先進行排序,然后通過二分查找的方式來確定列表中的值是否滿足條件,這是一個O(log n)復雜度的操作,等價地轉換成OR查詢的復雜度為O(n),對于IN()列表中有大量取值的時候,MySQL的處理速度將會更快。
表和索引的統(tǒng)計信息
服務器層有查詢優(yōu)化器,但是數據和索引的統(tǒng)計信息是在存儲引擎層實現的。因此MySQL查詢執(zhí)行計劃的時候,需要向存儲引擎層獲取響應的統(tǒng)計信息。常見的統(tǒng)計信息有:
- 每個表或者索引有多少個頁面
- 每個表的每個索引的基數是多少,數據行和索引的長度是多少
- 索引的分布信息等
MySQL如何執(zhí)行聯接查詢
MySQL認為每一個查詢都是聯接 – 不僅僅是匹配兩張表中對應的查詢,而是每一個查詢,每一個片段(包括子查詢,甚至基于單表的SELECT)都是聯接。
對于UNION查詢,MySQL先將一系列的單個查詢結果放到一個臨時表中,然后重新讀出臨時表中的數據來完成UNION查詢。在MySQL概念中,每個查詢都是一次聯接,所以讀取臨時表的結果也是一次聯接。
MySQL的聯接執(zhí)行策略是:MySQL對任何聯接都執(zhí)行嵌套循環(huán)連接操作,即MySQL先在一個表中循環(huán)讀取出單條數據,然后再嵌套循環(huán)到下一個表中尋找匹配的行。依次下去,直到找到所有表中匹配的行為止。最后根據各個表匹配的行,返回查詢中需要的各列。
執(zhí)行計劃
我們用一張圖來表示MySQL的聯接查詢執(zhí)行計劃:
聯接查詢優(yōu)化器
聯接優(yōu)化器會嘗試在所有的聯接順序中選擇一個成本最低的來生成執(zhí)行計劃樹。如果可能,優(yōu)化器會遍歷每一個表,然后逐個做嵌套循環(huán),計算執(zhí)行每一棵可能的計劃樹的成本,最后返回一個最優(yōu)的執(zhí)行計劃。但是這個代價可能有點大,所以優(yōu)化器選擇使用貪婪搜索的方式查找最優(yōu)的聯接順序。當需要聯接的表超過optimizer_search_depth
的限制的時候,就會選擇貪婪搜索模式了。
排序優(yōu)化
排序是一個成本很高的操作,所以從性能角度考慮,應盡可能避免排序或者盡可能避免對大量數據進行排序。當不能使用索引生成排序結果的時候,MySQL需要自己排序,如果數據量小的話在內存中進行,如果數據量大則需要使用磁盤。如果需要排序的數據量小于排序緩沖區(qū),MySQL使用內存進行快速排序操作。如果內存不夠排序,那么MySQL會先將數據分塊,對每個獨立的塊使用快速排序進行排序,并將各個塊的排序結果存放在磁盤上,然后將各個排序好的塊進行合并。
查詢執(zhí)行引擎
調用存儲引擎實現的接口來完成,這些接口就是我們說的handler API的接口。查詢中的每一個表都由一個handler的實例表示。如果一個表在查詢中出現了三次,服務器就會創(chuàng)建三個handler對象,handler里面存儲了表的所有列名,索引統(tǒng)計信息等等。
將結果返回給客戶端
MySQL將結果集返回客戶端是一個增量、逐步返回的過程。例如,對于關聯操作,一旦服務器處理完最后一個關聯表,開始生成第一條結果時,MySQL就可以開始向客戶端逐步返回結果集了。
MySQL查詢優(yōu)化器的局限性
等值傳遞
有些時候,等值傳遞會帶來一些意想不到的額外消耗。例如一列上的巨大IN()列表,優(yōu)化器知道它將等于其他表中的一些列,這是由于WHERE, ON 或者 USING子句使列彼此相等。
優(yōu)化器通過將列表復制到所有相關表中的相應列來“共享”列表。通過因為各個表新增了過濾條件,所以優(yōu)化器可以高效地從存儲引擎過濾記錄。但是如果這個列表非常大,則會導致優(yōu)化和執(zhí)行都會變慢。
并行執(zhí)行
MySQL無法利用多核特定來并行執(zhí)行查詢。
在同一個表中查詢和更新
MySQL不允許對一張表同時進行查詢和更新。
優(yōu)化特定類型的查詢
優(yōu)化COUNT()查詢
COUNT()有兩個不同的作用:
- 統(tǒng)計某個列值的數量,即統(tǒng)計某列值不為NULL的個數。
- 統(tǒng)計行數。
當使用COUNT(*)時,統(tǒng)計的是行數,它會忽略所有的列而直接統(tǒng)計所有的行數。而在括號中指定了一個列的話,則統(tǒng)計的是這個列上值不為NULL的個數。
可以考慮使用索引覆蓋掃描或增加匯總表對COUNT()進行優(yōu)化。
優(yōu)化LIMIT和OFFSET子句
處理分頁會使用到LIMIT,當翻頁到非常靠后的頁面的時候,偏移量會非常大,這時LIMIT的效率會非常差。例如對于***LIMIT 10000,20***這樣的查詢,MySql需要查詢10020條記錄,將前面10000條記錄拋棄,只返回最后的20條。這樣的代價非常高,如果所有的頁面被訪問的頻率都相同,那么這樣的查詢平均需要訪問半個表的數據。
優(yōu)化此類分頁查詢的一個最簡單的辦法就是盡可能地使用索引覆蓋掃描,而不是查詢所有的列。然后根據需要與原表做一次關聯操作返回所需的列。對于偏移量很大的時候,這樣的效率會提升非常大。考慮下面的查詢:
SELECT film_id, description FROM sakila.film ORDER BY title LIMIT 50, 5;
如果這個表非常大,那么這個查詢最好改寫成下面的這樣子:
SELECT film.film_id, film.description FROM sakila.film
INNER JOIN
(SELECT film_id FROM sakila.film ORDER BY title LIMIT 50,5) AS lim
USING(film_id);
注意優(yōu)化中關聯的子查詢,因為只查詢film_id一個列,數據量小,使得一個內存頁可以容納更多的數據,這讓MySQL掃描盡可能少的頁面。在獲取到所需要的所有行之后再與原表進行關聯以獲得需要的全部列。
LIMIT的優(yōu)化問題,其實是OFFSET的問題,它會導致MySql掃描大量不需要的行然后再拋棄掉??梢越柚鷷灥乃枷胗涗浬洗稳祿奈恢?#xff0c;那么下次就可以直接從該書簽記錄的位置開始掃描,這樣就避免了使用OFFSET??梢园阎麈I當做書簽使用,例如下面的查詢:
SELECT * FROM sakila.rental ORDER BY rental_id DESC LIMIT 20;
假設上面的查詢返回的是主鍵為16049到16030的租借記錄,那么下一頁查詢就可以直接從16030這個點開始:
SELECT * FROM sakila.rental WHERE rental_id < 16030
ORDER BY rental_id DESC LIMIT 20;
該技術的好處是無論翻頁到多么后面,其性能都會很好。
此外,也可以用關聯到一個冗余表的方式提高LIMIT的性能,冗余表只包含主鍵列和需要做排序的數據列。
優(yōu)化UNION查詢
MySQL總是通過創(chuàng)建并填充臨時表的方式來執(zhí)行UNION查詢,因此很多優(yōu)化策略在UNION查詢中都沒法很好地使用。經常需要手工地將WHERE、LIMIT、ORDER BY等子句“下推”到UNION的各個子查詢中,以便優(yōu)化器可以充分利用這些條件進行優(yōu)化(例如,直接將這些子句冗余地寫一份到各個子查詢)。
除非確實需要服務器消除重復的行,否則就一定要使用UNION ALL,這一點很重要。如果沒有ALL關鍵字,MySQL會給臨時表加上DISTINCT選項,這會導致對整個臨時表的數據做唯一性檢查。這樣做的代價非常高。即使有ALL關鍵字,MySQL仍然會使用臨時表存儲結果。事實上,MySQL總是將結果放入臨時表,然后再讀出,再返回給客戶端。
子查詢優(yōu)化
MySql的子查詢實現的非常糟糕。最糟糕的一類查詢是WHERE條件中包含IN()的子查詢語句。
應該盡可能用關聯替換子查詢,可以提高查詢效率。
排序優(yōu)化
應該盡量讓MySql使用索引進行排序。當不能使用索引生成排序結果的時候,MySql需要自己進行排序。如果數據量小于“排序緩沖區(qū)”的大小,則MySql使用內存進行“快速排序”操作。如果數據量太大超過“排序緩沖區(qū)”的大小,那么MySql只能采用文件排序,而文件排序的算法非常復雜,會消耗很多資源。
無論如何排序都是一個成本很高的操作,所以從性能角度考慮,應盡可能避免排序。所以讓MySql根據索引構造排序結果非常的重要。
臨時表
上面提到在MySql中,任何一個查詢實質上都是一個關聯查詢。那么對于子查詢或UNION查詢是如何實現關聯操作的呢。
對于UNION查詢,MySql先將每一個單表查詢結果放到一個臨時表中,然后再重新讀出臨時表數據來完成UNION查詢。MySql讀取結果臨時表和普通表一樣,也是采用的關聯方式。
當遇到子查詢時,先執(zhí)行子查詢并將結果放到一個臨時表中,然后再將這個臨時表當做一個普通表對待。
MySql的臨時表是沒有任何索引的,在編寫復雜的子查詢和關聯查詢的時候需要注意這一點。
臨時表也叫派生表。
用IN()取代OR
在MySql中,IN()先將自己列表中的數據進行排序,然后通過二分查找的方式確定列的值是否在IN()的列表中,這個時間復雜度是O(logn)。如果換成OR操作,則時間復雜度是O(n)。所以,對于IN()的列表中有大量取值的時候,用IN()替換OR操作將會更快。
優(yōu)化MAX()和MIN()
在MySql中,IN()先將自己列表中的數據進行排序,然后通過二分查找的方式確定列的值是否在IN()的列表中,這個時間復雜度是O(logn)。如果換成OR操作,則時間復雜度是O(n)。所以,對于IN()的列表中有大量取值的時候,用IN()替換OR操作將會更快。