你需要掌握的 mysql 性能優化的一些要點

緩存優化

大多數的MySQL服務器都開啓了查詢緩存,當有很多相同的查詢被執行了多次的時候,這些查詢結果會被放到一個緩存中,這樣,後續的相同的查詢就不用操作表而直接訪問緩存結果了

// 如果在sql語句中直接使用一些動態的函數查詢緩存不開啓
$r = mysql_query("SELECT username FROM user WHERE signup_date >= CURDATE()");
 
// 開啓查詢緩存
$today = date("Y-m-d");
$r = mysql_query("SELECT username FROM user WHERE signup_date >= '$today'");

如果不想使用緩存,可以在每次查詢時直接加參數SQL_NO_CACHE來禁止使用緩存

SQL_NO_CACHE解釋如下:

  • 對當前query不使用數據庫已有緩存來查詢,則當前query花費時間會多點
  • 對當前query的產生的結果集不緩存至系統query cache裏,則下次相同query花費時間會多點
select SQL_NO_CACHE picname, smallimg from pics where user_id=17853;

IN和EXISTS性能對比

EXISTS用法

exists對外表用loop逐條查詢,每次查詢都會查看exists的條件語句,當exists裏的條件語句能夠返回記錄行時(無論記錄行是的多少,只要能返回)),條件就爲真,返回當前loop到的這條記錄,反之如果exists裏的條件語句不能返回記錄行,則當前loop到的這條記錄被丟棄,exists的條件就像一個bool條件,當能返回結果集則爲true,不能返回結果集則爲 false。

//每查一條記錄都會判斷後面子查詢返回的數據是否存在
SELECT
	SQL_NO_CACHE *
FROM
	new_component
WHERE
	 EXISTS (SELECT new_dashboard.id FROM new_dashboard where new_dashboard.id = new_component.dashboard_id);
IN的用法

in查詢相當於多個or條件的疊加

SELECT
	SQL_NO_CACHE *
FROM
	new_component
WHERE
	new_component.dashboard_id IN (SELECT id FROM new_dashboard);
兩者性能對比

考慮下面兩條sql:

(1)select * from A where exists (select * from B where B.id = A.id);

(2)select * from A where A.id in (select id from B);
  • 如果存在索引,使用 exists 函數查詢時,不需要查詢整張表,只需要查詢索引即可
  • 如果使用 exists 查詢,那麼只需要查到一行滿足條件即可停止
  • in 查詢會生成臨時表,而 exists 查詢不會
  • 當A表和B表大小差不多時,且存在索引時,使用exists查詢
  • 無索引的情況下,當A表小,B表大時,第一條sql的性能高
  • 無索引的情況下,當B表小,A表大,第二條sql的性能高

or, in, union all, union性能對比

-- (1)in操作
select SQL_NO_CACHE * from new_component where type in ('comment', 'rect', 'indicator');
-- (2)or操作
select SQL_NO_CACHE * from new_component where type = 'comment' or type = 'rect' or type = 'indicator';

-- (3)union all操作
select SQL_NO_CACHE * from new_component where type = 'comment'
UNION all
select  * from new_component where type = 'rect'
UNION all
select  * from new_component where type = 'indicator'
UNION all
select  * from new_component where type = 'indicator';

-- (4)union操作
select SQL_NO_CACHE * from new_component where type = 'comment'
UNION
select  * from new_component where type = 'rect'
UNION
select  * from new_component where type = 'indicator'
UNION
select  * from new_component where type = 'indicator';
  • 不要迷信union all就比 or及in 快,要結合實際情況分析到底使用哪種情況;
  • 對於複雜的索引列,尤其是要計算的時候,最好使用union all,因複雜的查詢【包含運算等】將使or、in放棄索引而全表掃描,除非你能確定or、in會使用索引;
  • 對於只有非索引字段來說你就用or或in,因爲非索引字段本來要全表掃描而union all只成倍增加表掃描的次數;
  • union操作會比union all操作耗時,因爲union操作在合併以後,還要作去重操作,如果幾個需要select語句本來就已知是去重的,那麼就直接使用union all,否則使用union

儘量避免排序使用,在排序時使用索引

  • 會使用到排序的關鍵詞有:group by/order by/sum/avg/max/min/distinct/union/rank 等
  • 能使用union all的地方不要使用union,因爲union需要排序
  • 在聚合函數中儘量使用索引,這個時候排序只需要掃描索引即可,不需要進行全表掃描
  • 能寫在 where 條件中判斷不要寫在 having 子句中,因爲 group by 會對數據進行排序,如果事先排除掉一些數據,會減少排序量,還有就是聚合後的視圖可能索引條件已經丟失
  • group by/order by 的列使用索引,可以實現高速排序
  • 參與計算後或者使用函數後,索引會失效,比如 where col * 3 < 15 不會使用索引,而 col < 15/3會使用索引
  • IS NULL 或者 IS NOT NULL 查詢會使索引失效
  • <>/!=/ NOT IN 會使索引失效
  • 聯合索引必須有順序要求
  • like謂詞時只有頭部一致才能使用索引 where col like 「abc%」
  • 默認類型轉換不僅增加開銷,還會使索引失效,比如 col 是 vchar 類型,那麼 where col = ‘10’ 會使用索引,而 where col = 10 不會使用索引

減少使用中間表

  • having 子句和 group by 子句一起使用時比先 group by 成中間表再執行 where 要快
  • 合理使用視圖,視圖裏面使用聚合函數,可能會帶來巨大的性能消耗,視圖有兩種創建算法,可以在創建時指定
# 1. merge 算法會把視圖中的sql合併到查詢的sql中,和C語言中的宏展開有點類似,這個時候和普通查詢沒什麼區別,但是不是任何時候都可以使用merge算法的,有些時候包含了聚合函數/group by/having就不能使用merge算法
# 2. temptable 算法,先執行視圖定義,將其結果保存在臨時表裏,後續操作都以這個臨時表爲準,此時會造成丟失索引,並且存在臨時表中間態,性能開銷較大

使用索引優化性能

  • 索引過多會影響數據庫寫性能。索引不夠查詢會慢;
  • 數據的變更(增刪改)都需要維護索引,因此更多的索引意味着更多的維護成本,更多的空間,所以索引不是越多越好;
  • 對於like查詢,」xxxx%」 是可以用到索引的
  • 聯合索引的最左匹配規則,如果有一個三列索引(col1,col2,col3),則已經對(col1)、(col1,col2)、(col1,col2,col3)上建立了索引,而(col2, col3)類似這樣的查詢不會有索引
  • 一般來說,列的值唯一性太小(如性別,類型什麼的),不適合建索引(怎樣叫太小?一半說來,同值的數據超過表的百分之15,那就沒必要建索引了)
  • 太長的列,可以選擇只建立部分索引,(如:只取前十位做索引)
  • 更新非常頻繁的數據不適宜建索引(怎樣叫非常?意會)
  • 一次查詢只能用一個索引
  • 如果select只選擇索引字段,會走index查詢,提高效率,儘量不要使用select *查詢
  • 在Join表的時候使用相關聯的列,並將其索引
  • 索引匹配到範圍查詢時,就會終止索引,所以在聯合索引時,最好把需要精確查詢的字段放在左邊,而範圍查詢的字段放在右邊
  • 如果計劃在列上建索引的話,就應該儘量避免設計成可爲NULL的列

show processlist 查找需要優化的sql

show processlist 顯示用戶正在運行的線程,需要注意的是,除了 root 用戶能看到所有正在運行的線程外,其他用戶都只能看到自己正在運行的線程,看不到其它用戶正在運行的線程。除非單獨個這個用戶賦予 PROCESS 權限。

當遇到 sql 查詢超時或者慢查詢時,我們可以使用 show processlist 命令來查詢是哪些sql查詢影響到了我們的業務的正常運行,這樣我們可以針對特定的慢查詢sql做優化。show processlist 顯示的信息都是來自MySQL系統庫 information_schema 中的 processlist 表。所以下面兩條語句返回的結果一樣:

select * from information_schema.processlist;
show processlist;

執行 show processlist 命令返回的結果如下圖:
在這裏插入圖片描述
我們解釋一下每個字段的含義:

  • Id: 就是這個線程的唯一標識,當我們發現這個線程有問題的時候,可以通過 kill 命令,加上這個Id值將這個線程殺掉,前面我們說了show processlist 顯示的信息時來自information_schema.processlist 表,所以這個Id就是這個表的主鍵。
  • User: 就是指啓動這個線程的用戶。
  • Host: 記錄了發送請求的客戶端的 IP 和 端口號。通過這些信息在排查問題的時候,我們可以定位到是哪個客戶端的哪個進程發送的請求。
  • DB: 當前執行的命令是在哪一個數據庫上,如果沒有指定數據庫,則該值爲 NULL 。
  • Command: 是指此刻該線程正在執行的命令:
- Binlog Dump: 主節點正在將二進制日誌 ,同步到從節點
- Change User: 正在執行一個 change-user 的操作
- Close Stmt: 正在關閉一個Prepared Statement 對象
- Connect: 一個從節點連上了主節點
- Connect Out: 一個從節點正在連主節點
- Create DB: 正在執行一個create-database 的操作
- Daemon: 服務器內部線程,而不是來自客戶端的鏈接
- Debug: 線程正在生成調試信息
- Delayed Insert: 該線程是一個延遲插入的處理程序
- Drop DB: 正在執行一個 drop-database 的操作
- Execute: 正在執行一個 Prepared Statement
- Fetch: 正在從Prepared Statement 中獲取執行結果
- Field List: 正在獲取表的列信息
- Init DB: 該線程正在選取一個默認的數據庫
- Kill : 正在執行 kill 語句,殺死指定線程
- Long Data: 正在從Prepared Statement 中檢索 long data
- Ping: 正在處理 server-ping 的請求
- Prepare: 該線程正在準備一個 Prepared Statement
- ProcessList: 該線程正在生成服務器線程相關信息
- Query: 該線程正在執行一個語句
- Quit: 該線程正在退出
- Refresh:該線程正在刷表,日誌或緩存;或者在重置狀態變量,或者在複製服務器信息
- Register Slave: 正在註冊從節點
- Reset Stmt: 正在重置 prepared statement
- Set Option: 正在設置或重置客戶端的 statement-execution 選項
- Shutdown: 正在關閉服務器
- Sleep: 正在等待客戶端向它發送執行語句
- Statistics: 該線程正在生成 server-status 信息
- Table Dump: 正在發送表的內容到從服務器
  • Time: 表示該線程處於當前狀態的時間。
  • State: 線程的狀態,和 Command 對應。
  • Info: 一般記錄的是線程執行的語句。默認只顯示前100個字符,也就是你看到的語句可能是截斷了的,要看全部信息,需要使用 show full processlist

經常用到的查詢sql:

-- 按客戶端 IP 分組,看哪個客戶端的鏈接數最多
select client_ip,count(client_ip) as client_num from (select substring_index(host,':' ,1) as client_ip from processlist ) as connect_info group by client_ip order by client_num desc;

-- 查看正在執行的線程,並按 Time 倒排序,看看有沒有執行時間特別長的線程
select * from information_schema.processlist where Command != 'Sleep' order by

-- 找出所有執行時間超過 5 分鐘的線程,拼湊出 kill 語句,方便後面查殺
select concat('kill ', id, ';') from information_schema.processlist where Command != 'Sleep' and Time > 300 order by Time desc;

使用 mysql 時要注意的細節

  • 對於事務的操作,檢查在事務中是否存在RPC調用、HTTP調用、消息隊列操作、緩存、循環查詢等耗時的操作,這個操作應該移到事務之外,因爲這些操作會增加事務的處理時間,使sql查詢不穩定,理想的情況是事務內只處理數據庫操作;
  • 可以針對sql查詢提供報警功能,如果某個sql查詢時間大於某個閾值時,應該立即報警;
  • 讀寫分離,提高併發效率,主從複製,提高高可用性;
  • 當只要一行數據時使用LIMIT 1,這樣檢索到一條數據後,就停止搜索了;
  • 根據數據值的範圍,選擇正確的數據類型進行存儲;
  • 在定義字段類型時,如果一些字符串範圍固定,使用enum類型而不是varchar類型,提高性能
  • 使用 explain 函數分析查詢性能進行優化

參考文獻