這是個人第 83 篇原創文章java
做者 | 王磊mysql
來源 | Java中文社羣(ID:javacn666)web
年少不知優化苦,遇坑方知優化難。——村口王大爺
sql
全文內容預覽:數據庫
我以前有不少文章都在講性能優化的問題,好比下面這些:數組
-
《switch 的性能提高了 3 倍,我只用了這一招!》 -
《String性能提高10倍的幾個方法!(源碼+原理分析)》 -
《局部變量居然比全局變量快 5 倍?》 -
《池化技術到達有多牛?看了線程和線程池的對比嚇我一跳!》 -
《鏈表居然比數組慢了1000多倍?(動圖+性能評測)》 -
《HashMap 的 7 種遍歷方式與性能分析!》 -
更多性能優化文章
固然,本篇也是關於性能優化的,那性能優化就應該一把梭子嗎?仍是要符合一些規範和原則呢?安全
因此,在開始以前(MySQL 優化),我們先來聊聊性能優化的一些原則。性能優化
性能優化原則和分類
性能優化通常能夠分爲:服務器
-
主動優化 -
被動優化
所謂的主動優化是指不須要外力的推進而自發進行的一種行爲,好比當服務沒有明顯的卡頓、宕機或者硬件指標異常的狀況下,自我出發去優化的行爲,就能夠稱之爲主動優化。微信
而被動優化恰好與主動優化相反,它是指在發現了服務器卡頓、服務異常或者物理指標異常的狀況下,纔去優化的這種行爲。
性能優化原則
不管是主動優化仍是被動優化都要符合如下性能優化的原則:
-
優化不能改變服務運行的邏輯,要保證服務的 正確性; -
優化的過程和結果都要保證服務的 安全性; -
要保證服務的 穩定性,不能爲了追求性能犧牲程序的穩定性。好比不能爲了提升 Redis 的運行速度,而關閉持久化的功能,由於這樣在 Redis 服務器重啓或者掉電以後會丟失存儲的數據。
以上原則看似都是些廢話,但卻給了咱們一個啓發,那就是咱們性能優化手段應該是:預防性能問題爲主+被動優化爲輔。
也就是說,咱們應該以預防性能問題爲主,在開發階段儘量的規避性能問題,而在正常狀況下,應儘可能避免主動優化,以防止未知的風險(除非是爲了 KPI,或者是閒的沒事),尤爲對生產環境而言更是如此,最後纔是考慮被動優化。
PS:當遇到性能緩慢降低、或硬件指標緩慢增長的狀況,現在天內存的佔用率是 50%,明天是 70%,後天是 90% ,而且絲毫沒有收回的跡象時,咱們應該提前發現並處理此類問題(這種狀況也屬於被動優化的一種)。
MySQL 被動性能優化
因此咱們本文會重點介紹 MySQL 被動性能優化的知識,根據被動性能優化的知識,你就能夠獲得預防性能問題發生的一些方法,從而規避 MySQL 的性能問題。
本文咱們會從問題入手,而後考慮這個問題產生的緣由以及相應的優化方案。咱們在實際開發中,一般會遇到如下 3 個問題:
-
單條 SQL 運行慢; -
部分 SQL 運行慢; -
整個 SQL 運行慢。
問題 1:單條 SQL 運行慢
問題分析
形成單條 SQL 運行比較慢的常見緣由有如下兩個:
-
未正常建立或使用索引; -
表中數據量太大。
解決方案 1:建立並正確使用索引
索引是一種能幫助 MySQL 提升查詢效率的主要手段,所以通常狀況下咱們遇到的單條 SQL 性能問題,一般都是因爲未建立或爲正確使用索引而致使的,因此在遇到單條 SQL 運行比較慢的狀況下,你首先要作的就是檢查此表的索引是否正常建立。
若是表的索引已經建立了,接下來就要檢查一下此 SQL 語句是否正常觸發了索引查詢,若是發生如下狀況那麼 MySQL 將不能正常的使用索引:
-
在 where 子句中使用 != 或者 <> 操做符,查詢引用會放棄索引而進行全表掃描; -
不能使用前導模糊查詢,也就是 '%XX' 或 '%XX%',因爲前導模糊不能利用索引的順序,必須一個個去找,看是否知足條件,這樣會致使全索引掃描或者全表掃描; -
若是條件中有 or 即便其中有條件帶索引也不會正常使用索引,要想使用 or 又想讓索引生效,只能將 or 條件中的每一個列都加上索引才能正常使用; -
在 where 子句中對字段進行表達式操做。
所以你要儘可能避免以上狀況,除了正常使用索引以外,咱們也能夠使用如下技巧來優化索引的查詢速度:
-
儘可能使用主鍵查詢,而非其餘索引,由於主鍵查詢不會觸發回表查詢; -
查詢語句儘量簡單,大語句拆小語句,減小鎖時間; -
儘可能使用數字型字段,若只含數值信息的字段儘可能不要設計爲字符型; -
用 exists 替代 in 查詢; -
避免在索引列上使用 is null 和 is not null。
回表查詢:普通索引查詢到主鍵索引後,回到主鍵索引樹搜索的過程,咱們稱爲回表查詢。
解決方案 2:數據拆分
當表中數據量太大時 SQL 的查詢會比較慢,你能夠考慮拆分表,讓每張表的數據量變小,從而提升查詢效率。
1.垂直拆分
指的是將表進行拆分,把一張列比較多的表拆分爲多張表。好比,用戶表中一些字段常常被訪問,將這些字段放在一張表中,另一些不經常使用的字段放在另外一張表中,插入數據時,使用事務確保兩張表的數據一致性。垂直拆分的原則:
-
把不經常使用的字段單獨放在一張表; -
把 text,blob 等大字段拆分出來放在附表中; -
常常組合查詢的列放在一張表中。
2.水平拆分
指的是將數據錶行進行拆分,表的行數超過200萬行時,就會變慢,這時能夠把一張的表的數據拆成多張表來存放。一般狀況下,咱們使用取模的方式來進行表的拆分,好比,一張有 400W 的用戶表 users,爲提升其查詢效率咱們把其分紅 4 張表 users1,users2,users3,users4,而後經過用戶 ID 取模的方法,同時查詢、更新、刪除也是經過取模的方法來操做。
表的其餘優化方案:
-
使用能夠存下數據最小的數據類型; -
使用簡單的數據類型,int 要比 varchar 類型在 MySQL 處理簡單; -
儘可能使用 tinyint、smallint、mediumint 做爲整數類型而非 int; -
儘量使用 not null 定義字段,由於 null 佔用 4 字節空間; -
儘可能少用 text 類型,非用不可時最好考慮分表; -
儘可能使用 timestamp,而非 datetime; -
單表不要有太多字段,建議在 20 個字段之內。
問題 2:部分 SQL 運行慢
問題分析
部分 SQL 運行比較慢,咱們首先要作的就是先定位出這些 SQL,而後再看這些 SQL 是否正確建立並使用索引。也就是說,咱們先要使用慢查詢工具定位出具體的 SQL,而後再使用問題 1 的解決方案處理慢 SQL。
解決方案:慢查詢分析
MySQL 中自帶了慢查詢日誌的功能,開啓它就能夠用來記錄在 MySQL 中響應時間超過閥值的語句,具體指運行時間超過 long_query_time 值的 SQL,則會被記錄到慢查詢日誌中。long_query_time 的默認值爲 10,意思是運行 10S 以上的語句。默認狀況下,MySQL 數據庫並不啓動慢查詢日誌,須要咱們手動來設置這個參數,若是不是調優須要的話,通常不建議啓動該參數,由於開啓慢查詢日誌會給 MySQL 服務器帶來必定的性能影響。慢查詢日誌支持將日誌記錄寫入文件,也支持將日誌記錄寫入數據庫表。使用 mysql> show variables like '%slow_query_log%';
來查詢慢查詢日誌是否開啓,執行效果以下圖所示:slow_query_log 的值爲 OFF 時,表示未開啓慢查詢日誌。
開啓慢查詢日誌
開啓慢查詢日誌,可使用以下 MySQL 命令:
mysql> set global slow_query_log=1
不過這種設置方式,只對當前數據庫生效,若是 MySQL 重啓也會失效,若是要永久生效,就必須修改 MySQL 的配置文件 my.cnf,配置以下:
slow_query_log =1 slow_query_log_file=/tmp/mysql_slow.log
當你開啓慢查詢日誌以後,全部的慢查詢 SQL 都會被記錄在 slow_query_log_file 參數配置的文件內,默認是 /tmp/mysql_slow.log 文件,此時咱們就能夠打開日誌查詢到全部慢 SQL 進行逐個優化。
問題 3:整個 SQL 運行慢
問題分析
當出現整個 SQL 都運行比較慢就說明目前數據庫的承載能力已經到了峯值,所以咱們須要使用一些數據庫的擴展手段來緩解 MySQL 服務器了。
解決方案:讀寫分離
通常狀況下對數據庫而言都是「讀多寫少」,換言之,數據庫的壓力多數是由於大量的讀取數據的操做形成的,咱們能夠採用數據庫集羣的方案,使用一個庫做爲主庫,負責寫入數據;其餘庫爲從庫,負責讀取數據。這樣能夠緩解對數據庫的訪問壓力。
MySQL 常見的讀寫分離方案有如下兩種:
1.應用層解決方案
能夠經過應用層對數據源作路由來實現讀寫分離,好比,使用 SpringMVC + MyBatis,能夠將 SQL 路由交給 Spring,經過 AOP 或者 Annotation 由代碼顯示的控制數據源。優勢:路由策略的擴展性和可控性較強。缺點:須要在 Spring 中添加耦合控制代碼。
2.中間件解決方案
經過 MySQL 的中間件作主從集羣,好比:Mysql Proxy、Amoeba、Atlas 等中間件都能符合需求。優勢:與應用層解耦。缺點:增長一個服務維護的風險點,性能及穩定性待測試,須要支持代碼強制主從和事務。
擴展知識:SQL 語句分析
在 MySQL 中咱們可使用 explain 命令來分析 SQL 的執行狀況,好比:
explain select * from t where id=5;
以下圖所示:
其中:
-
id — 選擇標識符,id 越大優先級越高,越先被執行; -
select_type — 表示查詢的類型; -
table — 輸出結果集的表; -
partitions — 匹配的分區; -
type — 表示表的鏈接類型; -
possible_keys — 表示查詢時,可能使用的索引; -
key — 表示實際使用的索引; -
key_len — 索引字段的長度; -
ref— 列與索引的比較; -
rows — 大概估算的行數; -
filtered — 按表條件過濾的行百分比; -
Extra — 執行狀況的描述和說明。
其中最重要的就是 type 字段,type 值類型以下:
-
all — 掃描全表數據; -
index — 遍歷索引; -
range — 索引範圍查找; -
index_subquery — 在子查詢中使用 ref; -
unique_subquery — 在子查詢中使用 eq_ref; -
ref_or_null — 對 null 進行索引的優化的 ref; -
fulltext — 使用全文索引; -
ref — 使用非惟一索引查找數據; -
eq_ref — 在 join 查詢中使用主鍵或惟一索引關聯; -
const — 將一個主鍵放置到 where 後面做爲條件查詢, MySQL 優化器就能把此次查詢優化轉化爲一個常量,如何轉化以及什麼時候轉化,這個取決於優化器,這個比 eq_ref 效率高一點。
總結
本文咱們介紹了 MySQL 性能優化的原則和分類,MySQL 的性能優化可分爲:主動優化和被動優化,但不管何種優化都要保證服務的正確性、安全性和穩定性。它帶給咱們的啓發是應該採用:預防 + 被動優化的方案來確保 MySQL 服務器的穩定性,而被動優化常見的問題是:
-
單條 SQL 運行慢; -
部分 SQL 運行慢; -
整個 SQL 運行慢。
所以咱們給出了每種被動優化方案的問題分析和解決方案,但願本文能夠幫助到你。
最後的話
原創不易,都看到這了,點個「贊」再走唄,這是對我最大的支持與鼓勵,謝謝你!
往期推薦
阿里《Java開發手冊》最新嵩山版發佈!
池化技術到達有多牛?看了線程和線程池的對比嚇我一跳!
本文分享自微信公衆號 - Java中文社羣(javacn666)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。