架構設計:系統存儲(9)——MySQL數據庫性能優化(5)

轉自http://blog.csdn.net/yinwenjie/article/details/52757457

===================================
(接上文《架構設計:系統存儲(9)——MySQL數據庫性能優化(5)》)
4-3-3-3、避免死鎖的建議

上一篇文章我們主要介紹了MySQL數據庫中鎖的基本原理、工作過程和產生死鎖的原因。通過上一篇文章的介紹,可以確定我們需要業務系統中儘可能避免死鎖的出現。這裏爲各位讀者介紹一些在InnoDB引擎使用過程中減少死鎖的建議。

*正確使用讀操作語句

經過之前文章介紹,我們知道一般的快照讀是不會給數據表任何鎖的。那麼這些快照讀操作也就不涉及到參與任何鎖等待的情況。那麼對於類似insert…select這樣需要做當前讀操作的語句(但又不是必須進行當前讀的操作),筆者的建議是儘可能避免使用它們,如果非要進行也最好放到數據庫操作的非高峯期進行(例如晚間)。

*基於索引進行寫操作,避免基於表掃描(聚集索引掃描)進行寫操作

基於索引進行寫操作的目的是保證一個寫操作性質的事務中,被鎖住的索引和需要請求的鎖定資源被控制在最小範圍內。而避免使用表鎖的原因是保證一個寫操作性質的事務中,不會額外鎖住完全不需要的索引資源或者搶佔完全不需要的索引資源。表鎖雖然不會直接導致死鎖,但是由於表鎖的工作方式,導致它成爲死鎖原因的機率增大了。

*避免索引失效

使用索引一定要注意索引字段的類型,例如當字段是一個varchar類型,賦值卻是一個int類型,就會導致索引失效。如下所示:

explain select * from myuser where user_name = 1
# user_name 字段的類型是varchar,該字段建立了一個非唯一鍵索引
# 但是以上語句在使用字段進行檢索時,卻使用了一個int作爲條件值。
# 通過MySQL的執行計劃可以看到,InnoDB引擎在執行查詢時並未使用索引,而是走的全表掃描

+----+-------------+-------+------+---------------+-----+------+-------------+
| id | select_type | table | type | possible_keys | key | rows |    Extra    |
+----+-------------+-------+------+---------------+-----+------+-------------+
| 1  |   SIMPLE    | myuser|  ALL |  name_index   |     |  13  | Using where |
+----+-------------+-------+------+---------------+-----+------+-------------+
關鍵業務的delete、update語句應該使用執行計劃進行審覈:從MySQL version 5.6 版本開始,MySQL中的執行計劃功能已經支持對delete、update語句進行執行過程分析了。如果需要執行比較複雜和相關操作或者關鍵業務的寫操作,都應該首先在執行計劃中觀察其運行方式。後文我們馬上開始執行計劃的講解。

5、SQL執行計劃

爲了幫助開發人員根據數據表中現有索引情況,瞭解自己編寫的SQL的執行過程、優化SQL結構,MySQL提供了一套分析功能叫做SQL執行計劃(explain)。下面我們就爲大家介紹一下執行計劃功能的使用。
5-1、執行計劃基本使用
5-1-1、簡單實例

首先我們給出幾個執行計劃的具體案例,這裏使用的數據表還是上一篇文章中展示各種示例所使用的數據表。爲了便於讀者查看,這裏再一次給出數據表的結構:

# 我們所示例的數據表和SQL語句均是工作在InnoDB數據庫引擎下
# myuser數據表一共有4個字段,3個索引。
# user_name字段上創建了非唯一鍵非聚簇索引
# user_number字段上創建了唯一鍵非聚簇索引
# id字段上是聚簇索引
CREATE TABLE `myuser` (
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  `user_name` varchar(255) NOT NULL DEFAULT '',
  `usersex` int(9) NOT NULL DEFAULT '0',
  `user_number` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`Id`),
  UNIQUE KEY `number_index` (`user_number`),
  KEY `name_index` (`user_name`)
)

您可以使用任何一種MySQL數據庫客戶端執行以下執行計劃:

*不使用任何查詢條件
explain select * from myuser;
+----+-------------+-------+------+---------------+-----+---------+-----+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+-----+---------+-----+------+-------+
| 1  |   SIMPLE    | myuser|  ALL |               |     |         |     |  13  |       |
+----+-------------+-------+------+---------------+-----+---------+-----+------+-------+
# 檢索數據表中的所有記錄,由於沒有使用任何檢索條件,所以InnoDB引擎從聚簇索引上掃描出所有的數據行
使用非唯一建索引作爲查詢條件
explain select * from myuser where user_name = '用戶1';

# 省去了表頭,因爲不好排版(可以參考上一個示例的表頭)
......
|1 | SIMPLE | myuser | ref |name_index|name_index | 767 | const | 6 | Using index condition |

# InnoDB引擎首先從非聚簇索引上查找滿足條件的多個索引項,然後在聚簇索引上找到具體的數據
直接使用主鍵作爲查詢條件
explain select * from myuser where id = 1;

# 省去了表頭,因爲不好排版(可以參考上上一個示例的表頭)
......
|  1  |   SIMPLE   | myuser | const | PRIMARY | PRIMARY | 4 | const | 1 |  --這列沒有信息--  |

#使用聚簇索引直接定位數據
使用非索引字段作爲查詢條件
explain select * from myuser where usersex = 1
+----+-------------+-------+------+---------------+-----+---------+-----+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows |    Extra    |
+----+-------------+-------+------+---------------+-----+---------+-----+------+-------------+
| 1  |   SIMPLE    | myuser|  ALL |               |     |         |     |  13  | Using where |
+----+-------------+-------+------+---------------+-----+---------+-----+------+-------------+

# 由於沒有創建索引,所以在聚簇索引上進行全表掃秒,並且過濾出滿足條件的信息

5-1-2、執行計劃結果項

雖然本文還沒有針對以上執行計劃示例的分析結果進行講解,但是爲了讓各位讀者能夠無阻礙的看下去,本文需要首先說明一下執行計劃中的各個結果項的基本含義。在以上的示例中我們使用的MySQL的版本爲MySQL version 5.6,根據不同的數據庫版本,執行計劃的分析結果可能會有一些不同。

# 以下是MySQL 5.6版本的執行計劃的分析結果的表頭
+----+-------------+-------+------+---------------+-----+---------+-----+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+-----+---------+-----+------+-------+

以上表頭的各個字段項目的大致意義如下:

id:每個被獨立執行的操作的標識,表示對象被操作的順序;ID值大,先被執行;如果相同,執行順序一般從上到下。

select_type: 數據庫引擎將SQL拆分成若干部分的子查詢/子操作,每個查詢select子句中的查詢類型(後文詳細講解)。

table: 本次子查詢所查詢的目標數據表。SQL查詢語句即使再複雜,一次子查詢也只可能最多關聯一張數據表。

partitions: 本次查詢所涉及的數據表目標分區信息(如果有分區的話)。後文將對分區的概念進行概要說明。

type: 子查詢類型,非常重要的性能衡量點。這個字段項可能顯示的值包括:“ALL->index->range->ref->eq_ref->const | system->NULL”這些值所表示的查詢性能,從左至右依次增加(注意,按照數據庫基本思想——B+樹,查詢性能可能呈幾何級的變化也可能差異不大)。這些值所代表的查詢動作,在後文中會詳細進行介紹。

possible_keys: 本次子查詢可能使用的索引(前提是,您要建立了索引)。如果查詢所使用的檢索條件可能涉及到多個索引,這裏將會列出這些所有的可能性。

key: 本次子查詢最終被選定的執行索引。有的時候possible_keys可能有值,但keys可能沒有,這就代表InnoDB引擎最終並沒有使用任何索引作爲檢所依據。

key_len: 被選定的索引鍵的長度。

ref: 表示本次子查詢參照的參照條件/參照數據表,參照條件/參照數據表,這個字段的值還可能是一個常量。

rows: 執行根據目前數據表的實際情況預估的,完成這個子查詢需要掃描的數據行數。

Extra:包含不適合在其他列中顯示但十分重要的額外信息。這個字段所呈現的信息在後文也會進行詳細說明。

5-1-3、MySQL數據庫中的分區(partitions)

InnoDB引擎和MYISAM引擎都支持分區功能,只是不同的數據引擎實現細節不一樣。分區功能是指將某一張數據表中的數據和索引按照一定的規則在磁盤上進行存儲。分區功能只限於數據和索引的存儲,是否對數據表進行了分區都不會影響索引在內存中的組織方式,並且分區功能的優勢在數據量較小的情況下,是不怎麼體現出來的。

這裏寫圖片描述

目前主要的分區方式包括:按照某個字段的值範圍進行分區(Range)、按照某一個或者多個字段的Hash Key進行分區(Hash)、按照某個字段的固定值進行分區(List)。並且開發人員還可以同時使用多種分區方式,對數據表進行復合分區。以下是一個分區的示例:

# 爲partitionTable數據表建立四個存儲分區
CREATE TABLE `partitionTable` (
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  `FieldA` varchar(255) NOT NULL DEFAULT '',
  `FieldB` int(9) NOT NULL DEFAULT '0',
  PRIMARY KEY (`Id`)
)
ENGINE=innodb  
PARTITION BY HASH(Id)
PARTITIONS 4;  

接着我們可以到MySQL的基礎庫中觀察到partitionTable數據表的數據和索引計數結構:

# 查詢partitiontable數據表的存儲狀態(庫名爲mysql)
# 爲節約篇幅,省略了不相關的行和列
select * from innodb_table_stats where table_name like 'partitiontable%'
+---------------------+--------+----------------------+--------------------------+
|      table_name     | n_rows | clustered_index_size | sum_of_other_index_sizes |
+---------------------+--------+----------------------+--------------------------+
| partitiontable#p#p0 |   0    |           1          |            0             |
| partitiontable#p#p1 |   0    |           1          |            0             |
| partitiontable#p#p2 |   0    |           1          |            0             |
| partitiontable#p#p3 |   0    |           1          |            0             |
+---------------------+--------+----------------------+--------------------------+

# 從以上結果可以看出MySQL對於這個數據表的存儲狀態按照分區情況進行分別管理。

有一定數據量的情況下(至少應該超過100萬),當數據按照某個字段進行分區存儲,且這個字段(或者幾個字段)並沒有創建索引,那麼查詢操作的性能將會有明顯提高,而且數據表的數據量越大性能提高越明顯;如果這個字段(或者幾個字段)創建了索引,則查詢操作的性能提升並不明顯——因爲檢索還是依靠索引結構。在執行計劃的分析結果中有一個列,名字叫做partitions。該列的信息實際上是說明執行計劃可能涉及的分區情況。
5-2、關鍵性能點

在我們根據SQL的執行計劃進行查詢語句和索引調整時,我們主要需要注意以下這些字段顯示的值,以及它們背後所代表的性能表述。它們是:select_type列、type列、Extra列和key列。
5-2-1、select_type概要說明

一個複雜的SQL查詢語句,在進行執行時會被拆分成若干個子查詢。這些子查詢根據存在的位置、執行先後順序等要素被分解爲不同的操作類型。當然還有的操作可能不涉及到任何實際數據表,例如兩個子查詢間的連接操作過程。在執行計劃分析結果的select_type列,顯示了拆分後這些子查詢的類型,它們是:

SIMPLE(常見):簡單的 SELECT查詢。沒有表UNION查詢,沒有子查詢(嵌套查詢)。我們在本節之前內容中給出的示例基本上屬於這種查詢類型,它基本上不需要也不能再進行子查詢拆分。

PRIMARY(常見):由子查詢(嵌套查詢)的SQL語句下,最外層的Select 作爲primary 查詢。

DERIVED(常見):在from語句塊中的子查詢,屬於衍生查詢。例如以下的查詢中接在“from”後面的子查詢就屬於這種類型的子查詢:

explain select * from (select * from t_interfacemethod_param where name = 'uid') t_interfacemethod_param 

SUBQUERY 和 DEPENDENT SUBQUERY:這兩種類型都表示第一個查詢是子查詢。區別是SUBQUERY表示的子查詢不依賴於外部查詢,而後者的子查詢依賴於外部查詢。

UNCACHEABLE SUBQUERY:子查詢結果不能被緩存, 而且必須重寫(分析)外部查詢的每一行

UNION:從第二個或者在union 之後的select 作爲 union 查詢。這種查詢類型出現在結果集與結果集的UNION操作中。

UNION RESULT:結果集是通過union 而來的。這種查詢類型出現在結果集與結果集的UNION操作中。

DEPENDENT UNION:從第二個或者在union 之後的select 作爲 union 查詢, 依賴於外部查詢。這種查詢類型出現在結果集與結果集的UNION操作中。

UNCACHEABLE UNION:第二個 或者 在UNION 查詢之後的select ,屬於不可緩存的查詢。這種查詢類型出現在結果集與結果集的UNION操作中。

5-2-2、type概要說明

執行計劃的type列中,主要說明了子查詢的執行方式。它的值可能有如下的這些項目(根據MySQL數據庫的執行引擎和版本還會有一些其它選項):

ALL:全表掃描,實際上是掃描數據表的聚簇索引,並在其上加鎖還會視事務隔離情況加GAP間隙鎖。在數據量非常少的情況下,做全表掃描和使用聚簇索引檢索當然不會有太大的性能差異。但是數據量一旦增多情況就完全不一樣了。

index:進行索引進行的掃描,它和ALL一樣都是掃描,不同點是index類型的掃描只掃描索引信息,並不會對聚簇索引上對應的數據值進行任何形式的讀取。例如基於主鍵的函數統計:

# 以下語句還是要進行全表掃描,但是它並不需要讀取任何數據信息。
explain select count(*) from myuser

range:在索引(聚簇索引和非聚簇索引都有可能)的基礎上進行檢索某一個範圍內滿足條件的範圍,而並不是指定的某一個或者某幾個值,例如:

# 以下查詢語句在聚簇索引上檢索一個範圍
explain select * from myuser where id >= 10

ref:在非聚簇索引的基礎上使用“非唯一鍵索引”的方式進行查找。例如:

# 在myuser中已基於user_name字段建立了非聚簇索引,且並非唯一鍵索引
explain select count(*) from myuser where user_name = '用戶1'

const | system:const可以理解爲“固定值”查詢,常見於使用主鍵作爲“簡單查詢”的條件時。system是比較特殊的const,當這個數據表只有一行的情況下,出現system類型。例如以下查詢的操作類型就是const:

# 直接使用主鍵值就可以在索引中進行定位,無論數據量多大,這個定位的性能都不會改變
explain select * from myuser where id = 1

5-2-3、Extra概要說明

執行計劃分析結果中的Extra字段,包含了結果中其他字段中沒有說明但是對性能分析有非常有幫助的信息。甚至有的時候可以但從這個字段分析出某個子查詢是否需要調整、涉及到的索引是否需要調整或者MySQL服務的環境參數配置是否需要進行調整。Extra字段還可以看成是對特定子查詢的總結。

Using index:使用了索引(無論是聚簇索引還是非聚簇索引)並且整個子查詢都只是訪問了索引信息,而沒有訪問真實的數據信息,那麼在Extra字段就會出現這個描述。請看如下示例:

explain select user_name from myuser where user_name = '用戶1';
+--------+-------------------------------------+
| ...... |                Extra                |
+--------+-------------------------------------+
| ...... |     Using where; Using index        |
+--------+-------------------------------------+

# 使用user_name字段進行查詢,原本需要再從聚簇索引中查找數據信息
# 但是InnoDB引擎發現只需要輸出一個字段,且這個字段就是user_name本身,甚至不需要去找全部數據了。

Using where 和 Using index condition:此where關鍵字並不是SQL查詢語句中的where關鍵字(此where非彼where),而是指該子查詢是否需要依據一定的條件對滿足條件的索引(全表掃描也是掃描的聚簇索引)進行過濾。示例如下:

# user_number 是一個非聚簇唯一鍵索引,所以where條件後的user_number只會定位到唯一一條記錄
# 不需要再根據這個條件查詢是否還有其它滿足條件的索引項了
explain select * from myuser where user_number = 77777
+--------+-------------+
| ...... |    Extra    |
+--------+-------------+
| ...... |             |
+--------+-------------+

# user_name 是一個非聚簇非唯一鍵索引,索引where條件後的user_name可能定位到多條記錄
# 這時數據庫引擎就會對這些索引進行檢索,以便定位滿足查詢條件的若干索引項
#(由於B+樹的結構,所以這些索引項是連續的)
explain select * from from myuser where user_name = '用戶1'
+--------+------------------------------+
| ...... |              Extra           |
+--------+------------------------------+
| ...... |   Using index condition      |
+--------+------------------------------+

爲什麼以上示例中顯示的是“Using index condition”而不是“Using where”呢?這是MySQL Version 5.6+ 的新功能特性,Index Condition Pushdown (ICP)。簡單的說就是減少了查詢執行時MySQL服務和下層數據引擎的交互次數,達到提高執行性能的目的。如果您關閉MySQL服務中的ICP功能(這個功能默認打開),以上示例的第二個執行語句就會顯示“Using where”了。

Using temporary:Mysql中的數據引擎需要建立臨時表進行中間結果的記錄,才能完成查詢操作。這個常見於查詢語句中存在GROUP BY 或者 ORDER BY操作的情況。但並不是說主要子查詢中出現了GROUP BY 或者 ORDER BY就會建立臨時表,而如果Group By 或者 Order By所依據的字段(或多個字段)沒有建立索引,則一定會出現“Using temporary”這樣的提示。另一種常見情況發生在子查詢join連接時,連接所依據的一個字段(或多個字段)沒有建立物理外鍵和索引。一旦在Extra字段中出現了“Using temporary”提示,一般來說這條子查詢就需要重點優化。

Using filesort:Mysql服務無法直接使用索引完成排序時,就需要動用一個內存空間甚至需要磁盤交換動作輔助才能完成排序操作。這句話有兩層含義,如果排序所依據的字段(一個或者多個)並沒有創建索引,那麼肯定無法基於索引完成排序;即使排序過程能夠依據正確的索引完成,但是由於涉及到的查詢結果太多,導致用於排序的內存空間不足,所以MySQL服務在進行排序時還會有磁盤交換動作。負責配置某一個客戶(session)可用的內存空間參數項名字爲“sort_buffer_size”。默認的大小爲256KB,如果讀者對查詢結果集有特別要求,可以將該值改爲1MB。一旦在Extra字段中出現了“Using filesort”提示,那麼說明這條子查詢也需要進行優化。

explain select * from myuser order by usersex
+--------+-----------------------+
| ...... |          Extra        |
+--------+-----------------------+
| ...... |   Using filesort      |
+--------+-----------------------+

# 由於usersex並沒有創建索引,所以使用filesort策略進行排序。

注意,在子查詢中爲Group By和Order by操作創建索引時,有時需要聯合where關鍵字使用的查詢字段一起創建複合索引才能起作用。這是因爲子查詢爲了檢索,所首先選擇一個可用的索引項,隨後進行排序時,卻發現無法按照之前的索引進行排序,所以只有走filesort了。例如以下示例:

# user_name字段和user_number字段都獨立創建了索引
explain select * from myuser where user_name = '用戶1' group by user_number
+--------+------------+----------------------------------------------------------+
| ...... |    key     |                             Extra                        |
+--------+------------+----------------------------------------------------------+
| ...... | name_index |  Using index condition; Using where; Using filesort      |
+--------+------------+----------------------------------------------------------+

# 爲了首先完成條件檢索,InnoDB引擎選擇了user_name字段的索引
# 但是排序時發現無法按照之前的索引字段完成,所以選擇走filesort

Using join buffer:使用InnoDB引擎預留的join buffer區域(一個專門用來做表連接的內存區域),這是一個正常現象主要涉及到兩個子查詢通過join關鍵字進行連接的操作。每一個客戶端連接(session)獨立使用的join buffer區域大小可以通過join_buffer_size參數進行設置。這個參數在MySQL 5.6 Version中的默認值爲128KB。如果開發人員經常需要用到join操作,可以適當增加區域大小到1MB或者2MB。

# 以下語句是一個左外連接的操作
# 並且t_interfacemethod.uid和t_interfacemethod_param.interfacemethod之間有外鍵和索引存在
explain select * from  t_interfacemethod_param 
left join  t_interfacemethod on t_interfacemethod.uid = t_interfacemethod_param.interfacemethod

+--------+----------------------------------------------------------+
| ...... |                          Extra                           |
+--------+----------------------------------------------------------+
| ...... |                                                          |
+--------+----------------------------------------------------------+
| ...... |  Using where; Using join buffer (Block Nested Loop)      |
+--------+----------------------------------------------------------+

5-3、執行計劃的侷限性

執行計劃不考慮Query Cache : 執行計劃只考慮單次語句的執行效果,但實際上MySQL服務以及上層業務系統一般都會有一些緩存機制,例如MySQL服務中提供的Query Cache功能。所以實際上可能查詢語句的重複執行速度會快一些。

執行計劃不能分析insert語句:insert語句的執行效果實際上是和其他語句相互作用的,所以執行計劃不能單獨分析insert語句的執行效果。不過update和delete語句都是可以分析的(請使用MySQL Version 5.6+ 版本)。

執行計劃不考慮可能涉及的存儲過程、函數、觸發器帶來的額外性能消耗。

總的來說經過各個MySQL版本對執行計劃功能的優化,現在這個功能得到的分析結果已經非常接近真實執行效果了。但是MySQL執行性能最關鍵的依據還是各位技術人員的數據庫設計能力,起飛吧程序猿!
6、更高效的InnoDB引擎

MariaDB數據庫管理系統是MySQL的一個分支,主要由開源社區在維護,採用GPL授權許可 MariaDB的目的是完全兼容MySQL。Google早已將他們服務器運行的上萬個MySQL服務替換成了MariaDB,從公開的資料看淘寶技術部門也掀起一股使用MariaDB替換MySQL的技術思潮。

這裏寫圖片描述

MariaDB數據庫的產生和發展和甲骨文公司收購MySQL事件有關,它是MySQL之父Widenius先生重新主導開發的完全和MySQL兼容的產品,其下運行的核心引擎還是InnoDB(這個版本的InnoDB引擎,也被稱爲XtraDB )。各位讀者所在的技術團隊也不妨嘗試一下,因爲這兩中數據庫的使用從業務層開發人員來看完全沒有任何區別,DBA的維護手冊甚至都不需要做任何更改。

================================
後記:MySQL數據庫性能優化 部分的文章本來只計劃了三篇,但是這一動筆完全就是一發不可收拾。到這裏先告一段落吧,不然會嚴重影響後續知識內容的寫作。從下篇文章開始,我們將一起討論一下MySQL數據庫的備份和集羣方案。

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