35 張圖帶你 MySQL 調優

這是 MySQL 基礎系列的第四篇文章,以前的三篇文章見以下連接html

138 張圖帶你 MySQL 入門mysql

47 張圖帶你 MySQL 進階!!!程序員

炸裂!MySQL 82 張圖帶你飛面試

通常傳統互聯網公司不多接觸到 SQL 優化問題,其緣由是數據量小,大部分廠商的數據庫性能可以知足平常的業務需求,因此不須要進行 SQL 優化,可是隨着應用程序的不斷變大,數據量的激增,數據庫自身的性能跟不上了,此時就須要從 SQL 自身角度來進行優化,這也是咱們這篇文章所討論的。sql

SQL 優化步驟

當面對一個須要優化的 SQL 時,咱們有哪幾種排查思路呢?數據庫

經過 show status 命令 瞭解 SQL 執行次數

首先,咱們可使用 show status 命令查看服務器狀態信息。show status 命令會顯示每一個服務器變量 variable_name 和 value,狀態變量是隻讀的。若是使用 SQL 命令,可使用 like 或者 where 條件來限制結果。like 能夠對變量名作標準模式匹配。服務器

圖我沒有截全,下面還有不少變量,讀者能夠本身嘗試一下。也能夠在操做系統上使用 mysqladmin extended-status 命令來獲取這些消息。微信

可是我執行 mysqladmin extended-status 後,出現這個錯誤。session

應該是我沒有輸入密碼的緣由,使用 mysqladmin -P3306 -uroot -p -h127.0.0.1 -r -i 1 extended-status 後,問題解決。數據結構

這裏須要注意一下 show status 命令中能夠添加統計結果的級別,這個級別有兩個

  • session 級: 默認當前連接的統計結果
  • global 級:自數據庫上次啓動到如今的統計結果

若是不指定統計結果級別的話,默認使用 session 級別。

對於 show status 查詢出來的統計結果,有兩類參數須要注意下,一類是以 Com_ 爲開頭的參數,一類是以 Innodb_ 爲開頭的參數。

下面是 Com_ 爲開頭的參數,參數不少,我一樣沒有截全。

Com_xxx 表示的是每一個 xxx 語句執行的次數,咱們一般關心的是 select 、insert 、update、delete 語句的執行次數,即

  • Com_select:執行 select 操做的次數,一次查詢會使結果 + 1。
  • Com_insert:執行 INSERT 操做的次數,對於批量插入的 INSERT 操做,只累加一次。
  • Com_update:執行 UPDATE 操做的次數。
  • Com_delete:執行 DELETE 操做的次數。

以 Innodb_ 爲開頭的參數主要有

  • Innodb_rows_read:執行 select 查詢返回的行數。
  • Innodb_rows_inserted:執行 INSERT 操做插入的行數。
  • Innodb_rows_updated:執行 UPDATE 操做更新的行數。
  • Innodb_rows_deleted:執行 DELETE 操做刪除的行數。

經過上面這些參數執行結果的統計,咱們可以大體瞭解到當前數據庫是以更新(包括插入、刪除)爲主仍是查詢爲主。

除此以外,還有一些其餘參數用於瞭解數據庫的基本狀況。

  • Connections:查詢 MySQL 數據庫的鏈接次數,這個次數是無論鏈接是否成功都算上。
  • Uptime:服務器的工做時間。
  • Slow_queries:滿查詢次數。
  • Threads_connected:查看當前打開的鏈接的數量。

下面這個博客彙總了幾乎全部 show status 的參數,能夠看成參考手冊。

https://blog.csdn.net/ayay_87...

定位執行效率較低的 SQL

定位執行效率比較慢的 SQL 語句,通常有兩種方式

  • 能夠經過慢查詢日誌來定位哪些執行效率較低的 SQL 語句。

MySQL 中提供了一個慢查詢的日誌記錄功能,能夠把查詢 SQL 語句時間大於多少秒的語句寫入慢查詢日誌,平常維護中能夠經過慢查詢日誌的記錄信息快速準確地判斷問題所在。用 --log-slow-queries 選項啓動時,mysqld 會寫一個包含全部執行時間超過 long_query_time 秒的 SQL 語句的日誌文件,經過查看這個日誌文件定位效率較低的 SQL 。

好比咱們能夠在 my.cnf 中添加以下代碼,而後退出重啓 MySQL。

log-slow-queries = /tmp/mysql-slow.log
long_query_time = 2

一般咱們設置最長的查詢時間是 2 秒,表示查詢時間超過 2 秒就記錄了,一般狀況下 2 秒就夠了,然而對於不少 WEB 應用來講,2 秒時間仍是比較長的。

也能夠經過命令來開啓:

咱們先查詢 MySQL 慢查詢日誌是否開啓

show variables like "%slow%";

啓用慢查詢日誌

set global slow_query_log='ON';

而後再次查詢慢查詢是否開啓

如圖所示,咱們已經開啓了慢查詢日誌。

慢查詢日誌會在查詢結束之後才記錄,因此在應用反應執行效率出現問題的時候慢查詢日誌並不能定位問題,此時應該使用 show processlist 命令查看當前 MySQL 正在進行的線程。包括線程的狀態、是否鎖表等,能夠實時的查看 SQL 執行狀況。一樣,使用mysqladmin processlist語句也能獲得此信息。

下面就來解釋一下各個字段對應的概念

  • Id :Id 就是一個標示,在咱們使用 kill 命令殺死進程的時候頗有用,好比 kill 進程號。
  • User:顯示當前的用戶,若是不是 root,這個命令就只顯示你權限範圍內的 SQL 語句。
  • Host:顯示 IP ,用於追蹤問題
  • Db:顯示這個進程目前鏈接的是哪一個數據庫,爲 null 是尚未 select 數據庫。
  • Command:顯示當前鏈接鎖執行的命令,通常有三種:查詢 query,休眠 sleep,鏈接 connect。
  • Time:這個狀態持續的時間,單位是秒
  • State:顯示當前 SQL 語句的狀態,很是重要,下面會具體解釋。
  • Info:顯示這個 SQL 語句。

State 列很是重要,關於這個列的內容比較多,讀者能夠參考一下這篇文章

https://blog.csdn.net/weixin_...

這裏面涉及線程的狀態、是否鎖表等選項,能夠實時的查看 SQL 的執行狀況,同時對一些鎖表進行優化。

經過 EXPLAIN 命令分析 SQL 的執行計劃

經過以上步驟查詢到效率低的 SQL 語句後,能夠經過 EXPLAIN 或者 DESC 命令獲取 MySQL 如何執行 SELECT 語句的信息,包括在 SELECT 語句執行過程當中表如何鏈接和鏈接的順序。

好比咱們使用下面這條 SQL 語句來分析一下執行計劃

explain select * from test1;

上表中涉及內容以下

  • select_type:表示常見的 SELECT 類型,常見的有 SIMPLE,SIMPLE 表示的是簡單的 SQL 語句,不包括 UNION 或者子查詢操做,好比下面這段就是 SIMPLE 類型。

PRIMARY ,查詢中最外層的 SELECT(如兩表作 UNION 或者存在子查詢的外層的表操做爲 PRIMARY,內層的操做爲 UNION),好比下面這段子查詢。

UNION,在 UNION 操做中,查詢中處於內層的 SELECT(內層的 SELECT 語句與外層的 SELECT 語句沒有依賴關係時)。

SUBQUERY:子查詢中首個SELECT(若是有多個子查詢存在),如咱們上面的查詢語句,子查詢第一個是 sr(sys_role)表,因此它的 select_type 是 SUBQUERY。

  • table ,這個選項表示輸出結果集的表。
  • type,這個選項表示表的鏈接類型,這個選項頗有深刻研究的價值,由於不少 SQL 的調優都是圍繞 type 來說的,可是這篇文章咱們主要圍繞優化方式來展開的,type 這個字段咱們暫時做爲了解,這篇文章不過多深刻。

    type 這個字段會牽扯到鏈接的性能,它的不一樣類型的性能由好到差分別是

    system :表中僅有一條數據時,該表的查詢就像查詢常量表同樣。

    const :當表中只有一條記錄匹配時,好比使用了表主鍵(primary key)或者表惟一索引(unique index)進行查詢。

    eq-ref :表示多表鏈接時使用表主鍵或者表惟一索引,好比

    select A.text, B.text where A.ID = B.ID

這個查詢語句,對於 A 表中的每個 ID 行,B 表中都只能有惟一的 B.Id 來進行匹配時。

ref :這個類型不如上面的 eq-ref 快,由於它表示的是由於對於表 A 中掃描的每一行,表 C 中有幾個可能的行,C.ID 不是惟一的。

ref_or_null :與 ref 相似,只不過這個選項包含對 NULL 的查詢。

index_merge :查詢語句使用了兩個以上的索引,好比常常在有 and 和 or 關鍵字出現的場景,可是在因爲讀取索引過多致使其性能有可能還不如 range(後面說)。

unique_subquery :這個選項常常用在 in 關鍵字後面,子查詢帶有 where 關鍵字的子查詢中,用 sql 來表示就是這樣

value IN (SELECT primary_key FROM single_table WHERE some_expr)

range :索引範圍查詢,常見於使用 =,<>,>,>=,<,<=,IS NULL,<=>,BETWEEN,IN() 或者 like 等運算符的查詢中。

index :索引全表掃描,把索引從頭至尾掃一遍。

all : 這個咱們接觸的最多了,就是全表查詢,select * from xxx ,性能最差。

上面就是 type 內容的大體解釋,關於 type 咱們常常會在 SQL 調優的環節使用 explain 分析其類型,而後改進查詢方式,越靠近 system 其查詢效率越高,越靠近 all 其查詢效率越低。

  • possible_keys :表示查詢時,可能使用的索引。
  • key :表示實際使用的索引。
  • key_len :索引字段的長度。
  • rows :掃描行的數量。
  • filtered :經過查詢條件查詢出來的 SQL 數量佔用總行數的比例。
  • extra :執行狀況的描述。

經過上面的分析,咱們能夠大體肯定 SQL 效率低的緣由,一種很是有效的提高 SQL 查詢效率的方式就是使用索引,接下來我會講解一下如何使用索引提升查詢效率。

索引

索引是數據庫優化中最經常使用也是最重要的手段,經過使用不一樣的索引能夠解決大多數 SQL 性能問題,也是面試常常會問到的優化方式,圍繞着索引,面試官能讓你造出火箭來,因此總結一點就是索引很是很是重!要!不僅是使用,你還要懂其原!理!

索引介紹

索引的目的就是用於快速查找某一列的數據,對相關數據列使用索引可以大大提升查詢操做的性能。不使用索引,MySQL 必須從第一條記錄開始讀完整個表,直到找出相關的行,表越大查詢數據所花費的時間就越多。若是表中查詢的列有索引,MySQL 可以快速到達一個位置去搜索數據文件,而沒必要查看全部數據,那麼將會節省很大一部分時間。

索引分類

先來了解一下索引都有哪些分類。

  • 全局索引(FULLTEXT):全局索引,目前只有 MyISAM 引擎支持全局索引,它的出現是爲了解決針對文本的模糊查詢效率較低的問題,而且只限於 CHAR、VARCHAR 和 TEXT 列。
  • 哈希索引(HASH):哈希索引是 MySQL 中用到的惟一 key-value 鍵值對的數據結構,很適合做爲索引。HASH 索引具備一次定位的好處,不須要像樹那樣逐個節點查找,可是這種查找適合應用於查找單個鍵的狀況,對於範圍查找,HASH 索引的性能就會很低。默認狀況下,MEMORY 存儲引擎使用 HASH 索引,但也支持 BTREE 索引。
  • B-Tree 索引:B 就是 Balance 的意思,BTree 是一種平衡樹,它有不少變種,最多見的就是 B+ Tree,它被 MySQL 普遍使用。
  • R-Tree 索引:R-Tree 在 MySQL 不多使用,僅支持 geometry 數據類型,支持該類型的存儲引擎只有MyISAM、BDb、InnoDb、NDb、Archive幾種,相對於 B-Tree 來講,R-Tree 的優點在於範圍查找。

從邏輯上來對 MySQL 進行分類,主要分爲下面這幾種

  • 普通索引:普通索引是最基礎的索引類型,它沒有任何限制 。建立方式以下

    create index normal_index on cxuan003(id);

刪除方式

drop index normal_index on cxuan003;

  • 惟一索引:惟一索引列的值必須惟一,容許有空值,若是是組合索引,則列值的組合必須惟一,建立方式以下

    create unique index normal_index on cxuan003(id);

  • 主鍵索引:是一種特殊的索引,一個表只能有一個主鍵,不容許有空值。通常是在建表的時候同時建立主鍵索引。

    CREATE TABLE `table` (
             `id` int(11) NOT NULL AUTO_INCREMENT ,
             `title` char(255) NOT NULL ,
             PRIMARY KEY (`id`)
    )

  • 組合索引:指多個字段上建立的索引,只有在查詢條件中使用了建立索引時的第一個字段,索引纔會被使用。使用組合索引時遵循最左前綴原則,下面咱們就會建立組合索引。
  • 全文索引:主要用來查找文本中的關鍵字,而不是直接與索引中的值相比較,目前只有 char、varchar,text 列上能夠建立全文索引,建立表的適合添加全文索引

    CREATE TABLE `table` (
        `id` int(11) NOT NULL AUTO_INCREMENT ,
        `title` char(255) CHARACTER NOT NULL ,
        `content` text CHARACTER NULL ,
        `time` int(10) NULL DEFAULT NULL ,
        PRIMARY KEY (`id`),
        FULLTEXT (content)
    );

固然也能夠直接建立全局索引

CREATE FULLTEXT INDEX index_content ON article(content)

索引使用

索引能夠在建立表的時候進行建立,也能夠單首創建,下面咱們採用單首創建的方式,咱們在 cxuan004 上建立前綴索引

咱們使用 explain 進行分析,能夠看到 cxuan004 使用索引的狀況

若是不想使用索引,能夠刪除索引,索引的刪除語法是

索引使用細則

咱們在 cxuan005 上根據 id 和 hash 建立一個複合索引,以下所示

create index id_hash_index on cxuan005(id,hash);

而後根據 id 進行執行計劃的分析

explain select * from cxuan005 where id = '333';

能夠發現,即便 where 條件中使用的不是複合索引(Id 、hash),索引仍然可以使用,這就是索引的前綴特性。可是若是隻按照 hash 進行查詢的話,索引就不會用到。

explain select * from cxuan005 where hash='8fd1f12575f6b39ee7c6d704eb54b353';

若是 where 條件使用了 like 查詢,而且 % 不在第一個字符,索引纔可能被使用。

對於複合索引來講,只能使用 id 進行 like 查詢,由於 hash 列無論怎麼查詢都不會走索引。

explain select * from cxuan005 where id like '%1';

能夠看到,若是第一個字符是 % ,則沒有使用索引。

explain select * from cxuan005 where id like '1%';

若是使用了 % 號,就會觸發索引。

若是列名是索引的話,那麼對列名進行 NULL 查詢,將會觸發索引。

explain select * from cxuan005 where id is null;

還有一些狀況是存在索引可是 MySQL 並不會使用的狀況。

  • 最簡單的,若是使用索引後比不使用索引的效率還差,那麼 MySQL 就不會使用索引。
  • 若是 SQL 中使用了 OR 條件,OR 前的條件列有索引,然後面的列沒有索引的話,那麼涉及到的索引都不會使用,好比 cxuan005 表中,只有 id 和 hash 字段有索引,而 info 字段沒有索引,那麼咱們使用 or 進行查詢。

    explain select * from cxuan005 where id = 111 and info = 'cxuan';

咱們從 explain 的執行結果能夠看到,雖然 possible_keys 選項上仍然有 id_hash_index 索引,可是從 key、key_len 能夠得知,這條 SQL 語句並未使用索引。

  • 在帶有複合索引的列上查詢不是第一列的數據,也不會使用索引。

    explain select * from cxuan005 where hash = '8fd1f12575f6b39ee7c6d704eb54b353';

  • 若是 where 條件的列參與了計算,那麼也不會使用索引

    explain select * from cxuan005 where id + '111' = '666';

  • 索引列使用函數,同樣也不會使用索引

    explain select * from cxuan005 where concat(id,'111') = '666';

  • 索引列使用了 like ,而且 % 位於第一個字符,則不會使用索引。
  • 在 order by 操做中,排序的列同時也在 where 語句中,將不會使用索引。
  • 當數據類型出現隱式轉換時,好比 varchar 不加單引號可能轉換爲 int 類型時,會使索引無效,觸發全表掃描。好比下面這兩個例子可以顯而易見的說明這一點

  • 在索引列上使用 IS NOT NULL 操做

  • 在索引字段上使用 <>,!=。不等於操做符是永遠不會用到索引的,所以對它的處理只會產生全表掃描。

關於設置索引可是索引沒有生效的場景還有不少,這個須要小夥伴們工做中不斷總結和完善,不過我上面總結的這些索引失效的情景,可以覆蓋大多數索引失效的場景了。

查看索引的使用狀況

在 MySQL 索引的使用過程當中,有一個 Handler_read_key 值,這個值表示了某一行被索引值讀的次數。 Handler_read_key 的值比較低的話,則代表增長索引獲得的性能改善不是很理想,可能索引使用的頻率不高。

還有一個值是 Handler_read_rnd_next,這個值高則意味着查詢運行效率不高,應該創建索引來進行搶救。這個值的含義是在數據文件中讀下一行的請求數。若是正在進行大量的表掃描,Handler_read_rnd_next 的值比較高,就說明表索引不正確或寫入的查詢沒有利用索引。

MySQL 分析表、檢查表和優化表

對於大多數開發者來講,他們更傾向於解決簡單 SQL的優化,而複雜 SQL 的優化交給了公司的 DBA 來作。

下面就從普通程序員的角度和你聊幾個簡單的優化方式。

MySQL 分析表

分析表用於分析和存儲表的關鍵字分佈,分析的結果可使得系統獲得準確的統計信息,使得 SQL 生成正確的執行計劃。若是用於感受實際執行計劃與預期不符,能夠執行分析表來解決問題,分析表語法以下

analyze table cxuan005;

分析結果涉及到的字段屬性以下

Table:表示表的名稱;

Op:表示執行的操做,analyze 表示進行分析操做,check 表示進行檢查查找,optimize 表示進行優化操做;

Msg_type:表示信息類型,其顯示的值一般是狀態、警告、錯誤和信息這四者之一;

Msg_text:顯示信息。

對錶的按期分析能夠改善性能,應該成爲平常工做的一部分。由於經過更新表的索引信息對錶進行分析,可改善數據庫性能。

MySQL 檢查表

數據庫常常可能遇到錯誤,好比數據寫入磁盤時發生錯誤,或是索引沒有同步更新,或是數據庫未關閉 MySQL 就中止了。遇到這些狀況,數據就可能發生錯誤: Incorrect key file for table: ' '. Try to repair it. 此時,咱們可使用 Check Table 語句來檢查表及其對應的索引。

check table cxuan005;

檢查表的主要目的就是檢查一個或者多個表是否有錯誤。Check Table 對 MyISAM 和 InnoDB 表有做用。Check Table 也能夠檢查視圖的錯誤。

MySQL 優化表

MySQL 優化表適用於刪除了大量的表數據,或者對包含 VARCHAR、BLOB 或則 TEXT 命令進行大量修改的狀況。MySQL 優化表能夠將大量的空間碎片進行合併,消除因爲刪除或者更新形成的空間浪費狀況。它的命令以下

optimize table cxuan005;

個人存儲引擎是 InnoDB 引擎,可是從圖能夠知道,InnoDB 不支持使用 optimize 優化,建議使用 recreate + analyze 進行優化。optimize 命令只對 MyISAM 、BDB 表起做用。

我本身肝了六本 PDF,全網傳播超過10w+ ,微信搜索「程序員cxuan」關注公衆號後,在後臺回覆 cxuan ,領取所有 PDF,這些 PDF 以下

六本 PDF 連接