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 query_id=@query_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 小結
轉載于:https://www.cnblogs.com/BradMiller/p/10123397.html
總結
以上是生活随笔為你收集整理的18.Mysql SQL优化的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一个能描述erp系统的小故事。
- 下一篇: 使用SCOM常用的一些Managemen