MySQL優化步驟
首先學會如何定位到SQL語句
1.1查看SQL語句的執行次數
在MySQL中可以通過命令查看服務器該表狀態信息
show status like 'Com_______';
如果想查看整個數據庫信息
show global status like 'Com_______';
下面這些對於所有存儲引擎的表操作都會進行累計
- Com_select:執行 select 操作的次數,一次查詢只累加 1。
- Com_insert:執行 INSERT 操作的次數,對於批量插入的 INSERT 操作,只累加一次。
- Com_update:執行 UPDATE 操作的次數。
- Com_delete:執行 DELETE 操作的次數。
有專門針對Innodb統計的,其中 rows_read
代表的是讀取的行數。
show status like 'Innodb_rows_%';
對於事務型的應用,通過 Com_commit 和 Com_rollback 可以了解事務提交和回滾的情況, 對於回滾操作非常頻繁的數據庫,可能意味着應用編寫存在問題。
1.2 定位執行效率較低的SQL語句
- 通過慢查詢日志定位那些執行效率較低的 SQL 語句,用--log-slow-queries[=file_name]選 項啟動時,mysqld 寫一個包含所有執行時間超過 long_query_time 秒的 SQL 語句的日志 文件。具體可以查看本書第 26 章中日志管理的相關部分。
- 慢查詢日志在查詢結束以后才紀錄,所以在應用反映執行效率出現問題的時候查詢慢查 詢日志並不能定位問題,可以使用show processlist命令查看當前MySQL在進行的線程, 包括線程的狀態、是否鎖表等,可以實時地查看 SQL 的執行情況,同時對一些鎖表操 作進行優化。
通過下面命令可以查看MySQL進程
- Id:數據庫連接id
- User:顯示當前用戶
- Host:從哪個ip的哪個端口上發的
- db:數據庫
- Command:連接的狀態,休眠(sleep),查詢(query),連接(connect)
- Time:秒
- State:SQL語句執行狀態,可能需要經過copying to tmp table、sorting result、sending data等狀態才可以完成
- Info:SQL語句
1.3 通過 EXPLAIN 分析低效SQL的執行計划
找到相應的SQL語句之后,可以EXPLALIN獲取MySQL的執行信息。
其中每個列的解釋:
id:id相同表示加載表的執行順序從上到下,id越大加載的優先級越高
select_type:表示 SELECT 的類型,常見的取值有
- SIMPLE(簡單表,即不使用表連接 或者子查詢)
- PRIMARY(主查詢,即外層的查詢)
- UNION(UNION 中的第二個或 者后面的查詢語句)
- SUBQUERY(子查詢中的第一個 SELECT)
table:輸出結果集的表
type:表示表的連接類型,性能好到壞的結果
- system(表中僅有一行,即常量表)
- const(單表中最多有一個匹配行,只能查詢出來一條)
- eq_ref(對於前面的每一行,在此表中只有一條查詢數據,類似於主鍵和唯一索引)
- ref(與eq_ref類式,區別是不使用主鍵和唯一索引)
- ref_ir_null(與ref類似,區別在於對NULL的查詢)
- index_merge(索引合並優化)
- unique_subquery(in 的后面是一個查詢主鍵字段的子查詢)
- index_subquery(與 unique_subquery 類似, 區別在於 in 的后面是查詢非唯一索引字段的子查詢)
- range(單表中的范圍查詢)、
- index(對於前面的每一行,都通過查詢索引來得到數據)
- all(對於前面的每一行, 207 都通過全表掃描來得到數據)
possible_keys:表示查詢時,可能使用的索引。
key:表示實際使用的索引
key_len:索引字段的長度
rows:掃描行的數量
Extra:執行情況的說明和描述
根據以上內容創建 Teacher
、 Student
表,通過ClassID關聯
create table Teacher
(
teacherId int not NULL AUTO_INCREMENT,
teacherName VARCHAR(50),
ClassID int,
primary key (teacherId)
) ENGINE =innodb DEFAULT charset=utf8;
create table Student
(
StudentID int not NULL AUTO_INCREMENT,
ClassId int,
StudentName varchar(50),
primary key (StudentID)
) ENGINE = INNODB DEFAULT charset=utf8;
INSERT into Teacher(teacherName,ClassID) values("小李",204),("小劉",205),("小楊",206);
INSERT into Student(ClassId,StudentName) VALUES(204,"張三"),(205,"李四"),(206,"王五");
explain-id
(1)、Id相同表示執行順序從上到下
EXPLAIN select * from Teacher t,Student s where t.ClassID=s.ClassID;
(2)、Id不同表示,Id越大越先執行
explain select *from Teacher where ClassId =( select ClassId from Student where StudentName='張三');
(3)、Id有相同的也有不同的,先執行Id大的,再從上到下執行。
explain select_type
(1)、SIMLPLE簡單的select查詢,不包含子查詢或者UNION
explain select * from Teacher;
(2)、PRIMARY查詢當中包含了子查詢,最外層就是改查詢的標記
(3)、SUBQUERY在select或者Where中包含了子查詢
explain select *from Teacher where ClassId=(select ClassId from Student where StudentId=1);
(4)、DERIVED在form列表包含子查詢
explain select * from (select * from Student where Student.StudentID>2 ) a where a.ClassID=204;
如果查詢顯示都是SIMLPLE是因為mysql5.7對 derived_merge 參數默認設置為on,也就是開啟狀態,我們在mysql5.7中把它關閉 shut downn 使用如下命令就可以了
set session optimizer_switch=`derived_merge=off`;
set global optimizer_switch=`derived_merge=off`;
(5)、UNION 、UNION RESULT
explain select * from Student where StudentID=1 union select * from Student where StudentID=2;
UNION指的是后面那個Select,UNION RESULT 將前面的select語句和后面的select聯合起來。
explain-type
(1)、NULL直接返回結果,不訪問任何表索引
select NOW();
(2)、system查詢結果只有一條的數據,const類型的特例
explain select * from (select * from Student where StudentID=1) a;
(3)、const根據主鍵或者唯一索引進行查詢,表示一次就找到了
EXPLAIN select * from Student where StudentID=1;
(4)、eq_ref 索引是主鍵或者唯一索引,使用多表關聯查詢查詢出來的數據只有一條
explain select * from Student s,Teacher t where s.StudentID=t.teacherId
(5)、ref 根據非唯一性的索引查詢,返回的記錄有多條,比如給某個字段添加索引
explain select * from Student s WHERE StudentName='張三1';
(6)、range 范圍查詢 between <> in等操作,前提是用索引,要自己設定索引字段;
explain select * from Student where StudentID in (2,3);
(7)、index 遍歷整個索引樹,相當於查詢了整張表的索引
explain select StudentID from Student;
(8)、ALL 遍歷所有數據文件
explain select * from Student;
通過這個Type就可以判斷當前查詢返回了多少行,有沒有走索引還是走全表掃描
結果從最好到最壞
NULL > system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
system > const > eq_ref > ref > range > index > ALL
explain-key
(1)possible_keys:可能用到的索引
(2)key:實際用到的索引
(3)key_len:key的長度,越短越好
explain-rows
sql語句執行掃描的行數
explain-extra
(1)using filesort :會對進行文件排序即內容,而不是按索引排序,效率慢
EXPLAIN select *from Student order by StudentName;
如果要優化的話可以對該字段建索引
(2)using index 根據根據索引直接查,避免訪問表的數據行
explain select StudentID from Student order by StudentID ;
(3)using temporary 使用臨時表保存結果,在沒有索引的情況下,需要進行優化
EXPLAIN select * from Teacher t GROUP BY teacherName;
報錯:Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'demo_01.Teacher.teacherName' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
解決辦法:
1、找到mysql的配置文件 my.ini (一般在mysql根目錄)
2、在my.cn中將以下內容添加到 [mysqld]下
我的是:etc/my.cnf
sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
1.4 show profile分析SQL
show profile可以分析sql運行的時間,通過 have_profiling
可以查看MySQL是否支持profile
默認profiling是關閉的,可以通過語句打開
set profiling=1;//打開
執行SQL語句之后樂意通過show profiles指令,來查看語句的耗時
show profiles;
可以通過Show profile for query Query_id查看每個階段的耗時
Show profile for query 2;
其中Sending data表示來說訪問數據庫並把結果返回給數據庫的過程,MySQL需要做大量的磁盤讀取操作,因此是最耗時的。
在知道最消耗時間的狀態后,可以選擇all、cpu、block to、context switch、page fault等明細查看在什么資源上浪費了時間
show profile cpu for query 2;
1.5 trace分析優化器執行計划
Mysql有一個優化器按照規則對SQL進行優化處理,trace就是用來分析優化器的執行計划
首先開啟trace開關,然后設置trace文件占用的內存空間
set optimizer_trace="enabled=on",end_markers_in_json=on;
set optimizer_trace_max_mem_size=1000000;
執行SQL語句之后檢查系統表就可以知道如何執行的SQL
select * from information_schema.optimizer_trace\G;
*************************** 1. row ***************************
QUERY: select * from Student where StudentId<1
TRACE: {
"steps": [
{
"join_preparation": {
"select#": 1,
"steps": [
{
"expanded_query": "/* select#1 */ select `Student`.`StudentID` AS `StudentID`,`Student`.`ClassId` AS `ClassId`,`Student`.`StudentName` AS `StudentName` from `Student` where (`Student`.`StudentID` < 1)" //把*查詢的都解析出來了
}
] /* steps */
} /* join_preparation */
},
{
"join_optimization": {
"select#": 1,
"steps": [
{
"condition_processing": {
"condition": "WHERE",
"original_condition": "(`Student`.`StudentID` < 1)",
"steps": [
{
"transformation": "equality_propagation",
"resulting_condition": "(`Student`.`StudentID` < 1)"
},
{
"transformation": "constant_propagation",
"resulting_condition": "(`Student`.`StudentID` < 1)"
},
{
"transformation": "trivial_condition_removal",
"resulting_condition": "(`Student`.`StudentID` < 1)"
}
] /* steps */
} /* condition_processing */
},
{
"substitute_generated_columns": {
} /* substitute_generated_columns */
},
{
"table_dependencies": [
{
"table": "`Student`",
"row_may_be_null": false,
"map_bit": 0,
"depends_on_map_bits": [
] /* depends_on_map_bits */
}
] /* table_dependencies */
},
{
"ref_optimizer_key_uses": [
] /* ref_optimizer_key_uses */
},
{
"rows_estimation": [
{
"table": "`Student`",
"range_analysis": {
"table_scan": {
"rows": 4,
"cost": 3.9
} /* table_scan */,
"potential_range_indexes": [
{
"index": "PRIMARY",
"usable": true,
"key_parts": [
"StudentID"
] /* key_parts */
},
{
"index": "index_id_Student",
"usable": true,
"key_parts": [
"StudentID"
] /* key_parts */
},
{
"index": "index_Name_Student",
"usable": false,
"cause": "not_applicable"
}
] /* potential_range_indexes */,
"setup_range_conditions": [
] /* setup_range_conditions */,
"group_index_range": {
"chosen": false,
"cause": "not_group_by_or_distinct"
} /* group_index_range */,
"analyzing_range_alternatives": {
"range_scan_alternatives": [
{
"index": "PRIMARY",
"ranges": [
"StudentID < 1"
] /* ranges */,
"index_dives_for_eq_ranges": true,
"rowid_ordered": true,
"using_mrr": false,
"index_only": false,
"rows": 1,
"cost": 1.21,
"chosen": true
},
{
"index": "index_id_Student",
"ranges": [
"StudentID < 1"
] /* ranges */,
"index_dives_for_eq_ranges": true,
"rowid_ordered": false,
"using_mrr": false,
"index_only": false,
"rows": 1,
"cost": 2.21,
"chosen": false,
"cause": "cost"
}
] /* range_scan_alternatives */,
"analyzing_roworder_intersect": {
"usable": false,
"cause": "too_few_roworder_scans"
} /* analyzing_roworder_intersect */
} /* analyzing_range_alternatives */,
"chosen_range_access_summary": {
"range_access_plan": {
"type": "range_scan",
"index": "PRIMARY",
"rows": 1,
"ranges": [
"StudentID < 1"
] /* ranges */
} /* range_access_plan */,
"rows_for_plan": 1,
"cost_for_plan": 1.21,
"chosen": true
} /* chosen_range_access_summary */
} /* range_analysis */
}
] /* rows_estimation */
},
{
"considered_execution_plans": [
{
"plan_prefix": [
] /* plan_prefix */,
"table": "`Student`",
"best_access_path": {
"considered_access_paths": [
{
"rows_to_scan": 1,
"access_type": "range",
"range_details": {
"used_index": "PRIMARY"
} /* range_details */,
"resulting_rows": 1,
"cost": 1.41,
"chosen": true
}
] /* considered_access_paths */
} /* best_access_path */,
"condition_filtering_pct": 100,
"rows_for_plan": 1,
"cost_for_plan": 1.41,
"chosen": true
}
] /* considered_execution_plans */
},
{
"attaching_conditions_to_tables": {
"original_condition": "(`Student`.`StudentID` < 1)",
"attached_conditions_computation": [
] /* attached_conditions_computation */,
"attached_conditions_summary": [
{
"table": "`Student`",
"attached": "(`Student`.`StudentID` < 1)"
}
] /* attached_conditions_summary */
} /* attaching_conditions_to_tables */
},
{
"refine_plan": [
{
"table": "`Student`"
}
] /* refine_plan */
}
] /* steps */
} /* join_optimization */
},
{
"join_execution": {
"select#": 1,
"steps": [
] /* steps */
} /* join_execution */
}
] /* steps */
}
1.6 索引使用
(1)索引對查詢效率的提升
根據有索引的ID和名字查詢結果,數據量不是很大只有兩萬可能不是很明顯,有索引的快一些
如果查詢的條件值沒有索引,可以通過創建索引來達到快速查詢的目的
(2)全值匹配,先創建聯合索引,所有列都指定具體值
create index idx_Stuname_id on Student(ClassId,StudentName);
explain select * from Student where StudentName='貨物9000號' and ClassId=9000;
(3)最左前綴法則,從最左邊一個 索引開始匹配,順序位置不受where
影響,法則是查詢的結果包含索引的最左列,且后面沒有跳過其他列。
explain select * from Student where StudentName='貨物9000號' and ClassId=9000;
如果將where后面最左列匹配的索引ClassId
增加一個其他字段就無法用到idx_Stuname_id
索引
explain select * from Student where ClassId=9000 and StudentID=20771 AND StudentName=20771;
走索引就相當於爬樓梯,從一層一層開始爬,一層爬完爬二層,不能直接從二層開始爬,也不能爬了二層開始爬第三層
(3)在范圍查詢的字段后面索引失效
explain select *from Student where 索引1= and 字段>2 and 索引2=
因此索引2將會失效,用不到該索引
(4)如果對某一個列進行了計算操作,索引失效
explain select * from Student where ClassId BETWEEN 20771 and 20111
(5)、如果字符串不加單引號,索引會失效。
(6)、使用覆蓋索引(只訪問索引的查詢),避免使用select *
在查詢的時候將*號改成需要查詢的字段或者索引,減少不必要的開銷,使用索引查詢,using index condition
會將需要的字段查詢出來
using index :使用覆蓋索引的時候就會出現
using where:在查找使用索引的情況下,需要回表去查詢所需的數據
using index condition:查找使用了索引,但是需要回表查詢數據
using index ; using where:查找使用了索引,但是需要的數據都在索引列中能找到,所以不需要回表查詢數據
(7)、如果有 or
后面的字段沒有索引,則整個索引失效
explain select * from Teacher where teacherId=2;
原本主鍵索引
加上or之后,索引失效
explain select * from Teacher where ClassId=204 or teacherId=2;
(8)、以like '%XX'開頭不走索引
正常走索引
explain select * from Student where StudentName LIKE '貨物9000號%';
在like前加上%號
explain select * from Student where StudentName LIKE '%貨物9000號%' ;
不走索引解決辦法:使用覆蓋索引,將*號改成有索引的列,再通過索引查詢
explain select StudentID from Student where StudentName LIKE '%貨物9000號%'
(8)如果再一張表中,一個字段數據基本全是1,只有為2。這時候給該字段建立索引,查詢1的時候mysql認為走全表速度更快就不會走索引,如果查詢2就會走索引。
(9)IS NUL、IS NOT NULL有時走索引
如果一個字段中所有數據都不為空,那么查詢該字段時會走索引,是少量的就會走索引,大多數不會走索引。
EXPLAIN select * from Student where StudentName is NULL;
EXPLAIN select * from Student where StudentName is NOT NULL;
(10)in走索引、not in 不走索引,但也不是絕對的,按照第八條
(11)單列索引和復合索引
create index idx_Stuname_id on Student(ClassId,StudentName);
就相當於創建了三個索引 :
ClassId
StudentName
ClassId + StudentName
如果創建單個索引,數據庫不會全部使用,而是選擇一個最優的。一般選擇辨識度最高的。
(12)查看所有使用情況
show status like 'Handler_read%';
show global status like 'Handler_read%';//全局
Handler_read_first:索引中第一條被讀的次數。如果較高,表示服務器正執行大量全索引掃描(這個值越低越好)。
Handler_read_key:如果索引正在工作,這個值代表一個行被索引值讀的次數,如果值越低,表示索引得到的性能改善不高,因為索引不經常使用(這個值越高越好)。
Handler_read_next :按照鍵順序讀下一行的請求數。如果你用范圍約束或如果執行索引掃描來查詢索引列,該值增加。
Handler_read_prev:按照鍵順序讀前一行的請求數。該讀方法主要用於優化ORDER BY ... DESC。
Handler_read_rnd :根據固定位置讀一行的請求數。如果你正執行大量查詢並需要對結果進行排序該值較高。你可能使用了大量需要MySQL掃描整個表的查詢或你的連接沒有正確使用鍵。這個值較高,意味着運行效率低,應該建立索引來補救。
Handler_read_rnd_next:在數據文件中讀下一行的請求數。如果你正進行大量的表掃描,該值較高。通常說明你的表索引不正確或寫入的查詢沒有利用索引。
1.7 SQL優化
優化批量插入
(1)大批量插入數據時,需要將主鍵按順序插入會快很多
(2)如果插入過程中有唯一索引,可以先關閉索引檢查,防止每插入一條時對索引進行篩查
set unique_checks=1;//1為打開 0為關閉
(3)手動提交事務,關閉自動提交事務
set autocommit=1;//1為打開 0為關閉
優化insert語句
(1)將多條insert語句改為一條
(2)手動開啟事務,全部插入之后,再提交
(3)盡量按主鍵順序插入
優化Order by語句
(1)如果按照多字段排序,要么統一升序要么統一降序
(2)order 不用后面的字段需要和索引的順序保持一致
(3)如果Extra列還出現Using filesort,表示進行了額外的一次排序,考慮使用聯合索引
優化Group by語句
(1)使用Group by如果Extra列出現Using filesort,表示Group by語句默認進行了排序,可以使用Order by null取消排序
(2)使用Group by如果Extra列出現Using Temporary,可以給字段建立索引提高效率
優化嵌套查詢
(1)把多表連接查詢替換為子查詢
優化OR查詢
(1)如果需要用到索引,則每個列需要單獨創建索引,不能用復合索引
(2)使用Union替換Or
優化分頁查詢
(1)根據主鍵進行排序分頁操作,得到主鍵再回原表進行查詢
(2)主鍵自增時,可以直接根據ID查詢,數據沒刪除的情況下
SQL提示
(1)USE index,在有多個索引的情況下,希望Mysql使用該索引,但不是一定會用。
explain select * from sales2 use index (ind_sales2_id) where id = 3
(2)ignore index可以忽略使用該索引,使用其他索引
(3)在數據量很多的情況下,查詢數據占很大比重,即使使用了索引,數據庫也不會用,這時候使用force index強制指定索引。