文章目錄
MySQL優化(SQL語句及索引優化)
最近在複習算法,爲明年的春招做準備,歡迎互關呀,共同學習,進步!
一,慢查詢日誌
1.什麼是慢查詢日誌?
MySQL的慢查詢日誌是MySQL提供的一種日誌記錄,它用來記錄在MySQL中響應時間超過閥值的語句,具體指運行時間超過long_query_time值的SQL,則會被記錄到慢查詢日誌中。long_query_time的默認值爲10,意思是運行10S以上的語句。默認情況下,Mysql數據庫並不啓動慢查詢日誌,需要我們手動來設置這個參數,當然,如果不是調優需要的話,一般不建議啓動該參數,因爲開啓慢查詢日誌會或多或少帶來一定的性能影響。慢查詢日誌支持將日誌記錄寫入文件,也支持將日誌記錄寫入數據庫表。
查看慢查詢日誌是否開啓
show VARIABLES LIKE 'slow_query_log'
慢查詢日誌默認是沒有開啓的。
開啓慢查詢日誌
set GLOBAL slow_query_log = ON;
我們再看看沒有索引的查詢會不會被記錄到慢查詢日誌中
show VARIABLES LIKE 'log_queries_not_using_indexes'
默認是沒有開啓的
我們也要開啓將沒有使用索引的查詢記錄到慢查詢日誌中
set GLOBAL log_queries_not_using_indexes = ON
在查看一下
查看查詢時間超過多少秒會被記錄到慢查詢日誌中
show VARIABLES LIKE 'long_query_time'
爲了能記錄下所有的查詢語句,方便學習,我決定將他設置爲0
SET GLOBAL long_query_time = 0.000000
如果不能修改,可以關閉此次會話後新建一個會話重試
查看慢查詢日誌文件存放在哪
show VARIABLES LIKE 'slow_query_log_file'
設置下慢查詢日誌存放在自定義的路徑
set GLOBAL slow_query_log_file = '自定義路徑'
2.日誌內容
這個就是慢查詢日誌記錄的內容:
先看第一行,記錄了日誌記錄時間
Time:140606 12:30:17
第二行,記錄了執行SQL的主機信息
User@Host:root[root] @ localhost []
第三行,記錄了SQL的執行信息,從左到右分別是:SQL執行耗時,鎖定時間,發送行數,掃描行數及SQL執行時間
Query_time:0.000031 Lock_time:0.000000 Row_sent:0 Row_examined:0 SET timestamp = 1402029017
最一行是執行的語句
show Tables
3.分析SQL執行計劃
使用expain查詢SQL的執行計劃
格式:
EXPLAIN SQL語句
比如:
EXPLAIN SELECT * FROM film_text
顯示結果:
參數解釋:
-
table:所查詢的表
-
type:顯示連接使用了何種類型,從最好到最差的連接類型分別是:
const(唯一索引查找) --> eq_reg(唯一索引或主鍵的範圍查找) --> ref(連接查詢) --> range(範圍查找) --> index(索引掃描) --> ALL(表索引)
-
possible_keys:顯示可能應用在這張表中的索引,如果爲空,沒有可能的索引
-
key:實際使用的索引,爲null則沒有使用索引
-
key_len:使用的索引的長度,索引長度越短越好
-
ref:顯示索引的哪一列被使用了,如果可能的話,是一個常數
-
rows:MYSQL認爲必須檢查的用來返回請求數據的行數
-
擴展列:
- Using fileSort使用了文件排序
- Using temporary使用了臨時表
關於上文中的文件排序
當不能使用索引生成排序結果時,Mysql需要自己進行排序,此時會出現兩種情況
- 當數據量小的時候,則在內存中進行,在內存中進行快速排序
- 當數據量大的時候,MySQL會將數據分塊,對每個獨立的塊分別快速排序,將排序結果存在磁盤上,然後將每個排好序的塊進行合併,最後返回排序結果
以上兩種情況均屬於文件排序
如何通過慢查詢日誌發現有問題的SQL
- 查詢次數多其每次查詢佔用時間長的SQL
- IO大的SQL
- 未命中索引的SQL
二,SQL優化
1.優化count()和Max()
下邊以tb_test表作爲示例
CREATE TABLE `tb_test` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`test` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;
先看看普通MAX執行計劃
EXPLAIN SELECT MAX(test) from tb_test
EXPLAIN SELECT MAX(test) from tb_test
覆蓋索引:
- 解釋一: 就是select的數據列只用從索引中就能夠取得,不必從數據表中讀取,換句話說查詢列要被所使用的索引覆蓋。
- 解釋二: 索引是高效找到行的一個方法,當能通過檢索索引就可以讀取想要的數據,那就不需要再到數據表中讀取行了。如果一個索引包含了(或覆蓋了)滿足查詢語句中字段與條件的數據就叫做覆蓋索引。
- 解釋三:是非聚集組合索引的一種形式,它包括在查詢裏的Select、Join和Where子句用到的所有列(即建立索引的字段正好是覆蓋查詢語句[select子句]與查詢條件[Where子句]中所涉及的字段,也即,索引包含了查詢正在查找的所有數據)。
不是所有類型的索引都可以成爲覆蓋索引。覆蓋索引必須要存儲索引的列,而哈希索引、空間索引和全文索引等都不存儲索引列的值,所以MySQL只能使用B-Tree索引做覆蓋索引
從上圖中的查詢計劃可以看出,使用了索引後,沒有做表掃描,結果是從索引直接獲得的,這就是上面提到的覆蓋索引,**也可以這麼理解,對於max和min這樣的查詢,對於max,只需要讀到索引B-Tree的最後一個索引記錄就可以取到max,相反,min的話只需要取到B-Tree中第一條索引記錄即可,**這樣即使數據量增大,查詢效率幾乎不受影響。
所以,對於max()查詢的優化,我們可以通過給查詢列建立索引的方式優化的我們的查詢效率。
接下來我們看看count()這種如何優化?
我們先看看count(*)和count(某列)有什麼區別?
我們新建一個表
CREATE TABLE `tb_count` (
`val` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
注意該表可以包含null數據
分別執行count(*)和count(val)
select count(*) as 'count(*)',COUNT(val) as 'count(val' from tb_count
可以看到
- count(*):表中所有行,不論是否是null
- count(指定列):只統計非null行
查看執行計劃
EXPLAIN select COUNT(*) as 'count(val' from tb_count;
EXPLAIN select COUNT(val) as 'count(val' from tb_count
發現count(*)和count(指定列)的執行計劃都是做了一個全表掃描,掃描行數還是全表的數據行數
我們嘗試加一個索引
發現兩者執行計劃還是一樣,只是這次用到了索引,效率比之前要高些,但是掃描行數大家都是4行
所以,在討論如何優化count函數之前,我們得先明白count函數到底有什麼作用?
count()函數有着兩種非常不同的作用
- 他可以統計某個列值的數量(count(具體列))
- 也可以統計行數(count(*)),當這種情況下,他會忽略所有列進而統計所有行的行數
還有一個就是關於MyISAM這個存儲引擎的count函數很快,其實是有條件的,就是當沒有任何where條件的count(*)才非常快,如果帶有where子句,那麼跟其他存儲引擎並沒有什麼區別,所以在MyISAM中執行count函數有時候比別的引擎快,有時候慢,看情況而定
count函數查詢優化:
-
在MyISAM引擎中,我們可以這樣簡單優化下count函數查詢,我們可以利用count(*)全表非常快這個特性,加速一些帶有特定條件的count()查詢,例如我們需要看一個5000行的數據裏找到id大於5的行數,我們可以這樣
select (select count(*) from table) - count(*) from table where id <= 5
在這個sql中的子查詢,可以用到MyISAM不帶where子句時 count(*)特別快的特性,而且掃描的行數也僅僅只有5行
select count(*) from table
如果這樣的話,就不能發揮出MyISAM不帶where子句時 count(*)特別快的特性,掃描的行數還特別大,足足有4995行
select count(*) from table where id > 5
但是MyISAM不支持事務,所以還需要看具體業務而定,置於MyISAM中不帶where條件的count(*)爲何如此之快,是因爲在MyISAM中維護了一個變量來存放所有數據行的行數。
2.子查詢和關聯查詢優化
子查詢優化:
還是上文那兩張表,tb_test和tb_count
tb_test:
tb_count:
select * from tb_test where test in(select val from tb_count);
返回結果:
可以看到,原來在tb_count表中2是重複的(即存在一對多的關係),但是子查詢的時候,查出來的結果卻沒有重複
我們現在改造成join的關聯查詢
select * from tb_test JOIN tb_count on tb_test.test = tb_count.val
結果:
這種一對多關係到了關聯查詢的時候,就會重複,所以還需要注意的是當子查詢優化爲關聯查詢時,需要注意去重
select DISTINCT * from tb_test JOIN tb_count on tb_test.test = tb_count.val
關於子查詢優化,最關鍵的就是使用關聯查詢替代子查詢,但在優化時要注意關聯鍵是否存在一對多的關係,要注意重複數據的去重
關聯查詢優化:
在談優化之前,我們先了解下在MySQL中關聯查詢如何執行?
mysql中,任何一次查詢都是一次關聯,每一個查詢(包括子查詢,表單的簡單select)都可能是關聯
基本執行思路是這樣的:
MySQL現在一個表中循環取出單條數據,然後在嵌套循環到下一個表中尋找匹配的行,依次下去,知道找到所有表中匹配的行爲止。然後根據各個表匹配的行,返回查詢中需要的各個列。MySQL會嘗試在最後一個關聯表中找到所有匹配的行,如果最後一個關聯表無法找到更多的行,mysql會返回上一層表,看能否找到更多記錄
比如:
select tb1.col1 , tb2.col2 from tb1 inner join tb2 using(col3) where tb1.col1 in(5,6)
mysql執行該sql的僞代碼如下:
如何優化關聯查詢:
-
on或者using子句上的列上有索引,在創建索引的時候,要考慮關聯順序,一般來說,如果沒有特別理由,只需要在關聯順序中的第二個表中的相關列建立索引即可,太多索引會佔用空間
-
關聯查詢中的任何group by或者order by子句或者表達式中只涉及關聯表中的一個表的列,這樣mysql纔可能使用索引來優化這個過程
3.group by優化
刪除上文中的tb_count表中的index索引後查看sql執行計劃
EXPLAIN SELECT * FROM tb_count GROUP BY val
可以看出在沒有索引,group by做了一個全表掃描和使用到了文件排序和臨時表
爲了避免使用文件排序和臨時表的情況,我們需要對sql進行一定的優化:
查找了網上一些博客分析GROUP BY 與臨時表的關係 :
1. 如果GROUP BY 的列沒有索引,產生臨時表.
2. 如果GROUP BY時,SELECT的列不止GROUP BY列一個,並且GROUP BY的列不是主鍵 ,產生臨時表.
3. 如果GROUP BY的列有索引,ORDER BY的列沒索引.產生臨時表.
4. 如果GROUP BY的列和ORDER BY的列不一樣,即使都有索引也會產生臨時表.
5. 如果GROUP BY或ORDER BY的列不是來自JOIN語句第一個表.會產生臨時表.
6. 如果DISTINCT 和 ORDER BY的列沒有索引,產生臨時表.
所以:
- 如果要對關聯查詢做group by分組,那麼要確保關聯查詢group by子句中使用到列應該只來自一張表,且該列應建立索引,另外, 應該注意分組和關聯的順序,先對該表分組、分組後再和其他表關聯查詢 。( 先利用索引將結果集快速最小化、然後再和其他表關聯 )
- 普通單表查詢的話索引就可以了
- order by也需要注意上面兩點
4.limit優化
limit常用於分頁處理,時長會伴隨着order by從句使用,大多數時候都會使用文件排序造成大量的IO問題
EXPLAIN select * from tb_count ORDER BY val LIMIT 2
如果我們接下來使用有索引的列或者主鍵列進行order by操作
執行相同語句,看執行計劃
可以看到,原來沒有使用索引,掃描4行即全部數據行,使用了索引掃描2行,其實,limit在mysql中是這樣做的,他是先進行全表掃描,然後在根據limit的參數進行結果集截取,但是當我們使用了索引,根據索引的排序性(BTree)我們可以輕鬆完成order by的工作,且掃描的行數大大降低,但是雖然這樣,如果我們要掃描的行數過多,比如我從500行開始往後找5行,這樣就會掃描505行,雖然說不用全表掃描,但是當越往後找,IO壓力也會越大
EXPLAIN select * from tb_count ORDER BY val LIMIT 2 ,2
優化建議:
-
使用有索引的列或者主鍵列進行order by操作
-
要注意避免數據量大時掃描行數過多
三,索引優化
1.儘量不要出現重複索引
第一步
儘量不要出現重複索引,何爲重複索引?重複索引是指相同的列以相同的順序建立的同類的索引,比如主鍵和唯一索引
create table test(
id int not null primay key,
name varchar(10) not null,
title varchar(50) not null,
unique(id)
)engine = innodb
就像這裏邊的id既是唯一索引也是主鍵,就是重複索引.
2.減少冗餘索引
第二:
減少冗餘索引,什麼是冗餘索引就是指多個索引的前綴列相同,或是在聯合索引中包含了主鍵索引
create table test(
id int not null primay key,
name varchar(10) not null,
title varchar(50) not null,
key(name,id)
)engine = innodb
上邊就是在聯合索引中包含了主鍵索引,導致索引冗餘,因爲innodb的特性,innodb會在沒個索引的後邊加上主鍵索引,
還有前綴列相同就是下邊說到的聯合索引的情況,
建立了聯合索引(a,b,c)就沒必要建立一個獨立索引a
3.where子句後邊的列索引只能用上1個
第三:
注意where子句後邊的列不能都加上索引
select * from table where age = 1 and price > 100
where子句後邊的age和price如果兩個都是獨立的索引,那麼同時只能用上1個
4.多列索引必須遵循最左匹配原則
第四:
多列索引必須遵循最左匹配原則,索引順序應該遵循離散度大的列放的越前
先說最左匹配原則
所謂最左匹配原則,就是假如我創建了聯合索引index(a,b,c)
如果,我在查詢中:
where a = 1 and b = 1 and c = 1 #索引中abc三列都走
where a = 1 and b = 1 #索引中ab兩列走
where a = 1 and c = 1 #索引中ac兩列走
所以從上面可以看出,最左匹配中的一個要求:索引中排第一的列必須出現,索引纔會生效,比如上邊是索引生效的組合可以有:
abc
ab
ac
a
所以不生效的如:
bc
b
c
上面都是等值查詢,下邊涉及到範圍查詢
1. where a = 1 and b = 1 and c > 1
2. where a = 1 and b > 1 and c = 1
上邊的where子句中只有第一句會走索引,走索引中的abc列,第二句不走列c,這涉及到多列索引中第二個要求:當在查詢中出現範圍查詢時, 存儲引擎不能使用索引中範圍條件右邊的列
還有,建立了聯合索引(a,b,c)就沒必要建立獨立索引(a)
如果有order by或者group by的情景,也要注意索引的有序性
比如:
where a = ? and b = ? order by c
這樣可以建立key(a,b,c)的聯合索引,order by 最後的字段是組合索引的一部分且放在組合索引最後,避免出現文件排序fileSort
5.儘量使用覆蓋索引
第五:
**儘量使用覆蓋索引(只訪問索引的查詢(索引列包含的查詢列)減少select ***
比如在登錄驗證中:
select user_time from user where user_name = ? and password = ?
可以建立key(user_name,password,user_time)的聯合索引
6.前導模糊查詢不能使用索引
第六:
前導模糊查詢不能使用索引
select * from table where title like '%ax'
該語句屬於前導模糊查詢,即使title是索引,也不能使用到
非前導模糊查詢可以使用索引
select * table from title like 'abc%'
7.union,in,or都能命中索引推薦使用in
第七:
union,in,or都能命中索引,但是cpu耗費上,union < in < or
所以一般推薦使用in
比如:
select * from table where a = 1
union
select * from table where a = 2
直接告訴mysql怎麼做,cpu耗費最少,但是一般推薦使用in
select * from table where a in (1,2)
or的話,cpu耗費最大,不建議
select * from table where a = 1 or a = 2
8.負向條件查詢不能使用索引,可以優化爲in查詢
第八:
負向條件查詢不能使用索引,可以優化爲in查詢,負向條件有:!=,< >,not in , not exists , not like
9.建立索引的列,不能爲null,聯合索引不存全爲null的值
第九:
建立索引的列,不能爲null,聯合索引不存全爲null的值
10.使用索引查詢時,避免強制類型轉換
第十:
在使用索引查詢時,避免強制類型轉換,強制類型轉換會導致全表掃描,例如,phone本來是varchar類型
select * from table where phone = 123456789
這樣就不能命中索引.
應改爲:
select * from table where phone = '123456789'