MySql優化那些事

MySql如何工做

Mysql客戶端和服務器之間的通訊協議是「半雙工」的,意味着在任何一個時刻,要麼是由服務器向客戶端發送數據,要麼是有客戶端向服務器發送數據,這兩個動做不能同時發生。當咱們發送一條SQL命令引擎時,MySql執行過程以下圖所示
圖片描述mysql

  1. 客戶端發送一條查詢給服務器。
  2. 服務器先會查詢緩存,若是命中了緩存,則當即返回存儲在緩存中的結果。不然進入下一階段。
  3. 服務務器端進行SQL解析、預處理,再由優化器生成對應的執行計劃。
  4. MySQL根據優化器生成的執行計劃,調用存儲引擎的API來執行查詢。
  5. 將結果返回給客戶端。

查詢緩存
在解析一個查詢語句以前,若是查詢緩存是打開的,那麼MySQL會優先檢查這個查詢是否命中查詢緩存中的數據,若是命中緩存直接從緩存中拿到結果並返回給客戶端。這種狀況下,查詢不會被解析,不用生成執行計劃,不會被執行。sql

語法解析和預處理器
MySQL經過關鍵字將SQL語句進行解析,並生成一棵對應的「解析樹」。MySQL解析器將使用MySQL語法規則驗證和解析查詢。數據庫

查詢優化器
語法書被校驗合法後由優化器轉成查詢計劃,一條語句能夠有不少種執行方式,最後返回相同的結果。優化器的做用就是找到這其中最好的執行計劃。緩存

查詢執行引擎
在解析和優化階段,MySQL將生成查詢對應的執行計劃,MySQL的查詢執行引擎則根據這個執行計劃來完成整個查詢。最常使用的也是比較最多的引擎是MyISAM引擎和InnoDB引擎。mysql5.5開始的默認存儲引擎已經變動爲innodb了。性能優化

下面簡單的羅列幾點差異:MyISAM類型不支持事務處理等高級處理,強調的是性能,InnoDB支持事物;MyISAM支持表級鎖,InnoDB支持行級鎖;MyISAM不支持外鍵,InnoDB支持外鍵(雖然不建議使用外鍵約束)。
查詢當前默認的引擎能夠經過SHOW VARIABLES LIKE '%storage_engine%'語句來查看。服務器

InnoDB事物和鎖

InnoDB是支持事務的,那麼到底什麼是事務呢?
數據庫事務(Database Transaction) ,是指做爲單個邏輯工做單元執行的一系列操做,要麼徹底地執行,要麼徹底地不執行。併發

事物的基本特性(ACID)
原子性(atomicity):
事務中的操做要麼都發生,要麼都不發生。
一致性 (consistent):
在事務開始和完成時,數據必須保持一致狀態(讀一致、寫一致)。
隔離性 (isolation):
隔離性指併發的事務是相互隔離的。一個事物操做不能對另外一個事物產生影響。
持久性(durable):
事務完成以後,它對於數據的修改是永久性的,即便出現系統故障也可以保持。性能

事務的隔離級別
若是不考慮事物的隔離級別會出現如下三種異常狀況測試

  1. 不提交讀(髒讀)
    不提交讀是指在一個事務處理過程當中讀取了另外一個未提交的事務中的數據。
  2. 提交讀(不可重複讀)
    這種狀況是一個事務範圍內屢次查詢卻返回了不一樣的數據值,這是因爲在查詢間隔,被另外一個事物修改並提交了。
  3. 幻讀
    事物T1對一個表中全部的行的某個數據項作了從「1」修改成「2」的操做,這是T2又對這個表中插入了一行數據項,而這個數據項的數值仍是爲「1」而且提交給數據庫。而操做事務T1的用戶若是再查看剛剛修改的數據,會發現還有一行沒有修改,其實這行是從事務T2中添加的,就好像產生幻覺同樣,這就是發生了幻讀。

幻讀和提交讀都是讀取了另外一條已經提交的事物(這一天與不提交讀有區別),所不一樣的是不可重複讀查詢的都是同一個數據項,而幻讀針對的是一批數據總體。優化

Mysql數據庫爲咱們定義了四種隔離級別

  1. Serializable (串行化):它經過強制事物串行執行可避免不提交讀、提交讀、幻讀的發生。
  2. Repeatable read (可重複讀):可避免不提交讀、提交讀的發生。
    該級別在保證了在同一個事物中屢次讀取一樣的記錄結果是一致的。
  3. Read committed (已提交讀):可避免髒讀的發生。
    一個事物開始時,只能看見已提交的事物所作得修改。這個級別會出現已提交讀的現象。
  4. Read uncommitted (未提交讀):最低級別,任何狀況都沒法保證。
    事物中的修改,即便沒有提交,對其餘事物都是可見的,這種隔離級別會出現髒讀。在實際的應用中通常不多使用。

Repeatable read是mysql默認的隔離級別

InnoDB的鎖分類及加鎖方法
1、鎖分類
InnoDB的鎖定模式能夠分爲四類

  1. 共享鎖(S)又叫讀鎖
    對同一行數據能夠共享一把鎖,沒有得到鎖的事務只能夠讀,不能夠修改
  2. 排他鎖(X)又叫寫鎖
    對同一行數據,得到該鎖的事務可讀可寫,未得到鎖的事務不可讀也不可寫.
  3. 意向共享鎖(IS)
    事務打算給數據行加行共享鎖,事務在給一個數據行加共享鎖前必須先取得該表的IS鎖
  4. 意向排他鎖(IX)
    事務打算給數據行加行排他鎖,事務在給一個數據行加排他鎖前必須先取得該表的IX鎖。

按照鎖定級別劃分爲
1.表鎖
開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突的機率最高,併發度最低;適合於以查詢爲主,只有少許按索引條件更新數據的應用。
例如MyISAM引擎在執行查詢語句的時候會自動給涉及的全部表加讀鎖,在執行更新操做(UPDATE、DELETE、INSERT)前,自動給涉及的表加寫鎖。
2.行鎖
開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的機率最低,併發度也最高;適合有大量按索引條件併發更新狀況。
InnoDB的行級鎖定一樣分爲兩種類型,共享鎖和排他鎖,而在鎖定機制的實現過程當中爲了讓行級鎖定和表級鎖定共存,InnoDB也一樣使用了意向鎖(表級鎖定)的概念,也就有了意向共享鎖和意向排他鎖這兩種。
當一個事務須要給本身須要的某個資源加鎖的時候,若是遇到一個共享鎖正鎖定着本身須要的資源的時候,本身能夠再加一個共享鎖,不過不能加排他鎖。可是,若是遇到本身須要鎖定的資源已經被一個排他鎖佔有以後,則只能等待該鎖定釋放資源以後本身才能獲取鎖定資源並添加本身的鎖定。
3.頁鎖
開銷和加鎖時間界於表鎖和行鎖之間,咱們不多場景下會使用。
鎖定級別的選擇是由存儲引擎來自行選擇的。
4.間隙鎖
當咱們用範圍條件而不是相等條件檢索數據,並請求共享或排他鎖時,InnoDB會給符合條件的已有數據記錄的索引項加鎖;對於鍵值在條件範圍內但並不存在的記錄,叫作「間隙(GAP)」,InnoDB也會對這個「間隙」加鎖,這種鎖機制就是所謂的間隙鎖(Next-Key鎖)。
例如:select * from emp where empid > 100 for update;
是一個範圍條件的檢索,InnoDB不只會對符合條件的empid值爲101的記錄加鎖,也會對empid大於101(這些記錄並不存在)的「間隙」加鎖。

二. 加鎖方法
意向鎖是InnoDB引擎自動加的,不須要用戶干預。對於UPDATE、DELETE和INSERT語句,InnoDB會自動給涉及數據集加排他鎖(X);對於UPDATE、DELETE和INSERT語句,InnoDB會自動給涉及數據集加排他鎖(X);對於普通SELECT語句,InnoDB不會加任何鎖;事務能夠經過如下語句顯示給記錄集加共享鎖或排他鎖。
共享鎖:SELECT … LOCK IN SHARE MODE
排他鎖:SELECT … FOR UPDATE
InnoDB行鎖是經過給索引上的索引項加鎖來實現的,只有經過索引條件檢索數據,InnoDB才使用行級鎖,不然,InnoDB將使用表鎖。如下幾種狀況須要避免:

  1. 在不經過檢索條件檢索的時候,InnoDB確實使用的是表鎖,而不是行鎖。
  2. 因爲MySQL的行鎖是針對索引加的鎖,不是針對記錄加的鎖,因此雖然是訪問不一樣行的記錄,可是若是是使用相同的索引鍵,是會出現鎖衝突的。
  3. 當表有多個索引的時候,不一樣的事務可使用不一樣的索引鎖定不一樣的行,另外,不管是使用主鍵索引、惟一索引或普通索引,InnoDB都會使用行鎖來對數據加鎖。
  4. 即使在條件中使用了索引字段,可是否使用索引來檢索數據是由MySQL經過判斷不一樣執行計劃的代價來決定的,若是MySQL認爲全表掃描效率更高,好比對一些很小的表,它就不會使用索引,這種狀況下InnoDB將使用表鎖,而不是行鎖。所以,在分析鎖衝突時,別忘了檢查SQL的執行計劃,以確認是否真正使用了索引。

三.死鎖
Mysql MyISAM老是一次得到所需的所有鎖,要麼所有知足,要麼等待,所以不會出現死鎖。但在InnoDB中,除單個SQL組成的事務外,鎖是逐步得到的,當兩個事務都須要得到對方持有的排他鎖才能繼續完成事務,這種循環鎖等待就是典型的死鎖。
一般來講,死鎖都是應用設計的問題,經過調整業務流程、數據庫對象設計、事務大小,以及訪問數據庫的SQL語句,絕大部分死鎖均可以免。
須要注意:

  1. 不一樣的程序會併發存取多個表,應儘可能約定以相同的順序來訪問表,這樣能夠大大下降產生死鎖的機會。
  2. 在程序以批量方式處理數據的時候,若是事先對數據排序,保證每一個線程按固定的順序來處理記錄,也能夠大大下降出現死鎖的可能。
  3. 在事務中,若是要更新記錄,應該直接申請足夠級別的鎖,即排他鎖,而不該先申請共享鎖,更新時再申請排他鎖,由於當用戶申請排他鎖時,其餘事務可能又已經得到了相同記錄的共享鎖,從而形成鎖衝突,甚至死鎖。

SQL優化技巧

數據類型優化
更短的列一般更好,例如varchar(5)和varhchar(200)存儲hello的空間開銷是同樣的,事實證實更長的列會消耗更多的內存,由於MySQL一般會分配固定大小的內存塊來保存內部值。

使用緩存表和彙總表
以網站爲例,假設須要計算以前24小時內發送的消息數,在一個繁忙的網站不可能維護一個實時精確的計算器,能夠每一小時統計一次,而後把24個小時訪問數疊加。

建立高性能的索引

1.使用獨立的列
若是不是獨立的列,則MySQL就不會使用索引
例如:
Select actor_id from actor where actor_id + 1 = 5;沒法使用actor_id列的索引

2.聯合索引
不少人對多列索引的理解不夠,常見的錯誤是爲每一個列建立獨立的索引,或者按錯誤的順序建立多列索引。當出現服務器對多個索引作相交操做時(一般是多個AND條件),一般意味着須要一個包含全部相關列的多列索引,而不是多個單獨的單列索引。
當服務器須要多個索引作聯合操做時(一般有多個OR條件),一般須要耗費大量的CPU和內存資源。一般有兩種作法:
①使用union all
Select film_id,actor_id from film_actor where actor_id
Union all
Select film_id,actor_id from film_actor where film_id =1 and actor_id <>1;
②分別對film_id 和 actor_id建立索引。

3.選擇合適的索引列順序
Select * from payment where staff_id = 2 AND customer_id = 584
應該建立一個(staff_id,customer_id)索引仍是應該顛倒一下順序?哪一個字段選擇性更高,選擇哪一個字段做爲索引列的第一列。customer_id對應篩選掉的數據更多,則索引customer_id應該放在第一位。

4.冗餘和重複索引
索引並非越多越好,過多的索引會對系統產生負擔,一直未使用的索引應該把它刪掉,而不是留在咱們的系統中。
例若有一張參保人表裏面有100000行數據,每一個state_id值大概有20000條記錄。在state_id列有一個索引對下面查詢有用,查詢名爲Q1:
Select count(*) from table where state_id = 5;
一個簡單的測試查詢的執行速度每秒115次(QPS)。還有一個查詢
Select state_id,city,address from userinfo where state_id = 5;
對於這個查詢,測試結果QPS小於10。提高性能最簡單的辦法就是擴展索引爲(state_id,city,address),索引擴展後Q2變快了,可是Q1變慢了,若是咱們想讓兩個查詢都變得更快,就須要兩個索引。多個索引的缺點是索引的成本更高。向表中插入100萬數據,InnoDB引擎在一個索引狀況下須要80秒,兩個索引的狀況下須要136秒。能夠看出表中的索引越多插入速度越慢。

索引和鎖

咱們在寫查詢語句的時候,要利用索引鎖住最小範圍內的行。
例如:
Select actor_id from sakila.actor where actor_id < 5
And actor_id <> 1 for update
這條語句僅僅返回 2-4,但實際上獲取了1-4之間的行的排他鎖,InnoDB 會鎖住第1行,這是由於MySQL爲該查詢選擇的執行計劃是索引範圍掃描。
就像這個例子顯示的,即便使用了索引,InnoDB也可能鎖住一些不須要的數據,若是不使用索引查找和鎖定行的話問題可能會更糟,mysql會作全表掃描並鎖住全部的行,而無論是否是須要。避免使用多個範圍條件:
例如:select actor_id from actor where actor_id > 45
若是能改爲select actor_id from actor where actor_id in (46,99)
這兩種範圍條件查詢,MySQL沒法再使用範圍列後面的索引了,可是對於「多個等值條件查詢」則沒有這個限制。若是多個範圍查詢則索引就無能爲力了。

查詢性能優化

  1. 避免查詢不須要的記錄,須要多少查多少,建議使用limit
  2. 不要老是取出全部的列,不要輕易使用select * ,要判斷是否是本身須要的列。
  3. 將以此大的工做量拆分紅多個小的工做量
    例如一個大的事物,能夠分拆成幾個小事物執行,這樣也能夠將服務器本來一次性的壓力分散到一個很長的時間段中,能夠大大下降對服務器的影響,還能夠減小。

執行計劃

執行計劃能夠查看數據軟件是如何掃描表,如何使用索引的,所以明白MySql語句的執行計劃,對咱們優化mysql的性能很是有用。
查看執行計劃可使用以下語句:
EXPLAIN SELECT * FROM inventory WHERE item_id = 16102176G; 這樣的命令時,會給咱們反饋以下信息:
圖片描述
Key: 列指出優化器選擇使用的索引
Rows:用於分析結果集中的行數估計值,最好估值是1,通常來講這種狀況發生在當尋找的行在表中能夠經過主鍵或者惟一鍵找到的時候。
Possible_keys:指出優化器爲查詢選定的索引
Key_len: 列定義了用於SQL語句的鏈接條件的鍵的長度。
Table:這個值多是個是代表、表的別名或者一個爲查詢產生臨時表的標識符
Select_type:
提供了表示table列引用的使用方式的類型。最多見的值包括simple、primary,derived和union

  1. simple對於不包含子查詢和其餘複雜語句的簡單查詢
  2. primary 這是爲複雜的查詢而建立的最外層表
  3. derived當一個表不是物理表時叫作derived,派生表一個子查詢
  4. dependent subquery 這個表明是子查詢
  5. union 這是union語句其中的一個SQL元素

extra
extra列提供了有關不一樣種類的MySQL 優化器路徑的一系列
額外信息,下面給 出經常使用值的列表

  1. Using where
    這個值表示查詢使用了where語句來處理結果
  2. Using temporary
    這個值表示使用了內部臨時表
  3. Using index
    這個值重點強調了只須要使用索引就能夠知足查詢表的要求,不須要直接訪問數據表數據。
  4. Distinct 這個值意味着MySql在找到第一個匹配的行以後就會定製搜索其餘行。
相關文章
相關標籤/搜索