MySQL優化系列(一)--查詢優化(1)(非索引設計)
一、明確搜尋優化的整體思路以及查詢優化的因素:
(1)搜尋優化的整體思路:
索引優化,查詢優化,查詢快取,伺服器設定優化,作業系統和硬體優化,應用層面優化(web伺服器,快取)等等。對於一個整體專案而言只有這些齊頭並進,才能實現mysql高效能。
(2)查詢優化的因素思路:
[一]是否向資料庫請求了不需要的資料。
也就是說不要輕易使用select * from ,能明確多少資料就查多少個
[二]mysql是否掃描額外的紀錄
查詢是否掃描了過多的資料。最簡單的衡量查詢開銷三個指標如下:響應時間;掃描的行數;返回的行數。
沒有哪個指標能夠完美地衡量查詢的開銷,但它們大致反映了mysql在內部執行查詢時需要多少資料,並可以推算出查詢執行的時間。
這三個指標都會記錄到mysql的慢日誌中,所以檢查慢日誌記錄是找出掃描行數過多的查詢的好辦法。
響應時間:是兩個部分之和:服務時間和排隊時間。服務時間是指資料庫處理這個查詢真正花了多長時間。 排隊時間是指伺服器因為等待某些資源而沒有真正執行查詢的時間。—可能是等io操作完成,也可能是等待行鎖,等等。
掃描的行數和返回的行數:分析查詢時,檢視該查詢掃描的行數是非常有幫助的。這在一定程度上能夠說明該查詢找到需要的資料的效率高不高。
掃描的行數和訪問型別: 在expain語句中的type列反應了訪問型別。訪問型別有很多種,從全表掃描(ALL)到索引掃描(index)到範圍掃描()到唯一索引查詢到常數引用等。這裡列的這些,速度由慢到快,掃描的行數也是從小到大。
如果發現查詢需要掃描大量的資料但只返回少數的行,那麼通常可以嘗試下面的技巧去優化它:
使用索引覆蓋掃描。
改變庫表結構。例如使用單獨的彙總表。
重寫這個複雜的查詢。讓mysql優化器能夠以更優化的方式執行這個查詢。
[三]查詢方式:
1. 一個複雜查詢 or 多個簡單查詢
設計查詢的時候一個需要考慮的重要問題是,是否需要將一個複雜的查詢分成多個簡單的查詢。
2.切分查詢
有時候對於一個大查詢我們需要“分而治之”,將大查詢切分為小查詢,每個查詢功能完全一樣,只完成一小部分,每次只返回一小部分查詢結果。
3.分解關聯查詢
分解關聯查詢
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 tag = 'mysql'
> select * from tag_post where tag_id = 1234
> select * from post where post_id in (123, 456, 567, 9098, 8904)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
讓快取的效率更高。
將查詢分解後,執行單個查詢可以減少鎖的競爭。
在應用層做關聯,可以更容易對資料庫進行拆分,更容易做到高效能和可擴充套件。
查詢本身效率也可能會有所提升。
可以減少冗餘記錄的查詢,
更進一步,這樣做相當於在應用中實現了雜湊關聯,而不是使用mysql的巢狀迴圈關聯。
(3)查詢的流程(站在後端開發者角度):摘自此博主此文章
1.客戶端傳送一條查詢給伺服器
2.伺服器先檢查查詢快取,如果命中了快取,則立刻返回儲存在快取中的結果,否則進入下一階段。
3.伺服器進行SQL解析,預處理,再由優化器生成對應的執行計劃,
4.mysql根據優化器生成的執行計劃,呼叫儲存引擎的API來執行查詢。
5.將結果返回給客戶端。
二、優化查詢前的幾個工具說明:
(1)檢視MySQL整體狀態:
1. Mysql> show status; ——顯示狀態資訊(擴充套件show status like ‘XXX’)
2. Mysql>show variables ——顯示系統變數(擴充套件show variables like ‘XXX’)
3. Mysql>show innodb status ——顯示InnoDB儲存引擎的狀態
4. Mysql>show processlist ——檢視當前SQL執行,包括執行狀態、是否鎖表等
5. Shell> mysqladmin variables -u username -p password——顯示系統變數
6. Shell> mysqladmin extended-status -u username -p password——顯示狀態資訊
7. Shell> mysqld –verbose –help [|more #逐行顯示] 檢視狀態變數及幫助:
(2)開啟慢查詢日誌:
1. 在配置檔案my.cnf或my.ini中在[mysqld]一行下面加入兩個配置引數
log-slow-queries={自己想存放的日誌路徑}/slow-query.log
long_query_time=2
注:log-slow-queries引數為慢查詢日誌存放的位置,一般這個目錄要有mysql的執行帳號的可寫許可權,一般都將這個目錄設定為mysql的資料存放目錄;
long_query_time=2中的2表示查詢超過兩秒才記錄;
在my.cnf或者my.ini中新增log-queries-not-using-indexes引數,表示記錄下沒有使用索引的查詢。
log-slow-queries=/data/mysqldata/slow-query.log
long_query_time=10
log-queries-not-using-indexes
2. 檢視日誌啟動狀態:show variables like “slow%”;
3. 設定慢日誌開啟: set global slow_query_log = ON;
4. 查詢long_query_time 的值 :
show variables like “long%”;
5. 為了方便測試,可以將修改慢查詢時間為3秒。(小點容易比較,畢竟mysql處理那麼快)
6.以後就往我們設定的日誌路徑去訪問日誌即可:
more slow.log
1
(3)explain查詢分析:
使用 EXPLAIN 關鍵字可以模擬優化器執行SQL查詢語句,從而知道MySQL是如何處理你的SQL語句的。這可以幫你分析你的查詢語句或是表結構的效能瓶頸。通過explain命令可以得到:
表的讀取順序
資料讀取操作的操作型別
哪些索引可以使用
哪些索引被實際使用
表之間的引用
每張表有多少行被優化器查詢
EXPLAIN查詢出來的欄位解析:
1)Table:顯示這一行的資料是關於哪張表的
2)possible_keys:顯示可能應用在這張表中的索引。如果為空,沒有可能的索引。可以為相關的域從WHERE語句中選擇一個合適的語句。
指出MySQL能使用哪個索引在表中找到記錄,查詢涉及到的欄位上若存在索引,則該索引將被列出,但不一定被查詢使用,因為MySQL內部優化器有自己的抉擇。
該列完全獨立於EXPLAIN輸出所示的表的次序。這意味著在possible_keys中的某些鍵實際上不能按生成的表次序使用。
如果該列是NULL,則沒有相關的索引。在這種情況下,可以通過檢查WHERE子句看是否它引用某些列或適合索引的列來提高你的查詢效能。如果是這樣,創造一個適當的索引並且再次用EXPLAIN檢查查詢
3)key:實際使用的索引。如果為NULL,則沒有使用索引。MYSQL很少會選擇優化不足的索引,此時可以在SELECT語句中使用USE INDEX(index)來強制使用一個索引或者用IGNORE INDEX(index)來強制忽略索引
4)key_len:使用的索引的長度。在不損失精確性的情況下,長度越短越好
表示索引中使用的位元組數,可通過該列計算查詢中使用的索引的長度(key_len顯示的值為索引欄位的最大可能長度,並非實際使用長度,即key_len是根據表定義計算而得,不是通過表內檢索出的)
不損失精確性的情況下,長度越短越好
5)ref:顯示索引的哪一列被使用了,如果可能的話,是一個常數。
表示上述表的連線匹配條件,即哪些列或常量被用於查詢索引列上的值
6)rows:MySQL認為必須檢索的用來返回請求資料的行數
表示MySQL根據表統計資訊及索引選用情況,估算的找到所需的記錄所需要讀取的行數
7)select_type:查詢中每個select子句的型別
(1) SIMPLE(簡單SELECT,不使用UNION或子查詢等)
(2) PRIMARY(查詢中若包含任何複雜的子部分,最外層的select被標記為PRIMARY)
(3) UNION(UNION中的第二個或後面的SELECT語句)
(4) DEPENDENT UNION(UNION中的第二個或後面的SELECT語句,取決於外面的查詢)
(5) UNION RESULT(UNION的結果)
(6) SUBQUERY(子查詢中的第一個SELECT)
(7) DEPENDENT SUBQUERY(子查詢中的第一個SELECT,取決於外面的查詢)
(8) DERIVED(派生表的SELECT, FROM子句的子查詢)
(9) UNCACHEABLE SUBQUERY(一個子查詢的結果不能被快取,必須重新評估外連結的第一行)
8)type:這是最重要的欄位之一,顯示查詢使用了何種型別。從最好到最差的連線型別為NULL、system、const、eq_ref、ref、range、index和ALL
NULL: MySQL在優化過程中分解語句,執行時甚至不用訪問表或索引,例如從一個索引列裡選取最小值可以通過單獨索引查詢完成。
system、const:可以將查詢的變數轉為常量. 如id=1; id為 主鍵或唯一鍵。當MySQL對查詢某部分進行優化,並轉換為一個常量時,使用這些型別訪問。如將主鍵置於where列表中,MySQL就能將該查詢轉換為一個常量,system是const型別的特例,當查詢的表只有一行的情況下,使用system
eq_ref:訪問索引,返回某單一行的資料.(通常在聯接時出現,查詢使用的索引為主鍵或惟一鍵)。類似ref,區別就在使用的索引是唯一索引,對於每個索引鍵值,表中只有一條記錄匹配,簡單來說,就是多表連線中使用primary key或者 unique key作為關聯條件
ref:訪問索引,返回某個值的資料.(可以返回多行) 通常使用=時發生。表示上述表的連線匹配條件,即哪些列或常量被用於查詢索引列上的值
range:這個連線型別使用索引返回一個範圍中的行,比如使用>或<查詢東西,並且該欄位上建有索引時發生的情況(注:不一定好於index)。只檢索給定範圍的行,使用一個索引來選擇行。
index:以索引的順序進行全表掃描,優點是不用排序,缺點是還要全表掃描。index與ALL區別為index型別只遍歷索引樹
ALL:全表掃描,應該儘量避免。 MySQL將遍歷全表以找到匹配的行。
9)Extra:關於MYSQL如何解析查詢的額外資訊,主要有以下幾種
using index:只用到索引,可以避免訪問表.。表示查詢在索引樹中就可查詢所需資料, 不用掃描表資料檔案, 往往說明效能不錯
using where:使用到where來過慮資料. 不是所有的where clause都要顯示using where. 如以=方式訪問索引.
using tmporary:查詢有使用臨時表, 一般出現於排序, 分組和多表 join 的情況, 查詢效率不高, 建議優化.
using filesort:用到額外的排序. (當使用order by v1,而沒用到索引時,就會使用額外的排序)。MySQL中無法利用索引完成的排序操作稱為“檔案排序”
range checked for eache record(index map:N):沒有好的索引.
Using join buffer:改值強調了在獲取連線條件時沒有使用索引,並且需要連線緩衝區來儲存中間結果。如果出現了這個值,那應該注意,根據查詢的具體情況可能需要新增索引來改進能。
Impossible where:這個值強調了where語句會導致沒有符合條件的行。
(4)profiling查詢分析:
通過慢日誌查詢可以知道哪些SQL語句執行效率低下,通過explain我們可以得知SQL語句的具體執行情況,索引使用等,還可以結合show命令檢視執行狀態。
如果覺得explain的資訊不夠詳細,可以同通過profiling命令得到更準確的SQL執行消耗系統資源的資訊。
profiling預設是關閉的。可以通過以下語句檢視: select @@profiling;
開啟profiling查詢分析:set profiling = 1;
然後我們隨便寫幾條select語句,再檢視:show profiles\G;
mysql> show profiles\G; 可以得到被執行的SQL語句的時間和ID
mysql>show profile for query 1; 得到對應SQL語句執行的詳細資訊
Show Profile命令格式:
SHOW PROFILE [type [, type] … ]
[FOR QUERY n]
[LIMIT row_count [OFFSET offset]]
type引數:
|ALL
| BLOCK IO
| CONTEXT SWITCHES
| CPU
| IPC
| MEMORY
| PAGE FAULTS
| SOURCE
| SWAPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
以上的16rows是針對非常簡單的select語句的資源資訊,對於較複雜的SQL語句,會有更多的行和欄位,比如converting HEAP to MyISAM 、Copying to tmp table等等,由於以上的SQL語句不存在複雜的表操作,所以未顯示這些欄位。通過profiling資源耗費資訊,我們可以採取針對性的優化措施。
測試完畢以後 ,關閉引數:mysql> set profiling=0
三、單表查詢步步優化:(暫不討論索引,在下篇文章再詳解索引)
(我們繼續看上面所用的商品表)
//最傻的查詢方式
select * from commodity_list
1
2
(1)明確需要的欄位,要多少就寫多少欄位:
select d.Good_ID ,
d.Classify_ID,
d.Good_Name,
d.Monthsale_Num,
d.Store_Name,
d.Comment_Num,
d.Good_Brand,
d.Ishas_License,
ifnull(d.Good_Hot,0),
d.Good_Price,
d.Store_Add,
d.Store_Age,
d.Seller_Credit,
d.Classify_Description
from
Commodity_list d;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(2)使用分頁語句:limit start , count 或者條件 where子句
有什麼可限制的條件儘量加上,查一條就limit一條。做到不多拿不亂拿。
明確子句的執行順序先:
SELECT select_list
FROM table_name
[ WHERE search_condition ]
[ GROUP BY group_by_expression ]
[ HAVING search_condition ]
[ ORDER BY order_expression [ ASC | DESC ] ]
[limit m,n]
1
2
3
4
5
6
7
例子:
select
d.Good_ID ,
d.Classify_ID,
d.Good_Name,
d.Monthsale_Num,
d.Store_Name,
d.Comment_Num,
d.Good_Brand,
d.Ishas_License,
ifnull(d.Good_Hot,0),
d.Good_Price,
d.Store_Add,
d.Store_Age,
d.Seller_Credit,
d.Classify_Description
from
Commodity_list d
where Classify_ID=23
limit 1,10000
;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
補充:
1)limit語句的查詢時間與起始記錄的位置成正比
2)mysql的limit語句是很方便,但是對記錄很多的表並不適合直接使用。
對limit分頁效能優化分析:
偏移量越大,查詢越費時。
原因:
每條資料的實際儲存長度不一樣(所以必須要依次遍歷,不能直接跳過前面的一部分)
哪怕是每條資料儲存長度一樣,如果之前有過delete操作,那索引上的排列就有gap
所以資料不是定長儲存,不能像陣列那樣用index來訪問,只能依次遍歷,就導致偏移量越大查詢越費時
對limit的使用再優化 :
利用自增主鍵,避免offset的使用(演示在積分表score,商品表設計得不太好),約是上面方法的1/3時間。
select *
from
score
WHERE id>0 LIMIT 10000
;
select *
from
score
WHERE id>10000 LIMIT 10000
;
select *
from
score
WHERE id>20000 LIMIT 10000
;
......
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(3)如果是有序的查詢,可使用ORDER BY
select *
from
score
WHERE id>0
ORDER BY score ASC
LIMIT 10000
;
1
2
3
4
5
6
7
(4)開啟查詢快取:部分摘自此博主此部落格
大多數的MySQL伺服器都開啟了查詢快取。這是提高性最有效的方法之一。當有很多相同的查詢被執行了多次的時候,這些查詢結果會被放到一個快取中,這樣,後續的相同的查詢就不用操作表而直接訪問快取結果了。
命中快取條件:
1)快取存在一個hash表中,通過查詢SQL,查詢資料庫,客戶端協議等作為key.在判斷是否命中前,MySQL不會解析SQL,而是直接使用SQL去查詢快取,SQL任何字元上的不同,如空格,註釋,都會導致快取不命中.
2)如果查詢中有不確定資料,例如CURRENT_DATE()和NOW()函式,那麼查詢完畢後則不會被快取.所以,包含不確定資料的查詢是肯定不會找到可用快取的
工作流程:
1)伺服器接收SQL,以SQL和一些其他條件為key查詢快取表(額外效能消耗)
2)如果找到了快取,則直接返回快取(效能提升)
3)如果沒有找到快取,則執行SQL查詢,包括原來的SQL解析,優化等.
4)執行完SQL查詢結果以後,將SQL查詢結果存入快取表(額外效能消耗)
快取使用的時機:(並不是每個情況使用快取都是好的)
衡量開啟快取是否對系統有效能提升是一個整體的概念。
1)通過快取命中率判斷, 快取命中率 = 快取命中次數 (Qcache_hits) / 查詢次數 (Com_select)、
2)通過快取寫入率, 寫入率 = 快取寫入次數 (Qcache_inserts) / 查詢次數 (Qcache_inserts)
3)通過 命中-寫入率 判斷, 比率 = 命中次數 (Qcache_hits) / 寫入次數 (Qcache_inserts), 高效能MySQL中稱之為比較能反映效能提升的指數,一般來說達到3:1則算是查詢快取有效,而最好能夠達到10:1
快取引數配置:
1)query_cache_type: 是否開啟快取:
可選項:OFF: 關閉;ON: 總是開啟;DEMAND: 只有明確寫了SQL_CACHE的查詢才會吸入快取
2)query_cache_size: 快取使用的總記憶體空間大小,單位是位元組,這個值必須是1024的整數倍,否則MySQL實際分配可能跟這個數值不同(感覺這個應該跟檔案系統的blcok大小有關)
3)query_cache_min_res_unit: 分配記憶體塊時的最小單位大小
4)query_cache_limit: MySQL能夠快取的最大結果,如果超出,則增加 Qcache_not_cached的值,並刪除查詢結果
5)query_cache_wlock_invalidate: 如果某個資料表被鎖住,是否仍然從快取中返回資料,預設是OFF,表示仍然可以返回
6)快取的一些整體引數:
Qcache_free_blocks: 快取池中空閒塊的個數
Qcache_free_memory: 快取中空閒記憶體量
Qcache_hits: 快取命中次數
Qcache_inserts: 快取寫入次數
Qcache_lowmen_prunes: 因記憶體不足刪除快取次數
Qcache_not_cached: 查詢未被快取次數,例如查詢結果超出快取塊大小,查詢中包含可變函式等
Qcache_queries_in_cache: 當前快取中快取的SQL數量
Qcache_total_blocks: 快取總block數
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
減少碎片策略:
1)選擇合適的block大小
2)使用 FLUSH QUERY CACHE 命令整理碎片.這個命令在整理快取期間,會導致其他連線無法使用查詢快取
PS: 清空快取的命令式 RESET QUERY CACHE
InnoDB與查詢快取:
Innodb會對每個表設定一個事務計數器,裡面儲存當前最大的事務ID.當一個事務提交時,InnoDB會使用MVCC中系統事務ID最大的事務ID跟新當前表的計數器.
只有比這個最大ID大的事務能使用查詢快取,其他比這個ID小的事務則不能使用查詢快取.
另外,在InnoDB中,所有有加鎖操作的事務都不使用任何查詢快取