性能優化
了解查詢的整個生命周期,清楚每個階段的時間消耗情況
性能分析
慢查詢日志——服務器性能分析
參考
慢查詢日志是優化很重要的手段,但是開啟慢查詢日志對性能的影響並不大,所以可以考慮在線上打開慢查詢日志
- 查看慢查詢是否打開、以及日志存儲位置:show variables like '%slow%'
統計當前數據庫連接狀態
mysql -e 'show processlist \G' -uroot -proot | grep State | sort | uniq -c | sort -rn
剖析單條查詢
select @@profiling:查看profiling是否打開
set profiling=1:打開profiling
show profiles:查看每條查詢的性能
show profile for query id:查看query id的詳細時間花費
information_schema.profiling:該表存儲了每個query的詳細時間花費
show status:查看會話級別的計數器
show global status:查看全局的計數器
show status where variable_name like '%handler%':查看某些變量的計數
查詢性能優化
查詢由多個子任務組成,優化查詢也就是優化子任務
- 消除一些子任務
- 減少子任務執行次數
- 讓子任務執行更快
優化數據訪問
不要請求不需要的數據
- 只返回必要的行(limit)、列(盡量不要使用星號返回所有列)
- 盡量不要查詢重復的數據,使用緩存
是否在掃描額外的記錄
mysql衡量查詢開銷的指標:
- 響應時間
- 掃描行數
- 返回的行數
訪問類型
explain語句中的type指明了訪問類型,包括:全表掃描,索引掃描,范圍掃描,唯一索引查詢,常數引用,從左到右掃描的行數從多到少,速度從慢到快
查詢語句中where條件的使用,性能從好到壞是:
- 在索引中使用where條件過濾不匹配的記錄,這是在存儲引擎層完成的
- 使用覆蓋引擎(extra中出現using index)來返回記錄,直接從索引過濾不需要的記錄並返回結果,在在服務器層完成,不需要回表
- 在表中返回數據,使用where過濾不匹配的記錄(extra中出現using where),在服務層完成。mysql需要先讀數據然后過濾
分解復雜查詢
- 切分查詢:將數據量大的查詢切分為幾次(有些情況分析查詢的性能更好,比如刪除數據,每次刪除10條比一次刪除100條來得好,當在數據庫業務繁忙的時候)
- 分解關聯查詢:
- 緩存效率高:mysql中如果關聯表發生了變化,緩存就失效了;而且應用程序可以緩存切分查詢之后的結果
- 執行單個查詢減少鎖競爭
- 數據庫表不做強關聯,在應用層做,擴展性更好
mysql執行、優化查詢的方式
mysql查詢優化器的局限性
優化器只關心隨機頁面的讀取
- 關聯子查詢:有時候可以使用join的方式重寫關聯子查詢,效率更好
- union的限制:mysql不能將條件放入union各個查詢中,重寫的時候可以把共同的條件寫入各個查詢中
- 索引合並優化:mysql可以利用同一張表上的多個索引,explain的時候type為index_merge,key為使用到的索引。如果存在合並(and的情況)那么可以考慮將多個單列索引合為一個多列索引
- 等值傳遞
- 並行執行:
- 松散索引
- 哈希關聯
- 最大值和最小值:mysql的min和max函數
- 在同一個表上查詢和更新:mysql不允許同時對一張表查詢和更新,可以使用join的方式來select需要在該表上查詢的字段
干涉查詢優化器
mysql提供了一些選項來干涉優化器的行為,但是建議一般情況下不要使用,因為一般干涉優化器帶來的收效較小,反而給版本升級的時候帶來一些問題
優化特定類型的查詢
count
count(col):查詢該列值得個數(不包含null)
count(星號):查詢行數
- myisam全表count(星號)很快
- 對於不精確的統計使用緩存
優化關聯查詢
- 確保on和using列上有索引,A join B on col,那么一般只需要在B的col創建索引就夠了
- 確保group by 和order by的表達式只涉及一個表中的列,mysql才可以使用索引來優化
優化子查詢
使用關聯查詢代替子查詢,在mysql5.6和mariadb不需要考慮
優化group by和order by
group by的結果默認會按照分組字段進行排序,如果不需要排序可以去掉排序,指定order by NULL
優化分頁查詢
當頁碼比較多的時候需要掃描的數據較大,這個時候可以使用覆蓋索引進行優化,先使用索引覆蓋查詢出limit的分頁數據,然后join該表,查詢其他字段,這樣就減少了掃描的行數
select * from user_order inner join (select order_id from user_order order by buy_date limit 50, 5) as lim on lim.order_id=user_order.order_id;
或者可以記下該分界行的標識列(該列最好有索引),比如主鍵id,然后查詢基於該分界的記錄
select * from user_order where order_id > 500 order by order_id limit 5;
對於總記錄數,如果不那么精確的話可以使用explain的rows
優化union查詢
除非有消除重復行的必要,否則使用union all,因為使用union會在臨時表上加distinct,導致對整個臨時表做唯一性校驗
