一、爲什麼需要SQL優化?
在系統開發初期,由於數據庫中數據較少,對於SQL語句,複雜的視圖的編寫等看不出SQL語句的各種寫法的性能優劣,但是隨着應用的持續開發,系統交付實際應用之後,隨着數據量的增加,系統的響應速度很慢,這就需要系統解決了,而最重要的一個優化就是SQL優化。對於海量數據,優質的SQL與劣質的SQL之間的速度差距可以達到上百倍,對於一個系統,不僅僅是隻是實現其功能,還要寫出高質量的SQL語句,提高系統的可用性。SQL優化,可以體現在如下的幾個方法。
二、SQL優化-SQL語句優化
2.1 慢查日誌開啓
慢查詢日誌 slow_query_log,是用來記錄查詢比較慢的sql語句,通過查詢日誌來查找哪條sql語句比較慢,對比較慢的sql可以進行優化 。
-
登錄數據庫;
show global variables like '%slow%'; //查看慢查日誌存放路徑。
-
開啓慢查詢日誌
set global slow_query_log=on;
3.查看慢查日誌的設置時間,是否是自己需要的。
show global variables like '%long%';
-
默認設置的是10秒,如果不是自己需要的時間,修改慢查詢時間,只要超過了設置的時間,查詢的日誌就會添加到慢查日誌文件中,本次設置超時時間爲1s,將超過1s的SQL寫入慢查日誌中。
set global long_query_time=1;
5.數據準備,大數據或者複雜SQL查詢;
6.查看日誌:打開日誌(打開慢查日誌文件,)
慢查日誌內容: # Time: 181202 10:43:31 //執行時間 # User@Host: qiulin[qiulin] @ localhost [127.0.0.1] //使用的機器 # Query_time: 0.811781 Lock_time: 0.000000 Rows_sent: 160655 //執行時間 Rows_examined: 160668 //掃描行數 SET timestamp=1543718611; select * from tb_content where title like '%小%'; //執行的語句
7.執行計劃打印,就是在sql前面加上explain關鍵字
全表掃描,描述extra中,表示只使用了where條件,沒有其他什麼索引之類的。這裏只是簡單如何使用慢查詢,這條語句沒法優化。
explain:查詢語句,返回各列含義 table:查詢的那張表 type:顯示使用何種類型。const(常量) > eq_req(範圍) > ref(連接) > range(索引範圍) >index(索引掃描) > all(全表) prossible_key:可能應用在這張表的索引 extra:使用的條件
2.2 常見SQL語句優化
1. count() 和 max()優化
儘量使用索引列區max(),count();當數據量很大時,max()需要掃描某列所有值比較,當建立索引時,只需掃描最後一行,大大減少了查詢時間。
count()優化,首先區分count(*),count(列名)、count(1); count(*),count(1) 查詢的結果統計包含null值 count(id)忽略null值。
2. 子查詢優化
通常情況下,需要將子查詢優化爲join查詢,但在優化時需要注意關聯是否有一對多的關係(會出現數據的重複-distinct去重),需要注意重複數據。因爲:連接查詢不需要建立臨時表,因此其速度比子查詢快。
SELECT * FROM tb_order WHERE user_id =(SELECT id FROM tb_user WHERE username ='tom123' ) SELECT * FROM tb_order b JOIN tb_user u ON b.user_id = u.id WHERE u.username='tom123'
3. order by,group by優化
儘量使用同一張表的列,group by ,where 條件儘量使用索引。
1.order by優化 實現方式: 1. 根據索引字段排序,利用索引取出的數據已經是排好序的,直接返回給客戶端; 2. 沒有用到索引,將取出的數據進行一次排序操作後返回給客戶端。 EXPLAIN SELECT m.id,m.subject,c.content FROM group_message m,group_message_content c WHERE m.group_id = 1 AND m.id = c.group_msg_id ORDER BY m.user_id\G; create_time對query進行了優化,它會按照user_id上的索引順序來訪問數據,這樣獲取的數據已經是排好序的。 這種利用索引實現數據排序的方法是 MySQL 中實現結果集排序的最佳做法,利用已有的索引避免實際的排序計算所帶來的資源消耗。
==order By 關鍵字優化 1.儘量使用index方式排序,避免使用Filesort方式排序。 2.Mysql支持兩種排序方式,Filesort和Index,Index效率高, 它指Mysql掃描索引本身完成排序。FileSort排序方式效率低 ==order by滿足兩種情況,會使用index排序 1.order by 語句使用索引最左前列 2.使用where子句和order By子句條件滿足索引最左前列。
group by的實現過程除了要使用排序操作外,還要進行分組操作,如果使用到一些聚合函數,還要進行相應的聚合計算。 group by優化: 1. 儘可能利用索引並且是鬆散索引來完成group by操作,這的依靠調整索引或者調整query來實現; 2. 當無法利用索引的時候,必須要提供足夠的sort_buffer_size來供mysql完成排序操作,之前介紹過,不然mysql會將需要排序的字段進行分段排序,會影響性能。除此之外儘量不要對大結果集進行group by操作,因爲一旦數據量超過系統最大臨時表大小時,mysql會將臨時表裏的數據copy到磁盤上然後再進行操作,性能會成數量級的下降。
group by 實質是先排序後進行分組,遵照索引建的最佳左前綴。 Where高於having,能寫在where限定的條件就不要去having限定了。
4.limit 優化(數據量大的情況下,會產生大量IO),避免過多掃描,使用有索引的列或者主鍵進行order by操作。
三、SQL優化-索引優化
3.1 索引的概念
索引是一種特殊的文件(InnoDB數據表上的索引是表空間的一個組成部分),它們包含着對數據表裏所有記錄的引用指針。更通俗的說,數據庫索引好比是一本書前面的目錄,能加快數據庫的查詢速度。
索引分爲聚簇索引和非聚簇索引兩種,聚簇索引是按照數據存放的物理位置爲順序的,而非聚簇索引就不一樣了;聚簇索引能提高多行檢索的速度,而非聚簇索引對於單行的檢索很快。
3.2 索引的類型
3.2.1 普通索引
這是最基本的索引,它沒有任何限制,比如上文中爲title字段創建的索引就是一個普通索引,MyIASM中默認的BTREE類型的索引,也是我們大多數情況下用到的索引。
1.創建索引 create index index_name on table (column(length)) 2.修改索引 alter table table_name add index index_name on (column(length)) 3.刪除索引 drop index index_name on table
3.2.2 唯一索引
與普通索引類似,不同的就是:索引列的值必須唯一,但允許有空值(注意和主鍵不同)。如果是組合索引,則列值的組合必須唯一,創建方法和普通索引類似。
1.創建唯一索引 CREATE UNIQUE INDEX indexName ON table(column(length)) 2.修改表結構 ALTER TABLE table_name ADD UNIQUE indexName ON (column(length))
3.2.3 全文索引(FULLTEXT)
MySQL從3.23.23版開始支持全文索引和全文檢索,FULLTEXT索引僅可用於 MyISAM 表;他們可以從CHAR、VARCHAR或TEXT列中作爲CREATE TABLE語句的一部分被創建,或是隨後使用ALTER TABLE 或CREATE INDEX被添加。
1.創建表的時候添加全文索引 create table 'tb_item'( 'id' int(11) not null auto_increment, 'title' char(255) CHARACTER not null, 'content' text , 'time' int(10) , primary key('id'), fulltext(content) ); 2.修改表結構添加全文索引 ALTER TABLE tb_item ADD FULLTEXT index_content(content) 3.直接創建索引 CREATE FULLTEXT INDEX index_content ON tb_item(content)
3.2.4 聚集索引(最左前綴)
平時用的SQL查詢語句一般都有比較多的限制條件,所以爲了進一步榨取MySQL的效率,就要考慮建立組合索引。例如上表中針對title和time建立一個組合索引:ALTER TABLE tb_item ADD INDEX index_titme_time (title(50),time(10))。建立這樣的組合索引,其實是相當於分別建立了下面兩組組合索引:
–title,time
–title
3.3 索引的使用。
使用索引的好處就是加快查詢速度,但過多的使用索引將會造成濫用。因此索引也會有它的缺點:雖然索引大大提高了查詢速度,同時卻會降低更新表的速度,如對錶進行INSERT、UPDATE和DELETE。一張表上索引越多,在數據增刪改的時候越慢,因爲更新表時,MySQL不僅要保存數據,還要保存一下索引文件。建立索引會佔用磁盤空間的索引文件。一般情況這個問題不太嚴重,但如果你在一個大表上創建了多種組合索引,索引文件的會膨脹很快。
3.3.1 合適的地方添加索引
-
在where 從句,group by 從句,order by 從句,on從句中出現的列。
-
索引字段越小越好
-
離散度大的列放在聚集索引的前面。(唯一值越多,離散度越高。可用count(distinct )判斷)
3.3.2 索引優化
1.使用聚集索引或非聚集索引正確時機
動作描述 | 使用聚集索引 | 使用非聚集索引 |
---|---|---|
列經常被分組排序 | 使用 | 1用 |
返回某範圍內的數據 | 使用 | 不使用 |
一個或極少不同值 | 不使用 | 不使用 |
小數目的不同值 | 使用 | 不使用 |
大數目的不同值 | 不使用 | 使用 |
頻繁更新的列 | 不使用 | 使用 |
外鍵列 | 使用 | 使用 |
主鍵列 | 使用 | 使用 |
頻繁修改索引列 | 不使用 | 使用 |
2. 索引不會包含有NULL值的列
只要列中包含有NULL值都將不會被包含在索引中,複合索引中只要有一列含有NULL值,那麼這一列對於此複合索引就是無效的。所以我們在數據庫設計時不要讓字段的默認值爲NULL。
3.使用短索引
對串列進行索引,如果可能應該指定一個前綴長度。例如,如果有一個CHAR(255)的列,如果在前10個或20個字符內,多數值是惟一的,那麼就不要對整個列進行索引。短索引不僅可以提高查詢速度而且可以節省磁盤空間和I/O操作。
4. 索引列排序
MySQL查詢只使用一個索引,因此如果where子句中已經使用了索引的話,那麼order by中的列是不會使用索引的。因此數據庫默認排序可以符合要求的情況下不要使用排序操作;儘量不要包含多個列的排序,如果需要最好給這些列創建複合索引。
5. like語句操作
一般情況下不鼓勵使用like操作,如果非使用不可,如何使用也是一個問題。like “%aaa%” 不會使用索引而like “aaa%”可以使用索引。
6.不要在列上進行運算
例如:select * from tb_item where YEAR(from_unixTime(time))<2007,將在每個行上進行運算,這將導致索引失效而進行全表掃描,因此我們可以改成:select * from tb_item where time<unix_timestamp(’2007-01-01′)。
7.選擇合適的存儲引擎
Myisam 適合一些需要大量查詢的應用,但對於大量寫操作並不友好,因爲它用到的是表級鎖,在更新的時候,整張表都會被鎖起來。SELECT COUNT(*) 計算很快。InnoDB :對於一些較小的應用,比Myisam慢,行級鎖,多用於寫操作較多的時候,支持事務。
最後總結一下,MySQL只對一下操作符才使用索引:<,<=,=,>,>=,between,in,以及某些時候的like(不以通配符%或_開頭的情形)。而理論上每張表裏面最多可創建16個索引,不過除非是數據量真的很多,否則過多的使用索引也不是那麼好玩的,比如我剛纔針對text類型的字段創建索引的時候,系統差點就卡死了。
3.2 刪除冗餘的索引
1.冗餘和重複索引
mysql允許在相同列上創建多個索引,無論是有意還是無意,mysql需要單獨維護重複的索引,並且優化器在優化查詢的時候也需要逐個地進行考慮,這會影響性能。重複索引是指的在相同的列上按照相同的順序創建的相同類型的索引,應該避免這樣創建重複所以,發現以後也應該立即刪除。但,在相同的列上創建不同類型的索引來滿足不同的查詢需求是可以的。
冗餘索引和重複索引有一些不同,如果創建了索引(a,b),再創建索引(a)就是冗餘索引,因爲這只是前面一個索引的前綴索引,因此(a,b)也可以當作(a)來使用,但是(b,a)就不是冗餘索引,索引(b)也不是,因爲b不是索引(a,b)的最左前綴列,另外,其他不同類型的索引在相同列上創建(如哈希索引和全文索引)不會是btree索引的冗餘索引。
2. 冗餘索引清理規則
-
第一個索引是第二個索引的左前綴索引, 刪除第一個索引,創建了索引(a,b),刪除索引(a)。
-
有主鍵, 用不到唯一索引, 刪除唯一索引。
-
有主鍵, 用不到普通索引, 刪除普通索引。
-
有唯一索引, 用不到普通索引, 刪除普通索引。
-
有聯合主鍵索引, 用不到普通組合索引, 刪除普通組合索引。
-
索引重複, 刪除其中一個索引。
四、SQL優化-數據表及數據結構優化
4.1 選擇合適的數據類型
1. 使用可以存下你的數據的最小的數據類型。
選擇合適的標識符是非常重要的。選擇時不僅應該考慮存儲類型,而且應該考慮MySQL是怎樣進行運算和比較的。一旦選定數據類型,應該保證所有相關的表都使用相同的數據類型。(1) 整型:通常是作爲標識符的最好選擇,因爲可以更快的處理,而且可以設置爲AUTO_INCREMENT。
(2) 字符串:儘量避免使用字符串作爲標識符,它們消耗更好的空間,處理起來也較慢。而且,通常來說,字符串都是隨機的,所以它們在索引中的位置也是隨機的,這會導致頁面分裂、隨機訪問磁盤,聚簇索引分裂(對於使用聚簇索引的存儲引擎)。
2.使用簡單的數據類型。Int要不varchar類型在MySQL上處理簡單。
eg1:用int 存儲日期時間,可以使用 Unix_timestamp(時間字符串) 將字符串轉int 類型 from_unixTime(int) 將整數轉成字符串格式 eg2: 用bigint存儲ip地址。 inet_aton(ip字符串) 將ip地址轉爲int類型 inet_ntoa(int數據) 將整數型ip轉字符串
3.儘可能使用not null 定義字段,設置默認值。
儘量避免NULL:應該指定列爲NOT NULL,除非你想存儲NULL。在MySQL中,含有空值的列很難進行查詢優化,因爲它們使得索引、索引的統計信息以及比較運算更加複雜。你應該用0、一個特殊的值或者一個空串代替空值。
4.儘量減少text類型,非用不可時最好考慮分表。
char: char不用多說了,它是定長格式的,但是長度範圍是0~255. 當你想要儲存一個長度不足255的字符時,MySQL會用空格來填充剩下的字符。因此在讀取數據時,char類型的數據要進行處理,把後面的空格去除。
varchar: 關於varchar,有的說最大長度是255,也有的說是65535,查閱很多資料後發現是這樣的:varchar類型在5.0.3以下的版本中的最大長度限制爲255,而在5.0.3及以上的版本中,varchar數據類型的長度支持到了65535,也就是說可以存放65532個字節(注意是字節而不是字符)的數據 。
text:與char和varchar不同的是,text不可以有默認值,其最大長度是2的16次方-1
5.分庫分表
單表的字段控制在20以內爲最佳,而且字段儘量短小但是含義清晰。大字段或者過多的字段都會影響執行效率。
按照具體的業務需求,合理的設計數據庫和表,將數據合理分開存儲。單表數量最好不超過500萬記錄。一個數據中表數量最好不多於300。