解析SQL語句執行速度慢的原因(SQL性能優化)

前言

本篇文章主要是通過數據庫鎖機制和索引來分析SQL語句執行速度慢的原因。

偶爾執行速度慢

如果一條SQL語句絕大多數時候執行速度正常,偶爾執行慢。那麼可能是因爲產生了鎖競爭,也可能是數據庫爲了保持數據一致性,在將數據從日誌中刷新到磁盤上。

鎖競爭

因爲事務併發會帶來髒讀、修改丟失、不可重複讀、幻讀等問題,所以數據庫需要使用鎖機制保證數據一致性。數據庫常用的鎖級別有行級鎖、頁級鎖、表級鎖,因爲Mysql默認的存儲引擎InnoDB使用的是行級鎖和表級鎖,因此這裏只對行級鎖和表級鎖做個簡單介紹。行級鎖是加到每一行的記錄的索引之上或者加到記錄索引的間隙裏中(使得被鎖包圍的行記錄可以改,但不能在這個範圍裏面增加行記錄,可以防止新增幻影項)。表級鎖是加到數據庫表上。一個是對錶加鎖,一個是對某些行記錄加鎖,二者的競爭級別不可同日而語。

等待釋放鎖

當數據庫使用的默認存儲引擎使用的是表級鎖,由於鎖的顆粒太大,雖然加鎖很快,且不會出現死鎖,但很容易產生鎖衝突。當你查詢表user,有併發事務正在對user表進行更新,會導致阻塞等待,增加查詢時間。

當數據庫使用的存儲引擎使用的是行級鎖,鎖的顆粒較小,加鎖慢,使用資源多,且可能出現死鎖,但不容易產生鎖衝突。當你查詢表user的id>15,id<1500時。若有別的併發事務在修改這些行記錄,則需要阻塞等待,查詢時間增長。

刷新數據到磁盤

當數據被修改或者有新數據插入表中時,是先在內存中進行插入或者修改數據,這些更新不會立馬持久化到磁盤中,而是將這些更新的數據放入日誌中,然後在空閒時,通過日誌將數據持久化到磁盤中。但當頻繁的對數據庫表進行修改或者插入數據,而日誌的容量又有一定的限度,因此容量快滿時,數據庫就需要停止其它操作,來進行數據持久化,這就會導致此時來數據庫中進行查詢的操作速度很慢。
其它進行數據持久化的情況:
1、數據庫認爲系統空閒的時候。
2、數據庫正常關閉時,將數據同步回磁盤,保持數據一致性。
3、內存不夠用時,需要替換內存中的某些數據頁,此時就需要將某些被替換的髒數據頁(數據不一致的數據頁)持久化到磁盤中。

經常查詢速度慢

經常查詢速度慢那肯定就是SQL語句本身需要優化。或者需要建立索引,或者調整SQL語句以命中索引,或者需要防止回表等。這部分文章會涉及覆蓋索引、冗餘索引、回表操作、最左前綴原則等熱點索引知識,若不是很瞭解可以翻閱我的其它相關博文,例如《全面解析數據庫索引(數據庫索引種類大盤點)》

沒有使用索引導致查詢速度慢

對於經常需要查詢的字段或者是經常用來排序以及作爲查詢條件的字段需要在上面建立索引,通過索引進行查找速度會比全表掃描快的不要太多。這裏沒有進行詳述索引的作用,若是有需要,可以翻閱我的其它相關博文。

建立索引,但沒有使用到索引

1、若是使用聯合索引,但沒有注意聯合索引的最左前綴原則,很容易導致索引未命中。(最左前綴原則不瞭解的話,可以看我的另外一篇博文:《全面解析數據庫索引(數據庫索引種類大盤點)》)
2、在索引字段上面使用了函數或者對等式左邊的索引字段進行了運算,導致沒有使用索引。
錯誤寫法:

select name from user where id+500=1000;
select name from user where pow(id,4)=256;

正確寫法:

select name from user where id=1000-500;
select name from user where id=4;

3、數據類型的隱式轉換導致索引失效

select name,phone from customer where id = '111';

4、雖然字段上有索引,但數據庫優化器選擇錯誤,經過優化後選擇不使用索引,而使用全表掃描,使得執行速度慢。
數據庫優化器通過索引的區分度來確定索引是否值得被使用。一個索引上不同的值越多,意味着索引的區分度越高,索引也就越值得使用。數據庫不可能通過遍歷全表來進行區分度高低的計算,所以數據庫採用的是採樣確定區分度的方法,當採樣無意中採樣的大多是重複索引值數據,導致數據庫誤認爲區分度很低,導致不選擇索引,而是去走全表掃描,就會導致執行速度很低。

5、索引字段沒有設置爲 NOT NULL ,當引擎發現字段列的值爲NULL,引擎放棄使用索引而進行全表掃描。

其它情況導致執行速度慢

1、沒有使用覆蓋索引,導致需要回表操作以及隨機I/O,使得執行速度變慢。

2、索引是冗餘索引或者重複索引,導致增加了查詢優化器生成執行計劃的時間。
3、查詢所有字段或者查詢數據量過大,導致需要從磁盤讀取大量數據到內存中,導致內存不足,需要進行數據頁替換等操作,帶來了額外的開銷。

SQL優化建議

1、對於經常被查詢或者用於條件語句、用來排序或者連表查詢的字段,需要建立索引。

2、多建立聯合索引而不是單列索引,多個單列搜索會比一個聯合索引更浪費存儲空間,而且聯合索引在一定程度上可避免回表操作。

3、建立聯合索引時,把區分度高的字段放在前邊。將經常用來做範圍查詢的字段,放在索引的後面,因爲聯合索引在進行索引匹配時,一碰到像類似於where high>173 and high <183的範圍查詢就會導致索引停止命中,這就是最左匹配原則,不瞭解最左匹配原則的可以看我的另外一篇博文《全面解析數據庫索引(數據庫索引種類大盤點)》

4、充分使用表中的索引,避免使用雙%號的查詢條件,雙%號的查詢條件會導致索引失效,如 name like “%xiaohua%”,如果無前置%,只有後置%,是可以用到列上的索引的。

5、SQL語句儘量避免使用select * from 表名語句,而是需要哪些字段就查詢哪些字段,避免消耗更多的 CPU 和 IO 以網絡帶寬資源,而且避免了無法使用覆蓋索引。

6、使用聯表查詢而不是嵌套子查詢,嵌套子查詢性能低於聯表查詢,嵌套子查詢的子查詢生成的臨時表不能使用索引,速度會很慢。

7、不要在SQL語句的 where 子句中對字段施加函數或者在等號左側對字段進行運算,這會造成無法命中索引。

8、SQL語句應避免數據類型的隱式轉換導致索引失效。

9、禁止使用不含字段列表的 INSERT 語句。
如:insert into t values (‘a’,‘b’,‘c’);
應使用:insert into t(c1,c2,c3) values (‘a’,‘b’,‘c’);

10、對同一列進行 or 判斷時,使用 in 代替 or。in 操作可以更有效的利用索引,or 大多數情況下很少能利用到索引。

11、禁止使用 order by rand() 進行隨機排序。order by rand() 會把表中所有符合條件的數據裝載到內存中,然後在內存中對所有數據根據隨機生成的值進行排序,並且可能會對每一行都生成一個隨機值,如果滿足條件的數據集非常大,就會消耗大量的 CPU 和 IO 及內存資源。推薦在程序中獲取一個隨機值,然後從數據庫中獲取數據的方式。

12、拆分複雜的大 SQL 爲多個小 SQL。大 SQL 邏輯上比較複雜,需要佔用大量 CPU 進行計算的 SQL,MySQL 中,一個 SQL 只能使用一個 CPU 進行計算
,SQL 拆分後可以通過並行執行來提高處理效率。

13、不使用NOT IN和<>操作。NOT IN和<>操作都不會使用索引而是進行全表掃描。NOT IN可以NOT EXISTS代替,id<>3則可使用id>3 or id<3來代替。

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