MySQL数据库优化技巧
1. 使用EXPLAIN
使用EXPLAIN關鍵字可以幫助我們分析select語句,讓我們知道查詢效率低下的原因,從而改進我們查詢,讓查詢優化器能夠更好的工作。
基本思路
- 一定要注意看執行計劃里的 possible_keys、key和rows這三個值
- 讓影響行數盡量少
- 保證使用到正確的索引
- 減少不必要的Using temporary/Using filesort;
字段解釋
| id | 執行編號,標識select所屬的行。如果在語句中沒子查詢或關聯查詢,只有唯一的select,每行都將顯示1。否則,內層的select語句一般會順序編號,對應于其在原始語句中的位置 |
| select_type | 顯示本行是簡單或復雜select。如果查詢有任何復雜的子查詢,則最外層標記為PRIMARY(DERIVED、UNION、UNION RESUlT) |
| table | 訪問引用哪個表(引用某個查詢,如“derived3”) |
| type | 數據訪問/讀取操作類型(ALL、index、range、ref、eq_ref、const/system、NULL) |
| possible_keys | 揭示哪一些索引可能有利于高效的查找 |
| key | 顯示mysql決定采用哪個索引來優化查詢 |
| key_len | 顯示mysql在索引里使用的字節數 |
| ref | 顯示了之前的表在key列記錄的索引中查找值所用的列或常量 |
| rows | 為了找到所需的行而需要讀取的行數,估算值,不精確。通過把所有rows列值相乘,可粗略估算整個查詢會檢查的行數 |
| Extra | 額外信息,如using index、filesort等 |
select_type列
| SUBQUERY | 在select列表中的子查詢,如SELECT *,(SELECT id FROM product_info) AS id FROM product_info |
| DERIVED | 在from子語句中子查詢,如SELECT * FROM product_info p1 ,(SELECT * FROM product_info) p2.Mysql會遞歸執行,并把結果放到臨時表中 |
| UNION | 在UNION中第二個和隨后的SELECT被標記為UNION |
| UNION RESULT | 用來從UNION的匿名臨時表檢索結果的SELECT被標記為UNION RESULT |
| DEPENDENT SUBQUERY | 子查詢中的第一個SELECT,取決于外面的查詢。(需要優化) |
type列(依次從最差到最優):
| All | 最壞的情況,從頭到尾全表掃描 |
| index | 和全表掃描一樣。只是掃描表的時候按照索引次序進行而不是行。主要優點就是避免了排序, 但是開銷仍然非常大。如在Extra列看到Using index,說明正在使用覆蓋索引,只掃描索引的數據,它比按索引次序全表掃描的開銷要小很多 |
| range | 范圍掃描,一個有限制的索引掃描。key 列顯示使用了哪個索引。當使用=、 <>、>、>=、<、<=、IS NULL、<=>、BETWEEN 或者 IN 操作符,用常量比較關鍵字列時,可以使用 range |
| ref | 一種索引訪問,它返回所有匹配某個單個值的行。此類索引訪問只有當使用非唯一性索引或唯一性索引非唯一性前綴時才會發生 |
| eq_ref | 最多只返回一條符合條件的記錄。使用唯一性索引或主鍵查找時會發生 (高效) |
| const/system | 當主鍵放入where子句時,mysql把這個查詢轉為一個常量(高效) |
| Null | 意味說mysql能在優化階段分解查詢語句,在執行階段甚至用不到訪問表或索引(高效) |
Extra列常見情況(需要優化):
| Using temporary | 表示 MySQL 在對查詢結果排序時使用臨時表。常見于排序 order by 和分組查詢 group by |
| Using filesort | 表示 MySQL 會對結果使用一個外部索引排序,而不是從表里按索引次序讀到相關內容??赡茉趦却婊蛘叽疟P上進行排序。MySQL 中無法利用索引完成的排序操作稱為“文件排序” |
2. 建立索引
2.1 基本原則
- 不要在選擇性非常差的字段上建索引
- 查詢條件里出現范圍查詢(如A>7,A in (2,3))時,要警惕,不要建了組合索引卻完全用不上
- 在Join表的時候使用相同類型的列,并將其索引
- select 和order by和 group by字段要建立合適的索引
- 單個索引字段數不超過5個,單表索引數量不超過5個,索引設計遵循B+Tree 索引最左前綴匹配原則
- 建立的索引能覆蓋80%主要的查詢,不求全,解決問題的主要矛盾
- 避免索引的隱式轉換
- 謹慎合理使用索引,改善查詢、減慢更新,索引一定不是越多越好
- innodb主鍵推薦使用自增列、主鍵建立聚簇索引、主鍵不應該被修改、字符串不應該做主鍵(除非采用分布式id),如果不指定主鍵,innodb會使用唯一且非空值索引代替
- 不用外鍵,請由程序保證約束
優化策略A:字段選擇性
- 選擇性較低索引 可能帶來的性能問題
- 索引選擇性=索引列唯一值/表記錄數;(可執行show index from ads命令看字段的Cardinality(散列程度))
- 選擇性越高索引檢索價值越高,消耗系統資源越少;選擇性越低索引檢索價值越低,消耗系統資源越多;
- 查詢條件含有多個字段時,不要在選擇性很低字段上創建索引
- 可通過創建組合索引來增強低字段選擇性和避免選擇性很低字段創建索引帶來副作用;
- 盡量減少possible_keys,正確索引會提高sql查詢速度,過多索引會增加優化器選擇索引的代價,不要濫用索引;
優化策略B:組合索引字段順序
由于 mysql 索引是基于 B-Tree 的,所以組合索引有“字段順序”概念。
所以,查詢條件中有 ac.city_id IN (0, 8005),而組合索引是 (ads_id,city_id),則該查詢無法使用到這個組合索引。
組合索引查詢的各種場景
茲有 Index (A,B,C) ——組合索引多字段是有序的,并且是個完整的BTree索引。
下面條件可以用上該組合索引查詢:
下面條件將不能用上組合索引查詢:
B>5 ——查詢條件不包含組合索引首列字段 B=6 AND C=7 ——查詢條件不包含組合索引首列字段下面條件將能用上部分組合索引查詢:
A>5 AND B=2 ——當范圍查詢使用第一列,查詢條件僅僅能使用第一列 A=5 AND B>6 AND C=2 ——范圍查詢使用第二列,查詢條件僅僅能使用前二列組合索引排序的各種場景
茲有組合索引 Index(A,B)。
下面條件可以用上組合索引排序:
下面條件不能用上組合索引排序:
ORDER BY B ——排序在索引的第二列 A>5 ORDER BY B ——范圍查詢在第一列,排序在第二列 A IN(1,2) ORDER BY B ——理由同上 ORDER BY A ASC, B DESC ——注意,此時兩列以不同順序排序索引合并
順著組合索引怎么建繼續往下延伸,請各位注意“索引合并”概念:
- MySQL 5,0以下版本,SQL查詢時,一張表只能用一個索引(use at most only one index for each referenced table),
- 從 MySQL 5.0開始,引入了 index merge 概念,包括 Index Merge Union Access Algorithm(多個索引并集訪問),包括Index Merge Intersection Access Algorithm(多個索引交集訪問),可以在一個SQL查詢里用到一張表里的多個索引。
- MySQL 在5.6.7之前,使用 index merge 有一個重要的前提條件:沒有 range 可以使用。
索引合并的簡單說明:
1. SELECT * FROM TB WHERE A=5 AND B=6
- 能分別使用索引(A) 和 (B) 或 索引合并;
- 創建組合索引(A,B) 更好;
2. SELECT * FROM TB WHERE A=5 OR B=6
- 能分別使用索引(A) 和 (B) 或 索引合并;
- 組合索引(A,B)不能用于此查詢,分別創建索引(A) 和 (B)會更好;
2.2 索引類型的選擇
B-tree索引
- B-Tree 索引是 MySQL 數據庫中使用最為頻繁的索引類型。
- B-tree索引適用于全鍵值,鍵值范圍或鍵前綴查找。其中鍵前綴查找只適用于根據最左前綴查找。
HASH索引
- hash索引只支持等值比較:=,in(),<=>(<>不同于<=>),也不能用于范圍查找,比如:WHERE price>100;
- Hash 索引無法被用來避免數據的排序操作;
- Hash 索引不能利用部分索引鍵查詢(通過組合索引的前面一個或幾個索引鍵進行查詢的時候,Hash 索引也無法被利用);
- Hash 索引在任何時候都不能避免表掃描;Hash 索引遇到大量Hash值相等的情況后性能并不一定就會比B-Tree索引高。
查詢中的索引原理區別
在 Innodb 中如果通過主鍵來訪問數據效率是非常高的,而如果是通過 Secondary Index 來訪問數據的話, Innodb 首先通過 Secondary Index 的相關信息,通過相應的索引鍵檢索到 Leaf Node之后,需要再通過 Leaf Node 中存放的主鍵值再通過主鍵索引來獲取相應的數據行。
MyISAM 存儲引擎的主鍵索引和非主鍵索引差別很小,只不過是主鍵索引的索引鍵是一個唯一且非空 的鍵而已。而且 MyISAM 存儲引擎的索引和 Innodb 的 Secondary Index 的存儲結構也基本相同,主要的區別只是 MyISAM 存儲引擎在 Leaf Nodes 上面出了存放索引鍵信息之外,再存放能直接定位到 MyISAM 數據文件中相應的數據行的信息(如 Row Number ),但并不會存放主鍵的鍵值信息。
3. 表設計
盡可能的使用NOT NULL
使用緊湊的數據類型
- 對于大多數的數據庫引擎來說,硬盤操作可能是最重大的瓶頸。所以,把你的數據變得緊湊會對這種情況非常有幫助,因為這減少了對硬盤的訪問。
- 如果一個表只會有幾列(比如說字典表,配置表),那么我們不需要使用INT來做主鍵,使用MEDIUMINT,SMALLINT或是更小的TINYINT會更經濟一些。
- 如果你不需要記錄時間,使用DATE要比DATETIME好得多;
- 使用 TIMESTAMP 存儲日期時間。DATETIME和TIMESTAMP都是精確到秒,優先選擇TIMESTAMP,因為TIMESTAMP只有4個字節,而DATETIME8個字節。同時TIMESTAMP具有自動賦值以及自動更新的特性。
- ENUM類型是非??旌途o湊的。在實際上,其保存的是TINYINT,但其外表上顯示為字符串。適用于選項列表,比如“性別”,“國家”,“民族”,“狀態”或“部門”,這些字段取值有限而且固定,則應該使用ENUM而不是VARCHAR。
- IP地址使用UNSIGNED INT。如果你用整形來存放,只需要4個字節,并且你可以有定長的字段。而且,這會為你帶來查詢上的優勢,尤其是當你需要使用這樣的WHERE條件:IP between ip1 and ip2。
存儲精確浮點數必須使用DECIMAL替代FLOAT和DOUBLE。
永遠為每張表設置一個ID
- 我們應該為數據庫里的每張表都設置一個ID做為其主鍵,而且最好的是一個INT型的(推薦使用UNSIGNED),并設置上自動增加的AUTO_INCREMENT標志。
- 就算是你users表有一個主鍵叫“email”的字段,你也別讓它成為主鍵。使用VARCHAR類型來當主鍵會使用得性能下降。另外,在你的程序中,你應該使用表的ID來構造你的數據結構。
- 而且,在MySQL數據引擎下,還有一些操作需要使用主鍵,在這些情況下,主鍵的性能和設置變得非常重要,比如,集群,分區……
- 在這里,只有一個情況是例外,那就是“關聯表”的“外鍵”,也就是說,這個表的主鍵,通過若干個別的表的主鍵構成。我們把這個情況叫做“外鍵”。比如:有一個“學生表”有學生的ID,有一個“課程表”有課程ID,那么,“成績表”就是“關聯表”了,其關聯了學生表和課程表,在成績表中,學生ID和課程ID叫“外鍵”其共同組成主鍵。
選擇合適的存儲引擎
- MyISAM適合于一些需要大量查詢的應用,但其對于有大量寫操作并不是很好。甚至你只是需要update一個字段,整個表都會被鎖起來,而別的進程,就算是讀進程都無法操作直到讀操作完成。另外,MyISAM對于 SELECT COUNT(*) 這類的計算是超快無比的。
- InnoDB是一個非常復雜的存儲引擎,對于一些小的應用,它會比 MyISAM還慢。支持“行鎖” ,于是在寫操作比較多的時候,會更優秀。并且,他還支持更多的高級應用,比如:事務。
禁止在數據庫中使用VARBINARY、BLOB存儲圖片、文件等。
- 單表記錄控制在2000w行
- 控制字段數在20以內
- 單庫數據容量控制在30G,超過需要做分庫處理
少用 blob或者 text,varchar的性能會比text 高很多,實在避免不了blob,請拆表
固定長度的表會更快
- 如果表中的所有字段都是“固定長度”的,整個表會被認為是 “static” 或 “fixed-length”。 例如,表中沒有如下類型的字段: VARCHAR,TEXT,BLOB。只要你包括了其中一個這些字段,那么這個表就不是“固定長度靜態表”了,這樣,MySQL 引擎會用另一種方法來處理。
- 固定長度的表會提高性能,因為MySQL搜尋得會更快一些,因為這些固定的長度是很容易計算下一個數據的偏移量的,所以讀取的自然也會很快。而如果字段不是定長的,那么,每一次要找下一條的話,需要程序找到主鍵。
- 并且,固定長度的表也更容易被緩存和重建。不過,唯一的副作用是,固定長度的字段會浪費一些空間,因為定長的字段無論你用不用,他都是要分配那么多的空間。
- 使用“垂直分割”技術,你可以分割你的表成為兩個一個是定長的,一個則是不定長的。
從PROCEDURE ANALYSE()取得建議
- PROCEDURE ANALYSE()用來分析你的字段和其實際的數據,并會給你一些有用的建議。
- 只有表中有實際的數據,這些建議才會變得有用,因為要做一些大的決定是需要有數據作為基礎的。
SELECT * FROM TABLE_NAME PROCEDURE ANALYSE();
垂直分割
- “垂直分割”是一種把數據庫中的表按列變成幾張表的方法,這樣可以降低表的復雜度和字段的數目,從而達到優化的目的。
- 示例一:在Users表中有一個字段是家庭地址,這個字段是可選字段,相比起,而且你在數據庫操作的時候除了個人信息外,你并不需要經常讀取或是改寫這個字段。那么,為什么不把他放到另外一張表中呢?這樣會讓你的表有更好的性能,因為對于用戶表來說,只有用戶ID,用戶名,口令,用戶角色等會被經常使用。小一點的表總是會有好的性能。
- 示例二:你有一個叫“last_login”的字段,它會在每次用戶登錄時被更新。但是,每次更新時會導致該表的查詢緩存被清空。所以,你可以把這個字段放到另一個表中,這樣就不會影響你對用戶ID,用戶名,用戶角色的不停地讀取了,因為查詢緩存會幫你增加很多性能。
- 另外,你需要注意的是,這些被分出去的字段所形成的表,你不會經常性地去Join他們,不然的話,這樣的性能會比不分割時還要差。
4. SQL語句
避免 SELECT *,消耗cpu、io、內存、帶寬,這種寫法不具有擴展性
當只要一行數據時使用LIMIT 1
為查詢緩存優化你的查詢
- 大多數的MySQL服務器都開啟了查詢緩存。這是提高性能最有效的方法之一,而且這是被MySQL的數據庫引擎處理的。
- 當有很多相同的查詢被執行了多次的時候,這些查詢結果會被放到一個緩存中,這樣,后續的相同的查詢就不用操作表而直接訪問緩存結果了。
- CURDATE(),NOW()和RAND()會讓MySQL的查詢緩存不起作用,因為這些函數的返回是會不定的易變的。用一個變量來代替MySQL的函數,從而開啟緩存。
不要使用ORDER BY RAND()
- 如果你真的想把返回的數據行打亂了,你有N種方法可以達到這個目的。這樣使用只讓你的數據庫的性能呈指數級的下降。這里的問題是:MySQL會不得不去執行RAND()函數(很耗CPU時間),而且這是為了每一行記錄去記行,然后再對其排序。就算是你用了Limit 1也無濟于事(因為要排序)
Prepared Statements
- 可以寫動態參數化的查詢
- 更佳的性能優勢。SQL語句會預編譯在數據庫系統中,執行計劃同樣會被緩存起來,它允許數據庫做參數化查詢。
- 可以防止SQL注入式攻擊
like時避免負向%
避免在數據庫中進行數學運算和其他大量計算任務
使用load data導數據,load data比insert快約20倍。
合理的分頁,尤其大分頁。limit 越大,效率越低。
select id from t limit 10000,10; => select id from t where id > 10000 limit 10;盡量不使用存儲過程、觸發器、函數等
sql語句盡可能簡單、一條 sql只能在一個 cpu運算、大語句拆小語句,減少鎖時間、一條大sql
可以堵死整個庫多用性能分析工具:
- show profile:用來分析當前會話中語句執行的資源消耗情況,可以用于SQL的調優的測量。
- mysqlsla:daniel-nichter用perl寫的一個腳本,專門用于處理分析Mysql的日志而存在。
- mysqldumpslow:mysql自帶的用來分析慢查詢的工具。
- show processlist:顯示哪些線程正在運行。
參考資料
- MySQL開發規范和原則大全
- 建索引時注意字段選擇性 & 范圍查詢注意組合索引的字段順序
總結
以上是生活随笔為你收集整理的MySQL数据库优化技巧的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《测试驱动开发》学习笔记
- 下一篇: MySQL类型介绍以及适用范围