1. 程式人生 > >18.Mysql SQL優化

18.Mysql SQL優化

18.SQL優化
18.1 優化SQL語句的一般步驟
18.1.1 通過show status命令瞭解各種SQL的執行頻率
show [session|global] status; -- 檢視伺服器狀態資訊
show session status; -- 檢視session(當前連線)級別的伺服器狀態資訊,預設session級別
show global status; -- 檢視global(資料庫啟動至今)級別的伺服器狀態資訊
show status like 'Com_%'; -- 檢視當前session的所有統計資訊,輸出為型別和次數。
show status like 'Innodb_rows_%'; -- 檢視當前session的Innodb引擎的統計資訊,輸出為型別和次數。
Innodb_rows_read 50908229195 -- 查詢返回行數
Innodb_rows_inserted 1587576 -- 新增操作行數
Innodb_rows_deleted 101485 -- 刪除操作行數
Innodb_rows_updated 119280 -- 修改操作行數
show status like 'Com_commit'; -- 提交次數
show status like 'Com_rollback'; -- 回滾次數
show status like 'Connections'; -- 連線伺服器次數
show status like 'Uptime'; -- 伺服器工作時間
show status like 'Slow_queries'; -- 慢查詢次數

18.1.2 定位執行效率較低的SQL語句
1.通過慢查詢日誌定位那些執行效率低的SQL語句,
slow_query_log :指定是否開啟慢查詢日誌
log_slow_queries :指定是否開啟慢查詢日誌(該引數要被slow_query_log取代,做相容性保留)
slow_query_log_file :指定慢日誌檔案存放位置,可以為空,系統會給一個預設的檔案host_name-slow.log
long_query_time :設定慢查詢的閥值,超出次設定值的SQL即被記錄到慢查詢日誌,預設值為10s
min_examined_row_limit :查詢檢查返回少於該引數指定行的SQL不被記錄到慢查詢日誌
log_queries_not_using_indexes :不使用索引的慢查詢日誌是否記錄到索引
注意:慢查詢日誌在查詢後才記錄。
2.使用show processlist命令檢視當前mysql的執行緒,包括執行緒狀態、是否鎖表等。

18.1.3 通過explain分析SQL的執行計劃
explain SQL; -- 檢視SQL的執行計劃
show warnings; -- 檢視優化器將SQL的改寫後的結果
explain partitions SQL; -- 檢視分割槽表SQL的執行計劃及所使用的分割槽

explain SQL輸出解析:
select_type:查詢型別,包括:simple(單表查詢,不使用表連線或子查詢),primary(主查詢,即外層查詢),union(),subquery(子查詢的第一個select)。
table:查詢的表。
type:訪問型別,包括:ALL全表掃描--》index索引全部掃描--》range索引範圍掃描--》ref索引字首掃描--》eq_ref索引唯一掃描--》const,system常量唯一掃描--》NULL不需要訪問表或索引
index索引全部掃描:無條件查詢索引列,如:select 索引列 from 表
range索引範圍掃描:包括如下操作符<,<=,>,>=,between的查詢語句
ref索引字首掃描:使用非唯一索引或唯一索引的字首掃描,可能返回1行或多行。
eq_ref索引唯一掃描:使用唯一索引掃描(主鍵索引或唯一鍵索引),只返回1行。
const,system常量唯一掃描:使用唯一索引掃描(主鍵索引或唯一鍵索引)與常量匹配,只返回1行。
NULL:不需要訪問表或索引,如:select 1+2。
ref_or_null:非唯一索引掃描,且索引列包含null。
index_merge:索引合併優化
unique_subquery:單行子查詢
index_subquery:多行子查詢
possible_keys:備選的索引
key :選中的索引
key_len :選中索引的欄位長度
rows :掃描的行數
Extra :執行情況的說明和描述,如:using where等

18.1.4 通過show profile分析SQL
Profiling可以對某一條sql的效能進行分析,如查詢SQL執行狀態,System lock和Table lock,I/O消耗和CPU消耗 。
Profiling是從 mysql5.0.3版本以後才開放的。
啟動profile之後,所有查詢包括錯誤的語句都會記錄在內。
關閉會話或者set profiling=0 就關閉了。(如果將profiling_history_size引數設定為0,同樣具有關閉MySQL的profiling效果。)
--在mysql5.7之後,通過performance_schema.profiling表來檢視。
select status,sum(duration) as "總花費時間",count(*) as "執行次數",sum(duration)/count(*) as "平均每次花費時間" from performance_schema.profiling where [email protected]_id group by state,order by sum(duration) desc;

引數說明:
@@have_profiling 當前資料庫是否支援Profiling,YES支援
@@profiling 當前資料庫是否開啟Profiling,1開啟,0關閉
開啟Profiling set profiling=1;
關閉Profiling set profiling=0;
在開啟Profiling的狀態下執行SQL語句,
檢視SQL語句query_id:show profiles;
通過query_id檢視SQL語句分析:show profile for query "query_id";
通過query_id檢視SQL語句的I/O和CPU:show profile 分析型別 for query "query_id";
分析型別分為:
all 顯示所有效能資訊,
block io 顯示塊IO的次數
context switches 上下文切換相關開銷
cpu 顯示使用者和系統的CPU使用情況
ipc 顯示傳送和接收的訊息數量
memory 顯示佔用記憶體情況
page faults 顯示頁面故障
source 顯示原始碼的函式名稱,以及在原始碼檔案中的位置
swap 顯示交換次數相關開銷的資訊
例子:
show profile all for query 1;
首行列說明
"Status": "query end", 狀態
"Duration": "1.751142", 持續時間
"CPU_user": "0.008999", cpu使用者
"CPU_system": "0.003999", cpu系統
"Context_voluntary": "98", 上下文主動切換
"Context_involuntary": "0", 上下文被動切換
"Block_ops_in": "8", 塊輸入操作
"Block_ops_out": "32", 塊輸出操作
"Messages_sent": "0", 訊息發出
"Messages_received": "0", 訊息接受
"Page_faults_major": "0", 主分頁錯誤
"Page_faults_minor": "0", 次分頁錯誤
"Swaps": "0", 交換次數
"Source_function": "mys", 源功能
"Source_file": "sql_", 原始檔
"Source_line": "4465" 原始碼行
首列每行資訊(SQL執行狀態)說明:
starting: 開始
checking permissions:檢查許可權
Opening tables: 開啟表
init : 初始化
System lock : 系統鎖
optimizing : 優化
statistics : 統計
preparing : 準備
executing : 執行
Sending data : 傳送資料
Sorting result : 排序
end : 結束
query end : 查詢 結束
closing tables : 關閉表 /去除TMP 表
freeing items : 釋放項
cleaning up : 清理
一般只檢視CPU和BLOCK IO即可:
SHOW profile CPU,BLOCK IO FOR query 1;

18.1.5 通過trace可以分析優化器如何選擇執行計劃
首先,開啟trace,設定格式為JSON,並設定trace的記憶體大小(10MB),避免解析過程因預設記憶體(16K)過小不能完全顯示。
show variables like '%optimizer_trace%';
+------------------------------+----------------------------------------------------------------------------+
| Variable_name | Value |
+------------------------------+----------------------------------------------------------------------------+
| optimizer_trace | enabled=on,one_line=off |
| optimizer_trace_features | greedy_search=on,range_optimizer=on,dynamic_range=on,repeated_subselect=on |
| optimizer_trace_limit | 1 |
| optimizer_trace_max_mem_size | 16384 |
| optimizer_trace_offset | -1
set optimizer_trace="enabled=on";
set end_markers_in_json=on;
set optimizer_trace_max_mem_size=1000000;
接著,執行SQL語句;
最終,在performance_schema.optimizer_trace表檢視分析結果。

18.1.6 確定問題並採取相應的優化措施
show status; --檢視系統狀態
show processlist; --檢視當前執行緒及狀態
通過慢查詢日誌找到歷史上比較慢的的SQL;
通過explain分析執行計劃;
通過performance_schema.profiling分析SQL在哪個狀態產生了CPU耗時時長或IO高;
通過performance_schema.optimizer_trace分析執行計劃為什麼和預期的不一致;
最終通過增加索引、改建索引、改寫SQL的措施優化SQL執行速度。

18.2 索引問題
18.2.1 索引的儲存分類
索引由儲存引擎實現,每種儲存引擎支援的不同型別的索引。
InnoDB僅支援B-Tree索引。
MyISAM支援B-Tree索引、R-Tree索引、Full-Text索引。
Memory支援B-Tree索引、Hash索引。
Mysql不支援函式索引,但支援字首索引,字首索引在Order by和Group by操作中不能使用。
字首索引即只對某個列的前N個字元建立索引,
語法:
create index 索引名 on 表名(列名(字首長度));
例子:
create index idx_emp_ename on emp(ename(3));
Hash索引只支援等值比較(=),不支援範圍比較(<、<=、>、>=)。
B-Tree索引是將索引列構造成平衡多叉樹,包括一個根節點,多個分支節點,多個葉子節點。
B-Tree索引可用於關鍵字等值比較、關鍵字範圍比較和關鍵字字首比較等。
建立B-Tree索引語法:
create index 索引名 on 表名(列名); -- 如果包含多個列名,則稱為複合索引。
alter table 表名 add index 索引名(列名);
刪除B-Tree索引語法:
drop index 索引名;
alter table 表名 drop index 索引名;
18.2.2 mysql如何使用索引
訪問型別type=const
當where條件全部使用到B-Tree索引,且索引列使用等值比較,且右值為常量時訪問型別type=const。
訪問型別type=range
當where條件全部使用到B-Tree索引,且索引列使用範圍比較,且右值為常量時訪問型別type=range。
當where條件使用到B-Tree索引(字首索引),且索引列或複合索引的首使用右模糊比較(like 'xxx%')時,訪問型別type=range。
訪問型別type=ref
當where條件部分使用到B-Tree索引(複合索引的首列),且為等值比較,且右值為常量時訪問型別type=ref。
當where條件全部使用到B-Tree索引(複合索引)時,且首列為精確匹配,非首列為範圍匹配時,訪問型別type=ref。
當where條件索引列 is null時,訪問型別type=ref。
當where條件部分使用到B-Tree索引(複合索引的非首列)時,將不會使用索引。
訪問型別type=eq_ref
當where條件部分使用B-Tree索引(唯一索引) 時,訪問型別type=eq_ref。
Extra=using where
表示通過索引找到記錄的編號,再去表裡查詢記錄的詳細資訊。
Extra=using index
表示通過索引找到記錄的資訊後,不需要再去表裡查詢記錄的詳細資訊。
Extra=using index condition
表示根據複合索引首列的條件在索引裡找,再根據非首列的條件在索引裡進行二次過濾,最後再去表裡查詢記錄的詳細資訊。

索引失效:
左模糊(like '%xxx')和全模糊(like '%xxx%')將導致索引失效;
索引列出現資料型別隱式轉換時(字元列=數字)將導致索引失效;
where條件中不包括複合索引的首列時,將導致複合索引失效;
當使用索引查詢到的記錄是全部記錄的20%以上時,使用索引比全表掃描更慢,將導致索引失效;
or操作將導致索引失效;

18.2.3 檢視索引使用情況
show status like 'Handler_read%';
Handler_read_key :表示一個行被索引值讀的次數,值高時考慮增加索引;
Handler_read_rnd_next :表示在資料檔案中讀取下一行的請求數,值高時說明全表掃描多,應考慮增加索引。

18.3 兩個簡單實用的優化方法
18.3.1 定期分析表和檢查表
分析表語法:
analyze [local|no_write_to_binlog] table 表名,...;
用於分析和儲存表的關鍵字分佈,分析的結果將可以使系統得到準確的統計資訊,使生成的SQL執行計劃更準確。
分析過程會對錶進行鎖定。
檢查表語法:
check table 表名,... [{quick|fast|medium|extended|changed}]
用於檢查一個或多個表(或檢視)是否有錯誤。

18.3.2 定期優化表
優化表語法:
optimize [local|no_write_to_binlog] table 表名,...;
用於合併空間碎片,刪除語句和修改語句(由大改小)會導致產生空間碎片。
innodb_file_per_table引數可以為每個表設定獨立的表空間,即每個表單獨一個數據檔案(.ibd),表資料和表上索引的資料均存在該檔案中。
另一種回收空間碎片的方法是修改表(不修改儲存引擎),alter table 表名 engine=innodb;
analyze、check、optimize、alter table執行期間都會對錶進行鎖定,請避開業務高峰期。

18.4 常用SQL的優化
18.4.1 大批量插入資料
對於MyISAM儲存引擎,在匯入資料前可disable keys,即只插入資料不插入索引。
例子:
alter table 表名 disable keys;
load data infile '/home/mysql/檔名.txt' into table 表名;
alter table 表名 enable keys;
對於InnoDB儲存引擎,可以將匯入資料按照主鍵列有序排列,關閉唯一約束檢查,關閉自動提交等優化速度。
set unique_checks=0;
set autocommit=0;
load data infile '/home/mysql/檔名.txt' into table 表名;
set unique_checks=1;
set autocommit=1;

18.4.2 優化insert語句
儘量使用多行插入,以減少客戶端與資料庫之間的連線、關閉等消耗;
insert into 表名 values (值列表1),(值列表2),...;
使用insert delayed 語句,將資料先存放在記憶體佇列中;
將索引檔案和資料檔案放在不同的磁碟上(MyISAM);
增加引數bulk_insert_buffer_size的值(MyISAM);
使用load data infile。

18.4.3 優化order by語句
檢查表上的索引:
show index from 表名;
-- cardinality 不同值的個數
-- index_type 索引型別
通過索引順序掃描的結果是有序的,不需要額外的排序操作,Extra=using index。
未通過索引順序掃描的結果是無序的,需要對結果進行再排序,Extra=using filesort。
排序操作根據排序緩衝區(sort_buffer_size)容量設定和結果記錄量大小決定是否使用磁碟檔案或臨時表。
filesort排序演算法:將結果在引數sort_buffer_size設定的記憶體中進行排序;
如果該記憶體裝載不能裝載全部結果資料,將按照引數sort_buffer_size設定將結果資料分塊,
每塊在記憶體中排序後分別儲存在硬碟中,最終合併各個塊中結果資料輸出。
sort_buffer_size為每個程序分配單獨的sort buffer排序區。
1.優化的目標是:儘量減少額外的排序,通過索引直接返回資料。
當where條件和order by使用相同的索引,且是相同的升降序,否則需要額外排序。
使用索引排序的例子:
select * from 表名 order by key_part1,key_part2;
select * from 表名 order by key_part1 desc,key_part2 desc;
select * from 表名 where key_part1=xxx order by key_part1 desc,key_part2 desc; -- 範圍比較將導致需要排序
需要額外排序的例子:
select * from 表名 order by key_part1 desc,key_part2 asc;
select * from 表名 order by key1,key2;
select * from 表名 where key1=xxx order by key2;
2.filesort優化
自動選擇兩次掃描演算法或一次掃描演算法來優化排序。
兩次掃描演算法:首先根據條件取出排序欄位和行指標資訊,根據排序欄位在記憶體中排序,根據排序後的行指標順序的回表去讀行資訊。
優點:記憶體開銷少,能儘量避免排序時記憶體與硬碟交換資料。
缺點:需要從表中讀取兩次資料,且第二次讀取時回產生大量的隨機I/O操作。
一次掃描演算法:一次性取出滿足條件的行的所有資訊,然後在排序區排序後輸出結果。
優點:只用讀取一次資料,效率較高。
缺點:記憶體開銷大。
Mysql根據引數max_length_for_sort_data與SQL結果進行比較,引數max_length_for_sort_data更大時採用一次掃描演算法;否則採用兩次掃描演算法。

18.4.4 優化group by語句
Mysql對所有 group by col1,col2進行排序,
如果SQL包含了相同列的 order by語句,則只會進行一次排序;
如果SQL包含了不同列的 order by語句,則會進行兩次排序;
如果SQL包含了order by null 語句,則不會進行排序。

18.4.5 優化巢狀查詢
子查詢可以將多步操作一次完成,避免事務或者鎖表。
子查詢一般可等價的寫成表連線,且連線的效率要優於子查詢。

18.4.6 優化or條件
or 關鍵字可等價的替換為in或union。

18.4.7 優化分頁查詢
在索引上完成分頁操作,再通過行指標取表中查詢當前頁的記錄。
select * from 表名 order by 排序列 limit 之前頁數*每頁顯示行數,每頁顯示行數;
在排序列上建索引後,可改寫為
select * from 表名 a,(select 主鍵列 from 表名 order by 排序列 limit 之前頁數*每頁顯示行數,每頁顯示行數) b where a.主鍵列=b.主鍵列;
把分頁查詢轉換為位置查詢,即將前一頁排序列的臨界值傳遞給下一頁的查詢語句作為查詢條件。
上述兩種方法在遇到排序列有重複值時均不能保證結果正確,不建議使用。

18.4.8 使用SQL提示
SQL提示是在SQL語句中增加提示資訊告訴優化器怎麼執行SQL語句。
use index提示:告訴SQL直譯器使用指定的索引
select * from 表名 use index(索引名) where ...;
force index提示:告訴SQL直譯器強制使用指定的索引
select * from 表名 force index(索引名) where ...;
ignore index提示:告訴SQL直譯器忽略指定的索引
select * from 表名 ignore index(索引名) where ...;

18.5 常用SQL技巧
18.5.1 正則表示式的使用
正則表示式是指一個用來描述或者匹配一系列符合某個句法規則的字串的單個字串。
Mysql提供regexp命令來實現正則表示式,regexp中模式串是區分大小寫的。
"^"在字串開始處進行匹配,返回值:1匹配,0不匹配。
select 'abcdefg' regexp '^a';
"$"在字串末尾處進行匹配,返回值:1匹配,0不匹配。
select 'abcdefg' regexp 'g$';
"."匹配任意單個字元,包括換行符,返回值:1匹配,0不匹配。
select 'abcdefg' regexp '.h','abcdefg' regexp '.f'; -- 查詢字串中是否包含.後個哪個字元
"[...]"匹配[]內的任意字元,返回值:1匹配,0不匹配。
select 'abcdefg' regexp "[fhk]"; -- 查詢字串中是否包含[]內的任意一個或多個字元
"[^...]"不匹配[]內的任意字元,返回值:0匹配,1不匹配。
select 'abcdefg' regexp "[^hk]"; -- 查詢字串中是否不包含[]內的任意一個或多個字元
a* 匹配0個或多個a
a+ 匹配1個或多個a
a? 匹配0個或1個a
a|b 匹配a或b
a(m) 匹配m個a
a(m,) 匹配大於等於m個a
a(,n) 匹配0到n個a
a(m,n) 匹配m個到n個a
(...) 將模式元素組成單一元素

18.5.2 Rand()函式
Rand()函式 產生隨機數;
order by rand() 對記錄進行隨機排序;
order by rand() limit n 隨機輸出n行記錄。
隨機抽樣例子:
select * from 表名 order by rand() limit 100;

18.5.3 利用group by的with rollup子句
SQL中使用group by的with rollup子句可以對每個分組的資訊再進行聚合。
例子1:對所有分組再次聚合
select 分組列1,sum(聚合列),count(聚合列),avg(聚合列),max(聚合列),min(聚合列)
from 表名
where 1=1 group by 分組列1 with rollup;
例子2:先對分組按分組列1再聚合,再對所有分組再次聚合
select 分組列1,分組列2,sum(聚合列),count(聚合列),avg(聚合列),max(聚合列),min(聚合列)
from 表名
where 1=1 group by 分組列1,分組列2 with rollup;
注意:with rollup子句後不能再跟order by子句,但可以跟limit子句。

18.5.4 用bit group functions做統計
bit_and()函式:按位與操作;
bit_or()函式:按位或操作;
例子:分組後對聚合列進行按位與、按位或操作
select 分組列,bit_and(聚合列),bit_or(聚合列) from 表名 where 1=1 group by 分組列;

18.5.5 資料庫名、表名大小寫問題
作業系統大小寫敏感性決定了資料庫名、表名、表別名的大小寫敏感性。
Unix和Linux對大小寫是敏感的,Windows對大小寫不敏感。
建議資料庫名、表名統一採用大寫或小寫。
列名、索引名、儲存過程名、函式名、觸發器名在所有平臺大小寫均不敏感。
Mysql引數lower_case_tables_name=0,依據作業系統大小寫敏感性儲存並查詢;
Mysql引數lower_case_tables_name=1,mysql自動轉小寫儲存並查詢;
Mysql引數lower_case_tables_name=2,依據作業系統大小寫敏感性儲存,mysql自動轉小寫查詢。

18.5.6 使用外來鍵需要注意的問題
InnoDB儲存引擎支援外來鍵;其餘儲存引擎不支援外來鍵(不會報錯,但不存在外來鍵約束)。

18.6 小結