最近公司項目添加新功能,上線後發現有些功能的列表查詢時間好久。緣由是新功能用到舊功能的接口,而這些舊接口的 SQL 查詢語句關聯5,6張表且編寫不夠規範,致使 MySQL 在執行 SQL 語句時索引失效,進行全表掃描。本來負責優化的同事有事請假回家,所以優化查詢數據的問題落在筆者手中。筆者在查閱網上 SQL 優化的資料後成功解決了問題,在此從全局角度記錄和總結 MySQL 查詢優化相關技巧。mysql
數據查詢慢,不表明 SQL 語句寫法有問題。 首先,咱們須要找到問題的源頭才能「對症下藥」。筆者用一張流程圖展現 MySQL 優化的思路:程序員
無需更多言語,從圖中能夠清楚地看出,致使數據查詢慢的緣由有多種,如:緩存失效,在此一段時間內因爲高併發訪問致使 MySQL 服務器崩潰;SQL 語句編寫問題;MySQL 服務器參數問題;硬件配置限制 MySQL 服務性能問題等。正則表達式
若是系統的併發請求數不高,且查詢速度慢,能夠忽略該步驟直接進行 SQL 語句調優步驟。sql
執行命令:數據庫
show status
因爲返回結果太多,此處不貼出結果。其中,再返回的結果中,咱們主要關注 「Queries」、「Threads_connected」 和 「Threads_running」 的值,即查詢次數、線程鏈接數和線程運行數。vim
咱們能夠經過執行以下腳本監控 MySQL 服務器運行的狀態值緩存
#!/bin/bash while true do mysqladmin -uroot -p"密碼" ext | awk '/Queries/{q=$4}/Threads_connected/{c=$4}/Threads_running/{r=$4}END{printf("%d %d %d\n",q,c,r)}' >> status.txt sleep 1 done
執行該腳本 24 小時,獲取 status.txt 裏的內容,再次經過 awk 計算每秒請求 MySQL 服務的次數安全
awk '{q=$1-last;last=$1}{printf("%d %d %d\n",q,$2,$3)}' status.txt
複製計算好的內容到 Excel 中生成圖表觀察數據週期性。性能優化
若是觀察的數據有周期性的變化,如上圖的解釋,須要修改緩存失效策略。bash
例如:
經過隨機數在[3,6,9] 區間獲取其中一個值做爲緩存失效時間,這樣分散了緩存失效時間,從而節省了一部份內存的消耗。
當訪問高峯期時,一部分請求分流到未失效的緩存,另外一部分則訪問 MySQL 數據庫,這樣減小了 MySQL 服務器的壓力。
執行命令:
show processlist
返回結果:
mysql> show processlist; +----+------+-----------+------+---------+------+----------+------------------+ | Id | User | Host | db | Command | Time | State | Info | +----+------+-----------+------+---------+------+----------+------------------+ | 9 | root | localhost | test | Query | 0 | starting | show processlist | +----+------+-----------+------+---------+------+----------+------------------+ 1 row in set (0.00 sec)
從返回結果中咱們能夠了解該線程執行了什麼命令/SQL 語句以及執行的時間。實際應用中,查詢的返回結果會有 N 條記錄。
其中,返回的 State 的值是咱們判斷性能好壞的關鍵,其值出現以下內容,則該行記錄的 SQL 語句須要優化:
Converting HEAP to MyISAM # 查詢結果太大時,把結果放到磁盤,嚴重 Create tmp table #建立臨時表,嚴重 Copying to tmp table on disk #把內存臨時表複製到磁盤,嚴重 locked #被其餘查詢鎖住,嚴重 loggin slow query #記錄慢查詢 Sorting result #排序
State 字段有不少值,如需瞭解更多,能夠參看文章末尾提供的連接。
在配置文件 my.cnf 中的 [mysqld] 一行下邊添加兩個參數:
slow_query_log = 1 slow_query_log_file=/var/lib/mysql/slow-query.log long_query_time = 2 log_queries_not_using_indexes = 1
其中,slow_query_log = 1 表示開啓慢查詢;
slow_query_log_file 表示慢查詢日誌存放的位置;
long_query_time = 2 表示查詢 >=2 秒才記錄日誌;
log_queries_not_using_indexes = 1 記錄沒有使用索引的 SQL 語句。
注意:slow_query_log_file 的路徑不能隨便寫,不然 MySQL 服務器可能沒有權限將日誌文件寫到指定的目錄中。建議直接複製上文的路徑。
修改保存文件後,重啓 MySQL 服務。在 /var/lib/mysql/ 目錄下會建立 slow-query.log 日誌文件。鏈接 MySQL 服務端執行以下命令能夠查看配置狀況。
show variables like 'slow_query%'; show variables like 'long_query_time';
測試慢查詢日誌:
mysql> select sleep(2); +----------+ | sleep(2) | +----------+ | 0 | +----------+ 1 row in set (2.00 sec)
打開慢查詢日誌文件
[root@localhost mysql]# vim /var/lib/mysql/slow-query.log /usr/sbin/mysqld, Version: 5.7.19-log (MySQL Community Server (GPL)). started with: Tcp port: 0 Unix socket: /var/lib/mysql/mysql.sock Time Id Command Argument # Time: 2017-10-05T04:39:11.408964Z # User@Host: root[root] @ localhost [] Id: 3 # Query_time: 2.001395 Lock_time: 0.000000 Rows_sent: 1 Rows_examined: 0 use test; SET timestamp=1507178351; select sleep(2);
咱們能夠看到剛纔執行了 2 秒的 SQL 語句被記錄下來了。
雖然在慢查詢日誌中記錄查詢慢的 SQL 信息,可是日誌記錄的內容密集且不易查閱。所以,咱們須要經過工具將 SQL 篩選出來。
MySQL 提供 mysqldumpslow 工具對日誌進行分析。咱們可使用 mysqldumpslow --help 查看命令相關用法。
經常使用參數以下:
-s:排序方式,後邊接着以下參數 c:訪問次數 l:鎖定時間 r:返回記錄 t:查詢時間 al:平均鎖定時間 ar:平均返回記錄書 at:平均查詢時間 -t:返回前面多少條的數據 -g:翻遍搭配一個正則表達式,大小寫不敏感
案例:
獲取返回記錄集最多的10個sql mysqldumpslow -s r -t 10 /var/lib/mysql/slow-query.log 獲取訪問次數最多的10個sql mysqldumpslow -s c -t 10 /var/lib/mysql/slow-query.log 獲取按照時間排序的前10條裏面含有左鏈接的查詢語句 mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/slow-query.log
篩選出有問題的 SQL,咱們可使用 MySQL 提供的 explain 查看 SQL 執行計劃狀況(關聯表,表查詢順序、索引使用狀況等)。
用法:
explain select * from category;
返回結果:
mysql> explain select * from category; +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------+ | 1 | SIMPLE | category | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | NULL | +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------+ 1 row in set, 1 warning (0.00 sec)
字段解釋:
1. id:select 查詢序列號。id相同,執行順序由上至下;id不一樣,id值越大優先級越高,越先被執行
2. select_type:查詢數據的操做類型,其值以下:
simple:簡單查詢,不包含子查詢或 union primary:包含複雜的子查詢,最外層查詢標記爲該值 subquery:在 select 或 where 包含子查詢,被標記爲該值 derived:在 from 列表中包含的子查詢被標記爲該值,MySQL 會遞歸執行這些子查詢,把結果放在臨時表 union:若第二個 select 出如今 union 以後,則被標記爲該值。若 union 包含在 from 的子查詢中,外層 select 被標記爲 derived union result:從 union 表獲取結果的 select
3. table:顯示該行數據是關於哪張表
4. partitions:匹配的分區
5. type:表的鏈接類型,其值,性能由高到底排列以下:
system:表只有一行記錄,至關於系統表 const:經過索引一次就找到,只匹配一行數據 eq_ref:惟一性索引掃描,對於每一個索引鍵,表中只有一條記錄與之匹配。經常使用於主鍵或惟一索引掃描 ref:非惟一性索引掃描,返回匹配某個單獨值的全部行。用於=、< 或 > 操做符帶索引的列 range:只檢索給定範圍的行,使用一個索引來選擇行。通常使用between、>、<狀況 index:只遍歷索引樹 ALL:全表掃描,性能最差
注:前5種狀況都是理想狀況的索引使用狀況。一般優化至少到range級別,最好能優化到 ref
6. possible_keys:指出 MySQL 使用哪一個索引在該表找到行記錄。若是該值爲 NULL,說明沒有使用索引,能夠創建索引提升性能
7. key:顯示 MySQL 實際使用的索引。若是爲 NULL,則沒有使用索引查詢
8. key_len:表示索引中使用的字節數,經過該列計算查詢中使用的索引的長度。在不損失精確性的狀況下,長度越短越好 顯示的是索引字段的最大長度,並不是實際使用長度
9. ref:顯示該表的索引字段關聯了哪張表的哪一個字段
10. rows:根據表統計信息及選用狀況,大體估算出找到所需的記錄或所需讀取的行數,數值越小越好
11. filtered:返回結果的行數佔讀取行數的百分比,值越大越好
12. extra: 包含不合適在其餘列中顯示但十分重要的額外信息,常見的值以下:
using filesort:說明 MySQL 會對數據使用一個外部的索引排序,而不是按照表內的索引順序進行讀取。出現該值,應該優化 SQL using temporary:使用了臨時表保存中間結果,MySQL 在對查詢結果排序時使用臨時表。常見於排序 order by 和分組查詢 group by。出現該值,應該優化 SQL using index:表示相應的 select 操做使用了覆蓋索引,避免了訪問表的數據行,效率不錯 using where:where 子句用於限制哪一行 using join buffer:使用鏈接緩存 distinct:發現第一個匹配後,中止爲當前的行組合搜索更多的行
注意:出現前 2 個值,SQL 語句必需要優化。
使用 profiling 命令能夠了解 SQL 語句消耗資源的詳細信息(每一個執行步驟的開銷)。
select @@profiling;
返回結果:
mysql> select @@profiling; +-------------+ | @@profiling | +-------------+ | 0 | +-------------+ 1 row in set, 1 warning (0.00 sec)
0 表示關閉狀態,1 表示開啓
set profiling = 1;
返回結果:
mysql> set profiling = 1; Query OK, 0 rows affected, 1 warning (0.00 sec) mysql> select @@profiling; +-------------+ | @@profiling | +-------------+ | 1 | +-------------+ 1 row in set, 1 warning (0.00 sec)
在鏈接關閉後,profiling 狀態自動設置爲關閉狀態。
show profiles;
返回結果:
mysql> show profiles; +----------+------------+------------------------------+ | Query_ID | Duration | Query | +----------+------------+------------------------------+ | 1 | 0.00062925 | select @@profiling | | 2 | 0.00094150 | show tables | | 3 | 0.00119125 | show databases | | 4 | 0.00029750 | SELECT DATABASE() | | 5 | 0.00025975 | show databases | | 6 | 0.00023050 | show tables | | 7 | 0.00042000 | show tables | | 8 | 0.00260675 | desc role | | 9 | 0.00074900 | select name,is_key from role | +----------+------------+------------------------------+ 9 rows in set, 1 warning (0.00 sec)
該命令執行以前,須要執行其餘 SQL 語句纔有記錄。
show profile for query Query_ID;
返回結果:
mysql> show profile for query 9; +----------------------+----------+ | Status | Duration | +----------------------+----------+ | starting | 0.000207 | | checking permissions | 0.000010 | | Opening tables | 0.000042 | | init | 0.000050 | | System lock | 0.000012 | | optimizing | 0.000003 | | statistics | 0.000011 | | preparing | 0.000011 | | executing | 0.000002 | | Sending data | 0.000362 | | end | 0.000006 | | query end | 0.000006 | | closing tables | 0.000006 | | freeing items | 0.000011 | | cleaning up | 0.000013 | +----------------------+----------+ 15 rows in set, 1 warning (0.00 sec)
每行都是狀態變化的過程以及它們持續的時間。Status 這一列和 show processlist 的 State 是一致的。所以,須要優化的注意點與上文描述的同樣。
其中,Status 字段的值一樣能夠參考末尾連接。
show profile block io,cpu for query Query_ID; show profile cpu,block io,memory,swaps,context switches,source for query Query_ID; show profile all for query Query_ID;
主要以查詢優化、索引使用和表結構設計方面進行講解。
避免 SELECT *,須要什麼數據,就查詢對應的字段。
當 B 表的數據集小於 A 表時,用 in 優化 exist;使用 in ,兩表執行順序是先查 B 表,再查 A 表 select * from A where id in (select id from B) 當 A 表的數據集小於 B 表時,用 exist 優化 in;使用 exists,兩表執行順序是先查 A 表,再查 B 表 select * from A where exists (select 1 from B where B.id = A.id)
一些狀況下,可使用鏈接代替子查詢,由於使用 join,MySQL 不會在內存中建立臨時表。
適當添加冗餘字段,減小表關聯。
主鍵自動建立惟一索引
頻繁做爲查詢條件的字段
查詢中與其餘表關聯的字段
查詢中排序的字段
頻繁更新的字段
where 條件中用不到的字段
表記錄太少
常常增刪改的表
單表查詢:哪一個列做查詢條件,就在該列建立索引
多表查詢:left join 時,索引添加到右表關聯字段;right join 時,索引添加到左表關聯字段
不要對索引列進行任何操做(計算、函數、類型轉換)
索引列中不要使用 !=,<> 非等於
索引列不要爲空,且不要使用 is null 或 is not null 判斷
違背上述原則可能會致使索引失效,具體狀況須要使用 explain 命令進行查看
除了違背索引建立和使用原則外,以下狀況也會致使索引失效:
模糊查詢時,以 % 開頭
使用 or 時,如:字段1(非索引)or 字段2(索引)會致使索引失效。
index(a,b,c) ,以字段 a,b,c 做爲複合索引爲例:
語句 | 索引是否生效 |
---|---|
where a = 1 | 是,字段 a 索引生效 |
where a = 1 and b = 2 | 是,字段 a 和 b 索引生效 |
where a = 1 and b = 2 and c = 3 | 是,所有生效 |
where b = 2 或 where c = 3 | 否 |
where a = 1 and c = 3 | 字段 a 生效,字段 c 失效 |
where a = 1 and b > 2 and c = 3 | 字段 a,b 生效,字段 c 失效 |
where a = 1 and b like 'xxx%' and c = 3 | 字段 a,b 生效,字段 c 失效 |
使用能夠存下數據最小的數據類型
使用簡單的數據類型。int 要比 varchar 類型在mysql處理簡單
儘可能使用 tinyint、smallint、mediumint 做爲整數類型而非 int
儘量使用 not null 定義字段,由於 null 佔用4字節空間
儘可能少用 text 類型,非用不可時最好考慮分表
儘可能使用 timestamp 而非 datetime
當數據庫中的數據很是大時,查詢優化方案也不能解決查詢速度慢的問題時,咱們能夠考慮拆分表,讓每張表的數據量變小,從而提升查詢效率。
1. 垂直拆分:將表中多個列分開放到不一樣的表中。例如用戶表中一些字段常常被訪問,將這些字段放在一張表中,另一些不經常使用的字段放在另外一張表中。 插入數據時,使用事務確保兩張表的數據一致性。
2. 水平拆分:按照行進行拆分。例如用戶表中,使用用戶ID,對用戶ID取10的餘數,將用戶數據均勻的分配到0~9的10個用戶表中。查找時也按照這個規則查詢數據。
通常狀況下對數據庫而言都是「讀多寫少」。換言之,數據庫的壓力多數是由於大量的讀取數據的操做形成的。咱們能夠採用數據庫集羣的方案,使用一個庫做爲主庫,負責寫入數據;其餘庫爲從庫,負責讀取數據。這樣能夠緩解對數據庫的訪問壓力。
sort_buffer_size 排序緩衝區內存大小
join_buffer_size 使用鏈接緩衝區大小
read_buffer_size 全表掃描時分配的緩衝區大小
Innodb_log_file_size 事務日誌大小
Innodb_log_files_in_group 事務日誌個數
Innodb_log_buffer_size 事務日誌緩衝區大小
Innodb_flush_log_at_trx_commit 事務日誌刷新策略,其值以下:
0:每秒進行一次 log 寫入 cache,並 flush log 到磁盤
1:在每次事務提交執行 log 寫入 cache,並 flush log 到磁盤
2:每次事務提交,執行 log 數據寫到 cache,每秒執行一次 flush log 到磁盤
expire_logs_days 指定自動清理 binlog 的天數
max_allowed_packet 控制 MySQL 能夠接收的包的大小
skip_name_resolve 禁用 DNS 查找
read_only 禁止非 super 權限用戶寫權限
skip_slave_start 級你用 slave 自動恢復
### 7.4 其餘
max_connections 控制容許的最大鏈接數
tmp_table_size 臨時表大小
max_heap_table_size 最大內存表大小
筆者並無使用這些參數對 MySQL 服務器進行調優,具體詳情介紹和性能效果請參考文章末尾的資料或另行百度。
硬件的性能直接決定 MySQL 數據庫的性能瓶頸,直接決定 MySQL 數據庫的運行數據和效率。
做爲軟件開發程序員,咱們主要關注軟件方面的優化內容,如下硬件方面的優化做爲了解便可
內存的 IO 比硬盤的速度快不少,能夠增長系統的緩衝區容量,使數據在內存停留的時間更長,以減小磁盤的 IO
使用 SSD 或 PCle SSD 設備,至少得到數百倍甚至萬倍的 IOPS 提高
購置陣列卡同時配備 CACHE 及 BBU 模塊,能夠明顯提高 IOPS
### 8.3 配置 CUP 相關
在服務器的 BIOS 設置中,調整以下配置:
選擇 Performance Per Watt Optimized(DAPC)模式,發揮 CPU 最大性能
關閉 C1E 和 C States 等選項,提高 CPU 效率