深入解讀Mysql查詢性能的優(yōu)化
查詢性能優(yōu)化
在之前的文章中,我們介紹了如何設計最優(yōu)的庫表結(jié)構(gòu),如何建立最好的索引,這些對于高性能來說必不可少。但是這些還不夠—你還需要設計合理的查詢。如果查詢寫的很糟糕,即使庫表結(jié)構(gòu)再合理,索引再合適,也無法實現(xiàn)高性能。
為什么查詢速度會慢
我們在想寫一個快速的查詢之前需要明白一個問題,真正重要的是響應時間。如果把查詢看作是一個任務,那么它是由一系列子任務組成,每個子任務都會消耗一定的時間。如果想要優(yōu)化查詢,就需要優(yōu)化其子任務,要么你就消除其中的一些子任務,要么就減少子任務的執(zhí)行次數(shù),要么就讓子任務運行的更快。
通常來說呀,查詢的生命周期大致可以按照順序來看:從客戶端,到服務器,然后在服務器上解析,生成執(zhí)行計劃,執(zhí)行,并返回結(jié)果給客戶端。
其中 執(zhí)行 可以認為是整個生命周期最重要的階段,其中包括了大量為了檢索數(shù)據(jù)到存儲引擎的調(diào)用以及調(diào)用后的數(shù)據(jù)處理,包括排序,分組等。
在完成這些任務的時候,查詢需要在不同的地方花費時間,包括網(wǎng)絡,CPU計算,生成統(tǒng)計信息和執(zhí)行計劃,鎖等待等操作。
尤其是向底層存儲引擎檢索數(shù)據(jù)的調(diào)用操作,這些調(diào)用需要在內(nèi)存操作,CPU操作和內(nèi)存不足時導致IO操作上消耗時間。根據(jù)存儲引擎不同,可能還會產(chǎn)生大量的上下文切換以及系統(tǒng)調(diào)用。
下面我們就來看看如何優(yōu)化 查詢。
慢查詢基礎:優(yōu)化數(shù)據(jù)訪問
查詢性能低下的最基本的原因是訪問的數(shù)據(jù)太多。某些查詢可能不可避免的需要篩選大量數(shù)據(jù),但這并不常見。
對于一些低效的查詢,我們通??梢允褂孟旅鎯蓚€步驟來分析:
- 確認用用程序是否在檢索大量超過你需要的數(shù)據(jù)。這通常意味著訪問太多行,但有時候也可能是訪問了太多列。
- 確認MySQL服務器是否在分析大量超過需要的數(shù)據(jù)行 是否向數(shù)據(jù)庫請求了不需要的數(shù)據(jù)
有些查詢會請求超過實際需要 的數(shù)據(jù),然后這些多余的數(shù)據(jù)會被應用程序丟棄。這就會帶來一些額外的很多負擔,并增加網(wǎng)絡開銷。也會消耗應用服務器的cpu和內(nèi)存資源。
這里有一些經(jīng)典案例:
- 查詢不需要的記錄
很多人會以為MySQL只會返回需要的數(shù)據(jù),實際上MySQL卻是先返回全部結(jié)果集再進行計算。一些開發(fā)者會先使用select語句查詢大量的結(jié)果,然后獲取前面的N行后關(guān)閉結(jié)果集。
你以為mysql只返回了你需要的前幾條信息,實際上MySQL是返回了全部結(jié)果集,然后丟棄了大部分的數(shù)據(jù)。最簡單有效的解決方法就是加limit。
- 多表關(guān)聯(lián)時返回全部列
比如說你想查詢電影FLY 中出現(xiàn)的演員,你可千萬千萬不要像下面這樣寫:
select * FROM actor inner join film_actor using(actor_id) inner join film using(film_id) where film.title = 'FLY';
你這樣寫就把三個表的全部數(shù)據(jù)列都返回了,正確的方式是下面這么寫,只取需要的列:
select actor.* FROM actor inner join film_actor using(actor_id) inner join film using(film_id) where film.title = 'FLY';
- 總是取出全部列
每次看到**SELECT ***的時候都需要仔細想想,是不是真的需要返回全部的列?很可能不是必需的。取出全部列,會讓優(yōu)化器無法完成索引覆蓋掃描這類優(yōu)化, 還會為服務器帶來額外的I/O、內(nèi)存和CPU的消耗。因此,一些DBA是嚴格禁止 SELECT *的寫法的,這樣做有時候還能避免某些列被修改帶來的問題。何樂而不為呢?
當然,你話不能說死,查詢返回查過需要的數(shù)據(jù)也不總是壞事。因為這種有點浪費數(shù)據(jù)庫資源的方式是可以簡化開發(fā)的,因為它能提高相同代碼片段的復用性。
- 重復查詢相同的數(shù)據(jù)
你比如說,一個用戶多次評論的時候,你每次都要查詢它的id,這就很不好,我們呢可以采取的一種方案是,初次查詢的時候就將這個數(shù)據(jù)緩存起來,需要的時候從緩存中取出來,這樣性能顯然會更好。
MySQL是否在掃描額外的記錄
我們上面講的是確定查詢只返回需要的數(shù)據(jù),那么我們還要關(guān)注什么呢?
我們要去研究為了返回這個結(jié)果,有沒有掃描過多的數(shù)據(jù)這一現(xiàn)象。
對于mysql,最簡單的三個衡量查詢開銷的指標就下面這三哥們:
- 響應時間
- 掃描的行數(shù)
- 返回的行數(shù)
當然,沒有哪個指標能夠完美的說衡量出查詢的開銷,你只能通過這三指標去權(quán)衡罷了。
這三個指標都會記錄到MySQL的慢日志中去,所以檢查慢日志是找出掃描行數(shù)過多的查詢的好辦法。
1.響應時間
響應時間是兩個部分之和:服務時間和排隊時間。服務時間是指數(shù)據(jù)庫處理這個査詢真正花了多長時間。
排隊時間是指服務器因為等待某些資源而沒有真正執(zhí)行査詢,在那等資源所消耗的時間——可能是等I/O操作完成,也可能是等待行鎖之類的。
但是上面所說的這些情況在實際情況下是更加復雜的情況,所以響應時間是沒有什么一致的規(guī)律或者公式的。我們只能算個大致的時間然后去判斷是不是一個合理的值。
2.掃描的行數(shù)和返回的行數(shù)
分析査詢時,査看該査詢掃描的行數(shù)是非常有幫助的。這在一定程度上能夠說明該查詢找到需要的數(shù)據(jù)的效率高不高。
當然,這個指標可能不夠完美,因為并不是所有的行的訪問代價都是相同的。較短的行的訪問速度更快,內(nèi)存中的行也比磁盤中的行的訪問速度要快得多。
3.掃描的行數(shù)和訪問類型
在評估查詢的開銷的時候,我們還需要考慮一下從表中找到一行數(shù)據(jù)的成本。因為MySQL有好幾種訪問方式可以査找并返回你想要的一行結(jié)果。
有些訪問方式可能需要掃描很多行才能返回一行 結(jié)果,但是也有些訪問方式可能無須掃描就能返回結(jié)果。
在EXPLAIN語句中的type列可以體現(xiàn)出你的訪問類型。訪問類型有很多種,如全表掃描,索引掃描,范圍掃描,唯一索引査詢,常數(shù)引用等。
這些訪問的速度是從慢到快的,掃描的行數(shù)也是從小到大。當然我們是不需要記住這些訪問類型。
如果你的查詢沒有辦法找到合適的訪問類型,俺么最好的解決方法就是增加一個合適的索引,這我們之前文章已經(jīng)介紹了。
為什么索引歲查詢性能的優(yōu)化這么重要呢?索引讓MySQL以最高效,掃描行數(shù)最少的方式找到你想要的結(jié)果。
一般MySQL能夠使用如下的三種方式應用where條件,也是從好到壞的排序:
- 在索引中使用WHERE條件來過濾不匹配的記錄。這是在存儲引擎層完成的。
- 使用索引覆蓋掃描(在Extra列中出現(xiàn)了 Using index)來返回記錄,直接從索引中 過濾不需要的記錄并返回命中的結(jié)果。這是在MySQL服務器層完成的,但無須再回表查詢記錄。
- 從數(shù)據(jù)表中返回數(shù)據(jù),然后過濾不滿足條件的記錄(在Extra列中出現(xiàn)Using Where)這在MySQL服務器層完成,MySQL需要先從數(shù)據(jù)表讀出記錄然后過濾。這就很慢了,很糟糕了。
如果我們發(fā)現(xiàn)查詢需要掃描大量的數(shù)據(jù)但是只是返回少數(shù)的行,那么我們通??梢試L試這些策略技巧去優(yōu)化:
- 使用索引覆蓋掃描,把所有需要用到的列都放到索引中。
- 改變庫表結(jié)構(gòu)。例如使用單獨的匯總表
- 重寫這個復雜的査詢,讓MySQL優(yōu)化器能夠以更優(yōu)化的方式執(zhí)行這個査詢。
重構(gòu)查詢的方式
就是我們之前提到的一種優(yōu)化方式,我們的SQL查詢太慢,有時候是因為我們寫的這個SQL太糟糕了。我們需要換一種方式去寫SQL,但還是要返回一樣的結(jié)果。
一個復雜的查詢還是多個簡單的查詢
就如標題所言,我們在設計查詢的時候,需要考慮的一個重要問題是:是否需要將一個復雜的查詢變?yōu)槎鄠€簡單的查詢。
切分查詢
有時候啊,我們需要把一個大查詢“分而治之”,將大查詢變?yōu)樾〔樵儯總€查詢功能完全一樣,只完成一小部分,每次只返回一小部分的結(jié)果。
你比如說,想要刪除舊的數(shù)據(jù),,如果說你用一個大的語句一次性完成的話 ,則可能需要一次性鎖住很多數(shù)據(jù),占滿整個事務日志,耗盡系統(tǒng)資源,阻塞很多小的,但是重要的查詢。所以將一個大的delete語句切分為多個較小的查詢可以盡可能小地影響MySQL性能,同時還可以減少MySQL復制的延遲。
你比如說下面這個例子:
DELETE FROM messages WHERE created < DATE_SUB(NOW(),INTERVAL 3 MONTH);
我們就可以使用下面同樣的方法來解決:
rows_affected = 0
do {
rows_affected = do_query(
"DELETE FROM messages WHERE created < DATE_SUB(NOW(),INTERVAL 3 MONTH) LIMIT 10000")
} while rows_affected > 0我們通過上面的語句,就實現(xiàn)了一次刪除啊10000行數(shù)據(jù)這樣一個限制,一次刪除10000行數(shù)據(jù)是一個比較高效而且對服務器影響也是最小的做法。如果,每次刪除數(shù)據(jù)后,都暫停一會再做下一次刪除,這樣也可以將服務器上原本一次性的壓力分散到一個很長的時間段,也就大大的降低了對服務器的影響。
分解關(guān)聯(lián)查詢
很多高性能的應用都會對關(guān)聯(lián)查詢進行分解。簡而言之就是,可以對每一個表進行一次單表查詢,然后將結(jié)果在應用程序中進行關(guān)聯(lián)。
你比如說說下面這個查詢。
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);
返回的結(jié)果一模一樣,那么這樣做有什么好處呢?
- 讓緩存的效率更高。許多應用程序可以方便的緩存單表查詢對應的結(jié)果對象。你比如說呀,如果第一行的tag已經(jīng)被緩存了,那么應用就可以跳過第一個查詢了,
- 我們分解之后,執(zhí)行單個查詢可以減少鎖的競爭
- 在應用層做關(guān)聯(lián),可以更加容易對數(shù)據(jù)庫進行拆分,更加容易做到高性能和可擴展
- 查詢本身的效率也會提升,我們使用in代替關(guān)聯(lián)查詢,這比隨機的關(guān)聯(lián)是要更加高效的。
- 可以減少冗余記錄的查詢。在應用層做關(guān)聯(lián)査詢,意味著對于某條記錄應用只需要査詢一次,而在數(shù)據(jù)庫中做關(guān)聯(lián)査詢,則可能需要重復地訪問一部分數(shù)據(jù)。從這點看,這樣的重構(gòu)還可能會減少網(wǎng)絡和內(nèi)存的消耗。
到此這篇關(guān)于深入解讀Mysql查詢性能的優(yōu)化的文章就介紹到這了,更多相關(guān)Mysql查詢性能優(yōu)化內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用xshell實現(xiàn)代理功能并navicat?for?MySQL?進行測試
本文介紹使用xshell實現(xiàn)代理功能并使用navicat?for?MySQL進行測試,文章主要利用SSH連接工具xshell就可以實現(xiàn)簡單的代理功能,下面實現(xiàn)過程,需要的小伙伴可以參考一下2022-02-02
MySQL索引原理、分類與優(yōu)化實戰(zhàn)深入理解
索引是一種特殊的數(shù)據(jù)結(jié)構(gòu),用于加快數(shù)據(jù)庫表中數(shù)據(jù)的檢索速度,它類似于書籍的目錄,通過建立一個指向數(shù)據(jù)行的快速查找路徑,這篇文章主要介紹了MySQL索引原理、分類與優(yōu)化實戰(zhàn)的相關(guān)資料,需要的朋友可以參考下2026-06-06
MySQL 5.6 中TIMESTAMP with implicit DEFAULT value is deprecat
安裝mysql的時候出現(xiàn)TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details),可以參考下面的方法解決2015-08-08
MySQL?SELECT數(shù)據(jù)查看WHERE(AND?OR?IN?NOT)語句
這篇文章主要介紹了MySQL?SELECT數(shù)據(jù)查看WHERE(AND?OR?IN?NOT)de?語句學習,非常適合新手小白朋友,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-05-05
解讀sql中timestamp和datetime之間的轉(zhuǎn)換
這篇文章主要介紹了解讀sql中timestamp和datetime之間的轉(zhuǎn)換方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-12-12

