MySQL優化(SQL語句及索引優化)

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'
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章