MySQL系列-- 5. MySQL高級特性

5. MySQL高級特性

5.1 分區表

  • 對用戶來講,分區表是一個獨立的邏輯表,可是底層由多個物理字表組成。
    • 實現分區的代碼其實是對一組底層表的句柄對象(Handler Object)的封裝。
    • 對分區表的請求,都會經過句柄對象轉換成對存儲對象的接口調用。
    • 因此分區對於SQL層來講是一個徹底封裝底層實現的黑盒子,對應用是透明的,可是對底層的文件系統來看就很容易發現,每個分區表都有一個使用#分隔命名的表文件。
  • MySQL實現分區表的方式——對底層表的封裝——意味着索引也是按照分區的字表定義的,而沒有全局索引。這和Oracle不一樣,在Oracle中能夠更加靈活的定義索引和表是否分區
  • MySQL在建立表時使用PARTITION BY子句定義每一個分區存放的數據。在執行查詢的時候,優化器會根據分區定義過濾那些沒有咱們須要數據的分區,這樣查詢就無須掃描全部分區——只須要查找包含須要數據的分區。
  • 分區的一個主要目的是將數據按照一個較粗的粒度分在不一樣的表中。這樣作能夠將相關的數據存放在一塊兒,另外,若是想一次批量刪除整個分區的數據也會變得很方便。
  • 分區起到很是大做用的場景:
    • 表很是大以致於沒法所有都放在內存中,或者只在表的最後部分有熱點數據,其餘均是歷史數據。
    • 分區表的數據更容易維護。例如,想批量刪除大量數據可使用清除整個分區的方式。另外,還能夠對一個獨立分區進行優化、檢查、修復等操做。
    • 分區表的數據能夠分佈在不一樣的物理設備上,從而高效地利用多個硬件設備。
    • 可使用分區表來避免某些特殊的瓶頸,例如InnoDB的單個索引的互斥訪問、ext3文件系統的inode鎖競爭等。
    • 若是須要,還能夠備份和恢復獨立的分區,這在很是大的數據集的場景下效果很是好。
  • 分區表部分比較重要的限制:
    • 一個表最多隻能有1024個分區
    • 在MySQL5.1中,分區表達式必須是整數,或者是返回整數的表達式。在MySQL5.5中,某些場景中能夠直接使用列來進行分區
    • 若是分區字段中有主鍵或者惟一索引的列,那麼全部主鍵列和惟一索引列都必須包含進來。
    • 分區表中沒法使用外鍵約束。

5.1.1 分區表的原理

  • 如前所述,分區表由多個相關的底層表實現,這些底層表也是由句柄對象(Handler object)標識,因此能夠直接訪問各個分區。
    • 存儲引擎管理分區的各個底層表和管理普通表同樣(全部的底層表都必須使用相同的存儲引擎),分區表的索引只是在各個底層表上各自加上一個徹底相同的索引
    • 從存儲引擎的角度來看,底層表和一個普通表沒有任何不一樣,存儲引擎也無須直到這是一個普通表仍是一個分區表的一部分。
  • 分區表上的操做:
    • SELECT查詢:當查詢一個分區表的時候,分區層先打開並鎖住全部的底層表,優化器先判斷是否能夠過濾部分分區,而後再調用對應的存儲引擎接口訪問各個分區的數據。
    • INSERT操做:當寫入一條記錄時,分區層先打開並鎖住全部的底層表,而後肯定哪一個分區接收這條記錄,再將記錄寫入對應底層表
    • DELETE操做:當刪除一條記錄,分區層先打開並鎖住全部的底層表,而後肯定數據對應的分區,最後對相應底層表進行刪除操做。
    • UPDATE操做:當更新一條記錄時,分區層先打開並鎖住全部的底層表,MySQL先肯定須要更新的記錄在哪一個分區,而後取出數據並更新,在判斷更新後的數據應該放在哪一個分區,最後對底層表進行寫入操做,並對原數據所在的底層表進行刪除操做。
  • 有些操做是支持過濾的。
    • 當刪除一條記錄時,MySQL須要先找到這條記錄,若是WHERE條件剛好和分區表達式匹配,就能夠將全部不包含這條記錄的分區都過濾掉。這對UPDATE語句一樣有效。
    • 若是是INSERT操做,則自己就是隻命中一個分區,其餘分區都會被過濾掉。MySQL先肯定這條操做屬於哪一個分區,再將記錄寫入對應的底層分區表,無須對任何其餘分區進行操做。
  • 雖然每一個操做都會」先打開並鎖住全部的底層表「,但這並非說分區表在處理的過程當中是鎖住全表的。若是存儲引擎可以本身實現行級鎖,例如InnoDB,則會在分區層釋放對應表鎖。這個加鎖和解鎖過程與普通InnoDB上的查詢相似。

5.1.2 分區表的類型

  • MySQL支持多種分區表。最多的是根據範圍進行分區,每一個分區存儲落在某個範圍的記錄,分區表達式能夠是列,也能夠包含列的表達式。
  • PARTITION分區子句中可使用各類函數,可是表達式返回的值必須是一個肯定的整數,且不能是一個常數。
  • MySQL還支持鍵值、哈希和列表分區,這其中有些還支持子分區。在MySQL5.5中,還可使用RANGE COLUMNS類型的分區,這樣即便是基於時間的分區也無須再將其轉換成一個整數。
    • 按時間分區的InnoDB表,系統經過子分區可下降索引的互斥訪問的競爭。最近一年的分區的數據會被很是頻繁地訪問,這會致使大量的互斥量的競爭。使用哈希子分區能夠將數據切成多個小片,大大下降互斥量的競爭問題。
  • 其餘的分區技術:
    • 根據鍵值進行分區,來減小InnoDB的互斥量競爭
    • 使用數學模函數來進行分區,而後將數據輪詢放入不一樣的分區。例如,能夠對日期作模7的運算,或者更簡單地使用返回周幾的函數,若是隻想保留最近幾天的數據,這樣分區很方便
    • 假設表有一個自增的主鍵列id,但願根據時間將最近的熱點數據集中存放。那麼必須將時間戳包含在主鍵當中才行,而這和主鍵自己的意義相矛盾。這種狀況下可使用這樣的分區表達式來實現一樣的目的: HASH(id DIV 1000000),這將爲100萬數據創建一個分區。一方面實現了當初分區的目的,另外一方面比起使用時間範圍分區還避免了一個問題,就是當超過必定閾值時,若是使用時間範圍分區就必須新增分區。

5.1.3 如何使用分區表

  • 假設從一個很是大有10億條記錄的表找出最近幾個月的數據:
    • 由於數據量巨大,確定不能在每次查詢的時候掃描全表
    • 考慮到索引在空間和維護上的消耗,也不但願使用索引。
      • 除非是覆蓋查詢,不然服務器須要根據索引掃描的結果回表。
      • 若是真的使用索引,會發現數據不是按照想要的方式彙集的,並且會有大量的碎片產生,致使一個查詢產生大量的隨機IO
    • 剩下的路:
      • 讓全部查詢都只在數據表上作順序掃描
      • 將數據表和索引所有都緩存在內存裏
      • 使用分區
  • 理解分區時還能夠將其看成索引的最初形態,以代價很是小的方式定位到須要的數據在哪一片區域。在這一片區域中,能夠作順序掃描,能夠建索引,能夠將數據緩存到內存中,等等。由於分區無須額外的數據結構記錄每一個分區有哪些數據——分區不須要精肯定位每條數據的位置,也就無須額外的數據結構——所以代價很是低。
  • 保證大數據量的可擴展性的策略:
    • 全量掃描數據,不要任何索引:
      • 可使用簡單的分區方式存放表,不要任何索引,根據分區的規則大體定位須要的數據位置。只要可以使用WHERE條件,將須要的數據限制在少數分區中,則效率是很高的。固然,也須要作一些簡單的運算保證查詢的響應時間可以知足需求。
      • 使用該策略假設不用將數據徹底放入到內存中,同時還假設須要的數據所有在磁盤上。由於內存相對很小,數據很快會被擠出內存,因此緩存起不了任何做用。
      • 這個策略適用於以正常的方式訪問大量數據的時候。
      • 必須將查詢須要掃描的分區個數限制在一個很小的數量。
    • 索引數據,並分離熱點:
      • 若是數據有明顯的「熱點」,並且除了這部分數據,其餘數據不多被訪問到,那麼能夠將這部分熱點數據單獨放在一個分區中,讓這個分區的數據能有有機會都緩存在內存中。
      • 這樣查詢就能夠只訪問一個很小的分區表,可以使用索引,也可以有效地使用緩存。

5.1.4 什麼狀況下會出問題

上一節介紹的兩個分區策略都基於兩個很重要的假設:查詢可以過濾掉不少額外的分區,分區自己並不會帶來不少額外的代價。node

可能會遇到問題的場景:mysql

  • NULL值會使分區過濾無效算法

    分區的表達式的值能夠是NULL:第一個分區是一個特殊分區。sql

    • 假設按照PARTITION BY RANGE YEAR(order_date)分區,那麼全部order_date爲NULL或者是一個很是值的時候,記錄都會放到第一個分區。
      • 假設有以下查詢:WHERE order_date BETWEEN '2012-01-01' AND '2012-01-31' ,實際上MySQL會檢查兩個分區,由於YEAR()在接收非法值時會返回NULL而把記錄放到第一個分區。
    • 若是第一個分區很是大,特別是當使用"全量掃描數據,不要任何索引"的策略時,代價會很是大。
    • 優化技巧:
      • 建立一個無用的第一個分區,例如:PARTITION p_nulls VALUES LESS THAN (0)。這樣即便須要檢查第一個分區,代價也很是小
      • (最優)MySQL5.5之後不須要第一個優化技巧,由於能夠直接使用列自己而不是基於列的函數進行分區。PARTITION BY RANGE COLUMNS(order_date)
  • 分區列和索引列不匹配:
    • 若是定義的索引列和分區列不匹配,會致使查詢沒法進行分區過濾。
      • 假設在列a上定義了索引,而在列b上進行分區。由於每一個分區都有其獨立的索引,索引掃描列a上的索引就須要掃描每個分區內對應的索引。
    • 應該避免創建和分區列不匹配的索引,除非查詢中還同時包含了能夠過濾分區的條件:
      • 其餘問題:若是在一個關聯查詢中,分區表在關聯順序中是第二個表,而且關聯使用的索引和分區條件不匹配。那麼關聯時針對第一個表符合條件的每個行,都須要訪問並搜索第二個表的全部分區。
  • 選擇分區的成本可能很高
    • 不一樣類型的分區,因爲其實現方式不一樣,因此它們的性能也不一樣
      • 尤爲是範圍分區,對於回答「這一行屬於哪一個分區」這樣的成本可能會很是高,由於服務器須要掃描全部的分區的列表來找到正確的答案。相似這樣的線性搜索的效率不高,隨着分區數的增加,成本會愈來愈高。
      • 其餘的分區類型,如鍵分區和哈希分區,則沒有這樣的問題
    • 對大多數系統來講,100個左右的分區是沒有問題的。
  • 打開鎖並鎖住全部底層表的成本可能很高
    • 當查詢訪問分區表的時候,MySQL須要打開鎖並鎖住全部底層表,這是分區表的另外一個開銷。
      • 這個操做在分區過濾以前發生,所以沒法經過分區過濾下降此開銷,而且該開銷也和分區類型無關,會影響全部的查詢。
    • 對一些自己操做很是快的查詢,好比根據主鍵查找單行,會帶來明顯的額外開銷。
    • 優化技巧:
      • 用批量操做的方式來下降單個操做的此類開銷,例如使用批量插入或者LOAD DATA INFILE、一次刪除多行數據,等等
      • 限制分區的個數
  • 維護分區的成本可能很高
    • 某些分區維護操做的速度會很是快,例如新增或者刪除分區(刪除一個大分區可能會很慢,不過這是另外一回事)
    • 而有些操做,如重組分區或者相似ALTER語句的操做,速度會比較慢,由於須要複製數據。

分區實現中的一些其餘限制:數據庫

  • 全部分區都必須使用相同的存儲引擎
  • 分區函數中可使用的函數和表達式也有一些限制
  • 某些存儲引擎不支持分區
  • 對於MyISAM的分區表,不能再使用LOAD INDEX INTO CACHE操做
  • 對於MyISAM表,使用分區表時須要打開更多的文件描述符。每個分區對於存儲引擎來講都是一個獨立的表,即便分區表只佔用一個表緩存條目,文件描述符仍是須要多個。

5.1.5 查詢優化

  • 訪問分區表,需在WHERE條件中帶入分區列,即便有時候看似多餘,這樣就可讓優化器過濾掉無須訪問的分區。編程

    • MySQL只能在使用分區函數的列的自己進行比較才能過濾分區,而不能根據表達式的值去過濾分區,即便這個表達式就是分區函數也不行。這和查詢中使用獨立的列才能使用索引的道理是同樣的。緩存

      -- 沒法使用分區
      mysql> EXPLAIN PARTITIONS SELECT * FROM sales_by_day WHERE YEAR(day) = 2010\G;
      -- 可以使用分區
      mysql> EXPLAIN PARTITIONS SELECT * FROM sales_by_day
      -> WHERE day BETWEEN '2010-01-01' AND '2010-12-31'\G;複製代碼
  • 優化器在處理查詢的過程當中老是儘量聰明地去過濾分區。例如,若分區表是關聯操做中的第二張表,且關聯條件是分區鍵,MySQL就只會在對應的分區裏匹配行。(EXPLAIN沒法顯示這種狀況下的分區過濾,由於這是運行時的分區過濾,而不是查詢優化階段的)安全

5.1.6 合併表

合併表是一種早期的、簡單的分區實現,和分區表相比有一些不一樣的限制,而且缺少優化。合併表容許用戶單獨訪問各個子表。分區表是將來的發展趨勢,合併表是一種將被淘汰的技術,在將來版本可能會被刪除,在這裏不作過多闡述。服務器

5.2 視圖

  • 視圖自己是一個虛擬表,不存聽任何數據。在使用SQL語句訪問視圖的時候,它返回的數據是MySQL從其餘表生成的。網絡

    • 視圖和表是在同一個命名空間,MySQL在不少地方和表是一樣對待的。不過不能對視圖建立觸發器,也不能使用DROP TABLE命令刪除視圖。
    • MySQL5.0版本以後開始引進。
    • 我的理解:視圖不會對查詢產生任何優化,只是對結果進行一個更好的展現,由於其底層的原理是查詢原有的表。某些狀況下能夠幫助提高性能。
  • 工做原理:

    -- 實現視圖最簡單的辦法是將SELECT語句的結果存放到臨時表中。
    mysql> CREATE VIEW Oceania AS
    -> SELECT * FROM Country WHERE Continent = 'Oceania'
    -> WITH CHECK OPTION;
    -- 當須要訪問視圖的時候,可直接訪問這個臨時表
    mysql> SELECT Code, Name FROM Oceania WHERE Name = 'Australia';
    -- MySQL使用的並算法:重寫含有視圖的查詢,將視圖的定義SQL直接包含進查詢的SQL中:
    mysql> SELECT Code, Name FROM Country
    -> WHERE Continent = 'Oceania' AND Name = 'Australia';
    -- MySQL 使用的臨時表算法,如下SQL是爲展現用的。這樣作會有明顯的性能問題,優化器也很難優化在這個臨時表上的查詢。
    mysql> CREATE TEMPORARY TABLE TMP_Oceania_123 AS
    -> SELECT * FROM Country WHERE Continent = 'Oceania';
    mysql> SELECT Code, Name FROM TMP_Oceania_123 WHERE Name = 'Australia';複製代碼

    MySQL使用合併算法臨時表算法 來處理視圖。若是可能,儘量使用合併算法。

    • MySQL甚至能夠嵌套地定義視圖,也就是在一個視圖上再定義另外一個視圖。
    • 能夠在EXPLAIN EXTENDED以後使用SHOW WARNINGS來查看使用視圖的查詢重寫的結果
      • 若是是採用臨時表算法實現的視圖,EXPLAIN中的select_type會顯示爲派生表(DERIVED)。若是產生的底層派生表很大,那麼執行EXPLAIN可能會很是慢。由於在5.5及以前的版本中,EXPLAIN是須要實際執行併產生派生表的。

    兩種算法的實現細節:

    視圖的兩種實現
    視圖的兩種實現

    使用臨時表算法實現視圖的場景:

    • 視圖中包含GROUP BY、DISTINCT、任何聚合函數、UNION、子查詢等,只要沒法在原表記錄和視圖記錄中創建一一映射的場景。

    視圖的實現算法是視圖自己的屬性,和做用在視圖上的查詢語句無關。例如,能夠爲一個基於簡單查詢的視圖制定使用臨時表算法:CREATE ALGORITHM=TEMPTABLE VIEW v1 AS SELECT * FROM sakila.actor;,這樣不管基於執行什麼樣的查詢,視圖都會生成一個臨時表。

5.2.1 可更新視圖

  • 可更新視圖(updatable view)是指能夠經過更新這個視圖來更新視圖涉及的相關表
    • 只要指定了合適的條件,就能夠更新、刪除甚至向視圖中寫入數據
    • 更新視圖的查詢也能夠是一個關聯語句,可是被更新的列必須來自同一個表中
    • 全部使用臨時表算法實現的視圖都沒法被更新
    • CHECK OPTION子句表示任何經過視圖更新的行,都必須符合視圖自己的WHERE條件定義。因此不能更新視圖之外的列
    • MySQL不支持在視圖上建任何觸發器。某些關係數據庫容許在視圖上簡歷INSTEAD OF觸發器來精確控制在修改視圖數據時作些什麼。

5.2.2 視圖對性能的影響

  • 某些狀況下視圖也能夠幫助提高性能,並且視圖還能夠和其餘提高性能的方式疊加使用。
  • 提高性能的應用場景:
    • 在重構schema的時候,使得在修改視圖底層表結構的時應用代碼還可能繼續不報錯的運行
    • 實現基於列的權限控制,卻不須要真正的系統中建立權限,所以沒有任何額外的開銷
    • 使用僞臨時視圖:
      • MySQL雖然不能建立只在當前連接中存在的真正的臨時視圖,可是能夠建一個特殊名字的視圖,而後在鏈接結束的時候刪除該視圖。這樣在鏈接過程當中就能夠在FROM子句中使用這個視圖,MySQL處理視圖和子查詢的代碼路徑徹底不一樣,因此它們的性能也不一樣
      • 可使用鏈接ID做爲視圖名字的一部分來避免衝突。在應用發生崩潰和別的意外致使未清理臨時視圖的時候,這個技巧使得清理臨時視圖變得更簡單。
    • 使用臨時表算法實現的視圖,在某些時候性能查詢會很糟糕(雖然可能比直接使用等效查詢語句更好一點)
      • MySQL以遞歸的方式執行這類視圖,先會執行外層查詢,即便外層查詢優化器將其優化得很好,可是MySQL優化器可能沒法像其餘的數據庫那樣作更多的內外結合的優化。外層查詢的WHERE條件沒法「下推」到構建視圖的臨時表的查詢中,臨時表也沒法創建索引。
  • 注意視圖背後的複雜性,可能它引用了不少表。若是打算使用視圖來提高性能,須要作比較詳細的測試。即便是合併算法實現的視圖也會有額外開銷,並且視圖性能很難預測。由於在MySQL的優化器中,視圖的代碼執行路徑也徹底不一樣,這部分代碼測試還不夠全面,可能會有一些隱藏缺陷和問題,因此目前的視圖還不是那麼成熟。

5.2.3 視圖的限制

  • MySQL還不支持物化視圖(指視圖結果數據存放在一個能夠查看的表中,並按期從原始表刷新數據到這個表),也不支持在視圖中建立索引。可使用構建緩存表或者彙總表的辦法來模擬物化視圖和索引

  • MySQL並不會保存視圖定義的原始SQL語句,因此不能經過執行SHOW CREATE VIEW後再簡單地修改其結果的方式來從新定義視圖。

    • 若是打算修改視圖,而且無法找到視圖的原始的建立語句的話,能夠經過使用視圖.frm文件最後一行獲取一些信息。若是有FILE權限,甚至可直接使用LOAD_FILE()來讀取.frm中的視圖建立信息,在加上一些字符處理工做。

      mysql> SELECT
      -> REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
      -> REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
      -> SUBSTRING_INDEX(LOAD_FILE('/var/lib/mysql/world/Oceania.frm'),
      -> '\nsource=', −1),
      -> '\\_','\_'), '\\%','\%'), '\\\\','\\'), '\\Z','\Z'), '\\t','\t'),
      -> '\\r','\r'), '\\n','\n'), '\\b','\b'), '\\\"','\"'), '\\\'','\''),
      -> '\\0','\0')
      -> AS source;複製代碼

5.3 外鍵約束

  • InnoDB是目前MySQL中惟一支持外鍵的內置存儲引擎。
  • 使用外鍵是有成本的。
    • 好比外鍵一般都要求每次在修改數據時都要在另一張表中多執行一次查找操做。雖然InnoDB強制外鍵使用索引,但仍是沒法消除這種約束檢查的開銷。若是外鍵列的選擇性很低,則會致使一個很是大且選擇性很低的索引。
  • 某些場景下外鍵會提高一些性能:
    • 若是想確保兩個相關表始終有一致的數據,那麼使用外鍵比在應用程序中檢查一致性的性能要高得多。
    • 外鍵在相關數據的刪除和更新上,也比在應用中維護要更高效,不過,外鍵維護操做是逐行進行的,因此這樣的更新會比批量刪除和更新要慢。
  • 外鍵約束使得查詢須要額外訪問一些別的表,這也意味着須要額外的鎖。
    • 若是向子表中寫入一條記錄,外鍵約束會讓InnoDB檢查對應的父表的記錄,也就須要對父表對應記錄進行加鎖操做,來確保這條記錄不會在這個事務完成之時就被刪除了。這會致使額外的鎖等待,甚至會致使一些死鎖。由於沒有直接訪問這些表,因此這類死鎖問題每每很難排除。
  • 有時,可使用觸發器來代替外鍵,對於相關數據的同時更新外鍵更合適,可是若是外鍵只是用做數值約束,那麼觸發器或者顯式地限制取值會更好些(這裏,能夠直接使用ENUM類型)
  • 若是隻是用外鍵作約束,那一般在應用程序裏實現該約束會更好。外鍵會帶來很大的額外消耗。

5.4 在MySQL內部存儲代碼(暫時無用,簡單介紹)

在將來一段時間還不會用到,須要用到再看,感受更適合DBA,這裏只列舉經常使用的方式。

5.4.1 存儲過程和函數

5.4.2 觸發器

能夠在執行INSERT、UPDATE或者DELETE的時候,執行一些特定的操做。能夠在MySQL中指定是在SQL語句執行前觸發仍是在執行後觸發。

5.4.3 事件

相似於LINUX的定時任務,不過徹底是在MySQL內部實現。

5.4.4 在存儲過程當中保留註釋

5.5 遊標(暫時無用,簡單介紹)

MySQL在服務器中提供只讀的、單向的遊標,並且只能在存儲過程或者更底層的客戶端API中使用。由於遊標中指向的對象都是存儲在臨時表中而不是實際查詢到的數據,因此MySQL遊標老是可讀的。

5.6 綁定變量

  • 綁定變量的SQL語句:INSERT INTO tbl(col1, col2, col3) VALUES (?, ?, ?);。綁定變量的SQL,使用問號標記能夠接收參數的位置,當真正須要執行具體查詢的時候,則使用具體值代替這些問號。
  • 當建立一個綁定變量SQL時,客戶端(如C或JAVA等)向服務器發送了一個SQL語句的原型。服務器端收到這個SQL語句的框架後,解析並存儲這個SQL語句的部分執行計劃,返回給客戶端一個SQL語句處理句柄。之後每次執行這類查詢,客戶端都制定使用這個句柄。
  • MySQL在使用綁定變量的時候能夠更高效地執行大量重複語句的緣由:
    • 在服務器端只須要解析一次SQL語句。
    • 在服務器端某些優化器的工做只須要執行一次,由於它會緩存一部分的執行計劃。
    • 以二進制的方式只發送參數和句柄,比起每次發送ASCII碼文本效率更高。不過最大的節省仍是來自於BLOB和TEXT字段,綁定變量的形式能夠分塊傳輸,而無須一次性傳輸。二進制協議在客戶端也能夠節省不少內春,減小了網絡開銷,還節省了將數據從存儲原始格式轉換成文本格式的開銷。
    • 僅僅是參數而不是整個查詢語句須要發送到服務器端,因此網絡開銷會更小。
    • MySQL在存儲參數的時候,直接將其存放到緩存中,再也不須要在內存中屢次複製。
  • 綁定變量相對也更加安全。無須在應用程序中處理轉義,一則更簡單明瞭,二則也大大減小了SQL注入和攻擊的風險。

5.6.1 綁定變量的優化

理論上有些優化器只須要作一次,但實際上,下面的操做仍是都會被執行。根據優化器何時工做,能夠將優化分爲三類:

  • 在準備階段:服務器解析SQL語句,移除不可能的條件,並重寫子查詢
  • 在第一次執行的時候:若是可能的話,服務器先簡化嵌套循環的關聯,並將外關聯轉換成內關聯
  • 在每次SQL語句執行時,服務器作以下事情:
    • 過濾分區
    • 若是可能的話,儘可能移除COUNT()、MIN()和MAX()
    • 移除常數表達式
    • 檢測常量表
    • 作必要的等值傳播
    • 分析和優化ref、range和索引優化等訪問數據的方法。
    • 優化關聯順序。

5.6.2 SQL接口的綁定變量(暫時無用,簡單介紹)

最主要的用途就是在存儲過程當中使用。

5.6.3 綁定變量的限制

  • 綁定變量是會話級別的,因此鏈接之間不能共用綁定變量句柄。一樣地,一旦鏈接斷開,則原來的句柄也不能再使用。(鏈接池和持久化鏈接能夠在必定程度上緩解這個問題)
  • MySQL5.1以前,綁定變量的SQL是不能使用查詢緩存的。
  • 並非全部的時候使用綁定變量都能得到更好的性能。若是隻是執行一次SQL,那麼使用綁定變量的方式五一比直接執行多了一次額外的準備消耗階段,並且還須要一次額外的網絡開銷。(要正確的使用綁定變量,還須要在使用完成以後,釋放相關的資源)
  • 當前版本下,還不能在存儲函數中使用綁定變量,可是在存儲過程當中可使用
  • 若是老是忘記釋放綁定變量資源,則在服務器端很容易發發生資源泄漏。綁定變量SQL老是的限制是一個全侷限制,因此某一個其餘的錯誤可能會對全部其它的線程都產生影響。
  • 有些操做,如BEGIN,沒法在綁定變量中完成。

三種綁定變量類型的部分區別:

  • 客戶端模擬的綁定變量:客戶端的驅動程序接收到一個帶參數的SQL,再將指定的值帶入其中,最後將完整的查詢發送到服務器端。
  • 服務器端的綁定變量:客戶端使用特殊的二進制協議將帶參數的字符串發送到服務器端,而後使用二進制協議將具體的參數值發送給服務器端執行。
  • SQL接口的綁定變量:客戶端先發送一個帶參數的字符串到服務器端,這相似與使用PREPARE的SQL語句,而後發送設置參數的SQL,最後使用EXECUTE來執行SQL。全部這些都是用普通的文本傳輸協議。

5.7 用戶自定義函數(暫時無用,簡單介紹)

使用支持C語言調用約定的任何編程語言來實現用戶自定義函數(UDF)。UDF必須事先編譯後並動態連接到服務器上。

5.8 插件(暫時無用,簡單介紹)

插件類型:

  • 存儲過程插件
  • 後臺插件
  • INFORMATION_SCHEMA插件
  • 全文解析插件
  • 審計插件
  • 認證插件

5.9 字符集和校對

字符集是指一種從二進制編碼到某類字符符號的映射,能夠參考如何使用一個字節來表示英文字符。校對是指一組用於某個字符集的排序規則。

5.9.1 MySQL如何使用字符集

每種字符集均可能有多種校對規則,而且都有一個默認的校對規則,每一個校對規則都是針對某個特定的字符集,所以把字符集和校對規則統稱爲字符集。

MySQL有不少選擇用於控制字符集,這些選項和字符集很容易混淆。只有基於字符的值才真正的有字符集的概念。對於其餘類型的值,字符集只是一個設置,指定用哪一種字符集來作比較或者其餘操做。

MySQL的設置:

  • 建立對象時的默認設置:

    • 建立數據庫的時候,將根據服務器上character_set_server設置來設定該數據庫的默認字符集
    • 建立表的時候,將根據數據庫的字符集設置指定這個表的字符集設置
    • 建立列的時候,將根據表的設置指定列的字符集設置。
    • 以上三個階層,每一層都只是指定一個默認值,當這一層沒有指定字符集的時候,默認值纔會生效。
  • 服務器和客戶端通訊時的設置:

    • 服務器和客戶端通訊的時候,他們可能使用不一樣的字符集。這時,服務器端將進行必要的翻譯轉換工做:

      • 服務器端老是假設客戶端是按照character_set_client設置的字符來傳輸數據和SQL語句的。
      • 當服務器收到客戶端的SQL語句時,它先將其轉換成字符集character_set_connection。它仍是用這個設置來決定如何將數據轉換成字符串。
      • 當服務器端返回數據或者錯誤信息給客戶端時,它會將其轉換成character_set_result。

      客戶端和服務器的字符集
      客戶端和服務器的字符集

    • 根據須要,可使用SET NAMES或者SET CHARACTER語句來改變上面的設置。不過在服務器上使用這個命令只能改變服務器端的設置。客戶端程序和客戶端的API也須要使用正確的字符集才能避免在通訊時出現問題。

MySQL比較兩個字符串的大小時,經過將其轉換成同一個字符集再進行比較,若是兩個字符集不兼容的話,則會拋出錯誤。MySQL還會爲每一個字符串設置一個「可轉換性」,這個設置決定了值的字符集的優先級,於是會印象MySQL作字符集隱式轉換後的值。

  • 還可使用前綴和COLLATE子句來指定字符串的字符集或者校對字符集。

    mysql> SELECT _utf8 'hello world' COLLATE utf8_bin;複製代碼

一些特殊狀況:

  • 詭異的character_set_database設置:當改變默認數據庫的時候,這個變量也會跟着改變。
  • LOAD DATA INFILE:數據庫老是將文件中的字符按照字符集character_set_database來解析
  • SELECT INTO OUTFILE:將結果不作任何轉碼地寫入文件
  • 嵌入式轉義序列:MySQL會根據character_set_client的設置來解析轉義序列,即便字符串中包含前綴或者COLLATE子句也同樣。由於對解析器來講,前綴並非一個指令,只是一個關鍵字。

5.9.2 選擇字符集和校對規則

  • 可使用命令SHOW CHARACTERSET和SHOW COLLATION來查看MYSQL支持的字符集和校對規則。

  • 極簡原則:最好先爲服務器或者數據庫選擇一個合理的字符集,而後根據不一樣的實際狀況,讓某些列選擇合適的字符集。

  • 對於校對規則一般須要考慮的一個問題是,是否以大小寫敏感的方式比較字符串,或者是以字符串編碼的二進制值來比較大小。二進制校對規則直接使用字符的字節進行比較,而大小寫敏感的校對規則在多字節字符集時如德語有更復雜的比較規則。

  • MySQL如何選擇字符集和校對規則:

    MySQL如何選擇字符集和校對規則
    MySQL如何選擇字符集和校對規則

5.9.3 字符集和校對規則如何影響查詢

某些字符集和校對規則可能會須要更多的CPU操做、消耗更多的內存和存儲空間,甚至還會影響索引的正常使用。

  • 不一樣的字符集和校對規則之間的轉換可能會帶來額外的系統開銷。
    • 只有排序查詢要求的字符集與服務器數據的字符集相同的時候,才能使用索引來排序。索引根據數據列的校對規則進行排序。
  • MySQL會在須要的時候進行字符集轉換:
    • 當時用兩個字符集不一樣的列來關聯兩個表的時候,MySQL會嘗試轉換其中一個列的字符集。
  • UTF-8是一種多字節編碼,它存儲一個字符會使用變成的字節數。在MySQL內部,一般使用一個定長的時間來存儲字符串,在進行相關操做,這樣的目的是但願老是保證緩存中有足夠的空間來存儲字符串。
    • 在多字節字符集中,一個字符再也不是一個字節。能夠用LENGTH()和CHAR_LENGTH()來計算字符串的長度。在多字節字符集中,二者返回的結果會不一樣,所以要使用後者
    • 若是要索引一個UTF-8字符集的索引,MySQL會假設每個字符都是三個字節,索引最長索引前綴的限制一下縮短到原來的三分之一。對MySQL使用索引有一些影響,好比沒法使用索引覆蓋掃描。
    • 若是所有直接使用UTF-8字符集,從性能角度來並很差,只會消耗更多的存儲空間,由於不少應用無須使用該字符集。
  • 考慮字符集須要根據存儲的具體內存來決定:
    • 存儲的內容主要是英文字符,可使用UTF-8,由於其只佔用一個字節
    • 存儲一些非拉丁語系的字符,可使用cpl256
    • 存儲別的語言,使用UTF-8。
    • 當從某個具體的語種編碼轉成UTF-8時,存儲空間的使用會相對增長。若是使用的是InnoDB表,那麼字符集的改變可能會致使數據的大小超過能夠在頁內存儲的臨界值,須要保存在額外的外部存儲區,這會致使嚴重的空間浪費和空間碎片。
  • 有時候根本不須要使用任何的字符集。一般只有在作大小寫無關的比較、排序、字符串操做的時候才須要使用字符集。若是數據庫不關心字符集,那麼能夠直接將全部東西存儲到二進制列中,包括UTF-8編碼數據。這麼作可能還須要一個列記錄字符的編碼集,致使不少難以排除的錯誤。所以若是可能建議儘可能不要這麼作。

5.10 全文索引(暫時無用,簡單介紹)

  • 全文索引有着本身獨特的語法,沒有索引也能夠工做,若是有索引效率會更高。
  • 全文索引能夠支持各類字符內容的搜索,也支持天然語言搜索和布爾搜索。
  • 只有在MyISAM引擎支持,5.6版本後的InnoDB也已經實驗性質的支持
    • MyISAM的全文索引是一種特殊的B-Tree索引,共有兩層。第一層是全部關鍵字,而後對於每個關鍵字的第二層,包含的是一組相關的「文檔指針」

5.10.1 天然語言的全文索引

計算每個文檔對象和查詢的相關度。相關度是基於匹配的關鍵詞個數,以及關鍵詞在文檔中出現的個數。在整個索引中出現次數越少的詞語,匹配的相關度就越高,相反很是常見的單詞就不會被搜索。

5.10.2 布爾全文索引

能夠在查詢中自定以某個被搜索詞語的相關性。布爾搜索經過停用詞列表過濾掉那些噪聲詞,另外還要求搜索的關鍵詞長度必須大於ft_min_word_len並小於ft_max_word_len。搜索返回的結果是未經排序的。

5.10.3 MySQL5.1中全文索引的變化

5.10.4 全文索引的限制和替代方案

  • 限制:
    • 只有一種影響相關性的方法:詞頻
    • 數據量大小
    • 還會影響優化器的工做。索引選擇、WHER子句、ORDER BY都有可能不是按照預計的方式工做。

5.10.5 全文索引的配置和優化

  • 按期地進行全文索引重建等平常維護可提高性能
  • 保證索引緩存足夠大,從而保證全部的全文索引都能緩存在內存中
  • 提供一個好的停用詞列表
  • 忽略一些過短的單詞能夠提高全文索引的效率
  • 停用詞表和容許最小詞長均可以減小索引詞語來提高全文索引效率,但同時會下降搜索的精確度。
  • 當向一個全文索引的表中導入大量數據的時候,最後先DISABLE KEYS來禁用全文索引,而後在導入數據後再ENABLE KEYS來創建全文索引。
  • 若是數據集很是大,則須要對數據進行手動分區,而後將數據分佈到不一樣的節點,再作並行的搜索。

5.11 分佈式(XA)事務

存儲引擎的事務特性能勾保證在存儲引擎級別實現ACID,而分佈式事務則讓存儲引擎級別的ACID擴展到數據庫層面,甚至擴展到多個數據庫之間,這須要兩個階段提交實現:

  • 第一階段:XA事務中須要一個事務協調器來保證全部的事務參與者都完成了準備工做。
  • 第二階段:若是協調器收到全部的參與者都準備好的消息,就會告訴全部的事務能夠提交了。
  • MySQL在這個XA事務過程當中扮演一個參與者的角色,而不是協調者

5.11.1 內部XA事務

  • 做用:協調內部存儲引擎和二進制日誌
  • MySQL中各個存儲引擎是徹底獨立的,彼此不知道對方的存在,因此一個跨存儲引擎的事務就須要一個外部的協調者。若是不使用XA協議,例如,跨存儲引擎的事務提交就只是順序地要求每一個存儲引擎格子提交,若是在某個存儲提交過程當中發生系統崩潰,就會破壞事務的特性。
  • 若是將MySQL記錄的二進制日誌操做看做是一個獨立的存儲引擎,在存儲引擎提交的同時,須要將提交的信息寫入二進制文件,這就是一個分佈式事務,只不過二進制日誌的參與者是MySQL自己。
  • XA事務爲MySQL帶來巨大的性能降低。從MySQL5.0開始,它破壞了MySQL內部的「批量提交」(一種經過單磁盤IO操做完成多個事務提交的技術),使得MySQL不得不進行屢次額外的fsync()調用。

5.11.2 外部XA事務

  • 做用:MySQL能夠參與到外部的分佈式事務中
  • MySQL可以做爲參與者完成一個外部的分佈式事務。但它對XA協議支持並不完整,例如,XA協議要求在一個事務中的多個鏈接能夠作關聯,但目前版本的MySQL還不能支持。
  • 由於通訊延遲和事務參與者自己可能失敗,因此外部XA事務比內部消耗會更大。
    • 若是在廣域網中使用XA事務,一般會由於不可預測的網絡性能致使事務失敗。
    • 若是有太多不可控因素,例如,不穩定的網絡通訊或者用戶長時間地等待而不提交,則最好避免使用XA事務。任何可能讓事務提交發生延遲的操做代價都很大,由於它影響的不只是本身自己,還會讓全部參與者都在等待。
  • 還可使用別的方式實現高性能的分佈式事務。例如,能夠在本地寫入數據,並將其放入隊列,而後在一個更小、更快的事務中自動分發。還可使用MySQL自己的複製機制來發送數據。不少應用程序均可以徹底避免使用分佈式事務。
  • XA事務是一種在多個服務器之間同步數據的辦法。若是因爲某些緣由不能使用MySQL自己的複製,或者性能不是瓶頸的時候,能夠嘗試使用。

5.12 查詢緩存

MySQL的緩存類型:

  • 某些場景下實現緩存查詢的執行計劃,對於相同類型的SQL就能夠跳過SQL解析和執行計劃生成階段
  • 緩存完整的SELECT查詢結果,也就是「查詢緩存」

MySQL查詢緩存保存查詢返回的完整結果。當查詢命中該緩存,MySQL會馬上返回結果,跳過了解析、優化和執行階段。

  • 查詢緩存系統會跟蹤查詢中涉及的每一個表,若是這些表發生變化,那麼和這個表相關的全部的查詢緩存數據都將失效。這種機制效率看起來很低,可是實現代價很小,而這點對於一個很是繁忙的系統來講很是重要。
  • 查詢緩存對應用是徹底透明的。應用程序無須關心MySQL是經過查詢緩存返回仍是實際執行返回的結果。

隨着如今的通用服務器愈來愈大,查詢緩存被發現是一個影響服務器擴展性的因素。它可能成爲整個服務器的資源競爭單點,在多核服務器上還可能致使服務器僵死。建議默認關閉查詢緩存,若是查詢緩存做用很大的話,那就配置一個很小的查詢緩存空間(如幾十兆)。

5.12.1 MySQL如何命中查詢緩存

  • 緩存存放在一個引用表中,經過一個哈希值引用,這個哈希值包括了以下因素:即查詢自己、當前要查詢的數據庫、客戶端協議的版本等一些其餘可能會影響返回結果的信息。

    • 當判斷緩存是否命中時,MySQL不會解析、「正規化」或者參數化查詢語句,而是直接使用SQL語句和客戶端發送過來的其餘原始信息。任何字符上的不一樣,例如空格、註釋,都會致使緩存不命中。

    • 當查詢語句中有一些不肯定的數據時,則不會被緩存。例如包含函數NOW()或者CURRENT_DATE的查詢都不會被緩存。

      • 包含任何用戶自定義函數、存儲函數、用戶變量、臨時表、MySQL庫中的系統表,或者任何包含級別權限的表,都不會被緩存。
      • 在檢查查詢緩存以前,MySQL經過一個大小寫不敏感的檢查看看SQL語句是否以SEL開頭。
      • 而檢查查詢緩存的時候,MySQL還不會解析SQL語句,因此MySQL並不知道查詢語句中是否包含有返回不肯定數據的函數。可是MySQL在任什麼時候候只要發現不能被緩存的部分,就會禁止這個查詢被緩存。
      -- 若是但願換成一個帶日期的查詢,那麼最好將其日期提早計算好,而不要直接使用函數
      ... DATE_SUB(CURRENT_DATE, INTERVAL 1 DAY) -- Not cacheable!
      ... DATE_SUB('2007-07-14’, INTERVAL 1 DAY) -- Cacheable複製代碼
    • 子查詢和存儲過程都沒辦法使用查詢緩存,另外5.1版本以前,綁定變量也沒法使用。由於查詢緩存是在完整的SELECT語句基礎上的,並且只是在剛收到SQL語句的時候才檢查。

    • 查詢緩存在不少時候能夠提高查詢性能,但自己是一個加鎖排他操做,另外打開查詢緩存對讀和寫都會帶來額外的消耗:

      • 讀查詢在開始以前必須先檢查是否命中緩存
      • 若是這個讀查詢能夠被緩存,那麼當完成執行後,MySQL若發現查詢緩存中沒有這個查詢,會將其結果存入查詢緩存,這會帶來額外的系統消耗。
      • 這對寫操做也會有影響,由於當向某個表寫入數據的時候,MySQL必須將對應表的全部緩存都設置失效。若是查詢緩存很是大或者碎片不少,這個操做就可能帶來很大系統消耗(設置了不少的內存給查詢緩存用的時候)
    • 對InnoDB來講,事務的一些特性會限制查詢緩存的做用。當一個語句在事務中修改了某個表,MySQL會將這個表的對應的查詢緩存都設置失效,而事實上,InnoDB的多版本特性會暫時將這個修改對其它事務屏蔽。

      • 在這個事務提交以前,這個表的相關查詢是沒法被緩存的,因此全部在這個表上面的查詢,內部或外部的事務,都只能在該事務提交後才被緩存。所以,長時間運行的事務,會大大下降查詢緩存的命中率。
    • 若是查詢緩存使用了很大量的內存,緩存失效操做就可能會成爲一個很是嚴重的問題瓶頸。

      • 若是緩存中存放了大量的查詢結果,那麼緩存失效操做時整個系統均可能會僵死一會。由於這個操做是靠一個全局鎖操做保護的,全部須要作該操做的查詢都要等待這個鎖,並且不管是檢測是否命中緩存,仍是緩存失效檢測都須要等待這個全局鎖。

5.12.2 查詢緩存如何使用內存

  • 查詢緩存是徹底存儲在內存中。

    • 除了查詢結果,須要緩存的還有不少別的維護相關的數據。這些基本的管理維護數據結構大概須要40KB的內存資源。
    • 用於查詢緩存的內存被分紅一個個的數據塊,數據塊是變長的。
      • 每個數據塊中,存儲了本身的類型、大小和存儲的數據自己,還外加指向前一個和後一個數據塊的指針。數據塊的類型有:存儲查詢結果、存儲查詢和數據表的映射、存儲查詢文本等等。
  • 理想流程:

    • 當服務器啓動的時候,它先初始化查詢緩存須要的內存。這個內存池初始是一個完整的空閒塊,大小就是所配置的查詢緩存再減去用於維護元數據的數據結構鎖消耗的空間。
      • 經過函數malloc()向操做系統申請內存,在整個流程只在初次建立查詢緩存的時候執行一次。
    • 當有查詢結果須要緩存的時候,MySQL先從大的空閒塊中申請一個數據塊用於存儲結果。
      • 這個數據塊須要大於參數query_cache_min_res_unit的配置,即便查詢結果遠遠小於此,仍須要至少申請query_cache_min_res_unit空間。由於須要在查詢開始返回結果的時候就分配空間,而此時是沒法預知查詢結果到底有多大的,因此MySQL沒法爲每個查詢結果精確分配大小剛好匹配的緩存空間。
      • 這個內存塊會盡量小(也可能選擇較大的,這裏不介紹細節),而後將結果存入其中。由於須要先鎖住數據塊,而後找到合適大小的數據塊,因此相對來講,分配內存塊是一個很是慢的操做,MySQL儘可能避免這個操做的次數。
      • 這裏的分配內存塊,是指在空閒塊列表中找到一個合適的內存塊,或者從正在使用的、待淘汰的內存塊中回收再使用。也就是說,MySQL本身管理內存而不依賴與操做系統的內存管理。
    • 若是數據塊所有用完,但仍有剩餘數據須要存儲,MySQL會申請一塊新數據塊(仍然是儘量小)繼續存儲結果數據。
    • 當查詢完成時,若是申請的內存空間仍有剩餘,MySQL會將其釋放,並放入空閒內存部分。

    查詢緩存如何分配內存來存儲結果數據
    查詢緩存如何分配內存來存儲結果數據

  • 實際流程:

    • 假設平均查詢結果很是小,服務器在併發地向不一樣的兩個鏈接返回結果,返回完結果後MySQL回收剩餘數據塊空間時會發現,回收的數據塊小於query_cache_min_res_unit,因此不可以直接在後續的內存塊分配中使用。考慮到這種狀況,數據塊的分配就更復雜些。

      • 在收縮第一個查詢結果使用的緩存空間時,就會在第二個查詢結果之間留下一個「空隙」——很是小的空閒空間,由於小於query_cache_min_res_unit而不能再次被查詢緩存使用。這類空隙稱爲碎片,在內存管理、文件系統管理上都是經典問題。
      • 有不少狀況下都會致使碎片,例如緩存失效時,可能致使留下過小的數據塊沒法在後續緩存中使用。

      查詢緩存中存儲查詢結果後剩餘的碎片
      查詢緩存中存儲查詢結果後剩餘的碎片

5.12.3 什麼狀況下查詢緩存能發揮做用

  • 只有當緩存帶來的資源節約大於其自己的資源消耗時纔會給系統帶來性能提高。
  • 任何SELECT語句沒有從查詢緩存中返回都稱爲「緩存未命中」,緩存未命中的緣由:
    • 查詢語句沒法被緩存,多是由於查詢中包含一個不肯定的函數,或者查詢結果太大。這都會致使狀態值Qcache_not_cached增長
    • MySQL從未處理這個查詢,因此結果也從未曾被緩存過
    • 以前緩存了查詢結果,但因爲查詢緩存的內存用完,須要將某些緩存清除,或者因爲數據表被修改致使緩存失效
  • 服務器上有大量緩存未命中,但實際上最大多數查詢都被緩存了,必定是有以下狀況發生:
    • 查詢緩存尚未完成預熱。也就是說,MySQL尚未將查詢結果都緩存起來。
    • 查詢語句以前從未被執行過。若是應用程序不會重複執行一條查詢語句,那麼即便完成預熱仍然會有不少緩存未被命中。
    • 緩存失效的操做太多。
      • 緩存碎片、內存不足、數據修改都會致使緩存失效。
      • 若是配置了足夠的緩存空間,並且query_cache_min_resunit設置也合理的話,那麼緩存失效應該主要是數據修改致使的。能夠經過Com*查看數據修改的狀況(包括Com_update,Com_delete),也能夠經過Qcache_lowmem_prunes來查看有多少次失效是因爲內存不足致使的。
  • 評估是否使用查詢緩存的方法:
    • 理論上,能夠經過打開或者關閉緩存時候的系統效率來決定是否須要開啓查詢緩存。可是很難評估查詢緩存是否可以帶來性能提高。
      • SHOW STATUS只能提供一個全局的性能指標,也很難評估性能的提高
    • 對於那些須要消耗大量資源的查詢一般都是很是適合緩存。
      • 一些彙總計算查詢,如COUNT()
      • 複雜的SELECT語句,如多表JOIN後還須要排序和分頁,這類查詢每次執行消耗都很大,可是返回的結果集卻很小,很是適合查詢緩存。不過須要注意,涉及表上的UPDATE、DELETE和INSERT相比SELECT來講要很是少。
    • 判斷查詢是否有效的直接數據是命中率,就是使用查詢緩存返回結果佔總查詢的比率。
      • 當MySQL接收到一個SELECT查詢時,要麼增長Qcache_hints的值,要麼增長Com_select的值。
      • 查詢緩存命中率是一個很難判斷的值。命中率多大才是好的?只要查詢緩存帶來的效率提高大於它的消耗,即便只有30%的命中率也能夠;緩存了哪些查詢也很重要,例如,被緩存的查詢自己消耗很是大,即便緩存命中率低也能夠接受
    • 考慮緩存命中率的同時,一般還須要考慮緩存失效帶來的額外消耗。
      • 極端的辦法,對某一個表先作一次只有查詢的測試,而且全部的查詢都命中緩存,另外一個相同的表只作修改操做。這時,查詢緩存的命中率是100%,但由於會給更新操做帶來額外的消耗,因此查詢緩存並不必定會帶來整體效率提高。這裏,全部的更新語句都會作一次緩存失效檢查,而檢查的結果都是相同的,這會給系統帶來額外的資源浪費。
    • MySQL中若是更新操做和帶緩存的操做混合,查詢緩存帶來的好處很難衡量。
      • 若是緩存的結果在失效前沒有被任何其餘的SELECT語句使用,那麼此次緩存操做就是浪費時間和內存。
      • 能夠經過查看Qcache_select和Qcache_inserts的相對值來查看。若是每次查詢操做都是緩存未命中,而後須要將查詢結果放到緩存中,這兩個值應該差很少。因此在緩存完成預熱後,最好的狀況是Query_inserts遠遠小於Query_select
    • 命中率和「INSERT和SELECT比例」都沒法直觀地反應緩存的效率,還有另外一個直觀的辦法:命中和寫入的比例,即Qcache_hints和Qcache_inserts的比率
      • 根據經驗,這個比值大於3:1時一般查詢緩存是有效的,最好可以達到10:1.
      • 若是應用沒有達到這個比率,能夠考慮禁用查詢緩存,除非可以經過精確的計算得知:命中帶來的性能提高大於緩存失效的消耗,而且查詢緩存並無成爲系統的瓶頸。
    • 觀察查詢緩存內存的實際使用狀況,來肯定是否須要縮小或者擴大查詢緩存。
      • 若是查詢緩存達到幾十兆這樣的數量級,是有潛在風險的。(這和硬件以及系統大小有關)
    • 須要和系統的其餘緩存一塊兒考慮,如InnoDB的緩存池,或者MyISAM的索引緩存。
    • 最好的判斷查詢緩存是否有效的辦法仍是經過查看某類查詢時間消耗是否增大或者減小來判斷。

5.12.4 如何配置和維護查詢緩存·

  • 配置:

    • query_cache_type:是否打開查詢緩存。能夠是ON、OFF或者DEMAND。DEMAND表示只有在查詢語句中明確寫明SQL_CACHE的語句才放入查詢緩存。這個變量能夠是會話級別也能夠是全局級別。
    • query_cache_size:查詢緩存使用的總內存空間,單位是字節,必須是1024的整數倍,不然MySQL實際分配的數據可能會有不一樣。
    • query_cache_min_res_unit:在查詢緩存中分配內存塊時的最小單位。
    • query_cache_limit:MySQL可以緩存的最大查詢結果。
      • 若是查詢結果大於這個值,則不會被緩存。由於查詢緩存在數據生成的時候就開始嘗試緩存數據,因此只有當結果所有返回後,MySQL才知道查詢結果是否超出限制
      • 若是超出,MySQL則增長狀態值Qcache_not_cached,並將結果從查詢緩存中刪除。若是事先知道有不少這樣的狀況發生,那麼建議在查詢語句中加入SQL_NO_CACHE來避免查詢緩存帶來的額外消耗。
    • query_cache_wlock_invalidate:若是某個數據表被其餘的鏈接鎖住,是否仍然從查詢緩存中返回結果。默認是OFF。
  • 減小碎片

    • 沒有什麼辦法可以徹底避免碎片,可是合適的query_cache_min_res_unit能夠減小由碎片致使的內存空間浪費。
      • 設置合適的值能夠平衡每一個數據塊的大小和每次存儲結果時內存塊的申請次數,實際上是在平衡內存浪費和CPU消耗。
      • 這個值過小,則浪費的空間更少,可是會致使更頻繁的內存塊申請操做。若是太多,則碎片會不少。
    • 這個參數的最合適大小和應用程序的查詢結果的平均大小直接相關。
      • 能夠經過內存實際消耗(query_cache_size-Qcache_free_memory)除以Qcache_queries_in_cache計算單個查詢的平均緩存大小。
      • 若是查詢結果大小很不均勻,那麼碎片和反覆的內存塊分配可能沒法避免。
      • 若是發現緩存了一個很是大的結果,能夠經過參數query_cache_limit限制能夠緩存的最大查詢結果。
    • 能夠經過參數Qcache_free_blocks來觀察碎片,反映了查詢緩存中內存塊的多少。
      • 若是Qcache_free_blocks剛好達到Qcache_total_blocks/2,那麼查詢緩存就有嚴重的碎片問題。
      • 若是還有不少空閒塊,而狀態值Qcache_lowmem_prunes還不斷增長,則說明因爲碎片致使了過早的刪除查詢緩存結果。
    • 可使用FLUSH QUERY CACHE完成碎片整理。這個命令會將全部的查詢緩存從新排序,並將全部的空閒空間都彙集到查詢緩存的一塊區域上。
      • 會訪問全部的查詢緩存,在這期間任何其餘的鏈接都沒法訪問查詢緩存,從而致使服務器僵死一段時間。所以,建議保持查詢緩存空間足夠小
      • 清空緩存由RESET QUERY CACHE完成
  • 提升查詢緩存的使用率

    • 若是查詢緩存再也不有碎片問題,但命中率仍然很低,還多是查詢緩存內存空間過小致使的。若是MySQL沒法爲一個新的查詢緩存結果的時候,則會刪除某個老的查詢緩存
    • 當刪除老的查詢緩存時,會增長狀態值Qcache_lowmem_prunes。若是這個值增加的很快,多是由如下兩個緣由致使的:
      • 若是還有不少空閒塊,那麼碎片可能就是罪魁禍首
      • 若是這時沒什麼空閒塊,就說明在這個系統壓力下,分配的查詢緩存空間不夠大。能夠經過檢查狀態值Qcache_free_memory來查看還有多少沒有使用的內存。

    如何分析和配置查詢緩存
    如何分析和配置查詢緩存

5.12.5 InnoDB和查詢緩存

  • 由於InnoDB有本身的MVVC機制,InnoDB會控制在一個事務中是否可使用查詢緩存,InnoDB會同時控制對查詢緩存的讀和寫操做。
    • 事務是否能夠訪問查詢緩存決定於當前的事務ID,以及對應的數據表上是否有鎖。每個InnoDB表的內存數據字典都保存了一個事務ID,若是當前事務ID小於該事務ID,則沒法訪問查詢緩存。
    • 若是表上有任何鎖,那麼對這個表的任何查詢語句都是沒法被緩存的。
  • InnoDB下的查詢緩存:
    • 全部大於該表計數器的事務才能夠直接使用(讀和寫)查詢緩存。
    • 該表的計數器並非直接更新爲對該表進行加鎖操做的事務ID,而是被更新成一個系統事務ID。因此,會發現該事務自身後續的更新操做也沒法讀取和修改查詢緩存。
  • 查詢緩存存儲、檢索和失效操做都是在MySQL層面完成,InnoDB沒法繞過或者延遲這個行爲。
    • 可是InnoDB能夠在事務中顯式地告訴MySQL什麼時候應該讓某個表的查詢緩存都失效。在有外鍵限制的時候這是必須的,例如某個SQL有ON DELETE CASCADE。
  • 原則上,在InnoDB的MVVC架構下,當某些修改不影響其餘事務讀取一致的數據時,是可使用查詢緩存的。可是這樣實現起來會很負責,InnoDB作了簡化,讓全部有加鎖操做的事務都不使用任何查詢緩存,這個限制不是必須的。

5.12.6 通用查詢緩存優化

  • 用多個小表代替一個大表對查詢緩存有好處。這個設計將會使得失效策略可以在一個更合適的粒度上進行。固然,不要讓這個原則過度影響設計,畢竟其它的一些優點很容易就能彌補。
  • 批量寫入時只須要作一次緩存失效,因此相比單條寫入的效率要高。注意不要同時作延遲寫和批量寫,不然可能會致使服務器僵死較長時間。
  • 由於緩存空間太大,在過時操做的時候可能會致使服務器僵死。一個簡單的辦法就是控制緩存空間的大小,或者直接禁用查詢緩存。
  • 沒法在數據庫或則表級別控制查詢緩存,可是能夠經過SQL_CACHE和SQL_NO_CACHE來控制某個SELECT語句是否須要進行緩存。還能夠修改會話級別的query_cache_type來控制查詢緩存。
  • 對於寫密集型的應用來講,直接禁用查詢緩存可能會提升系統性能。關閉查詢緩存能夠移除全部相關的消耗,例如將query_cache_size設置爲0
  • 由於對互斥信號量的競爭,有時直接關閉查詢緩存對讀密集型的應用也會有好處。
  • 若是不想全部的查詢都進入查詢緩存,能夠將query_cache_type設置爲DEMAND,而後在但願緩存的查詢中加上SQL_CACHE

5.12.7 查詢緩存的替代方案

查詢緩存的工做原則是:執行查詢最快的方式就是不去執行。可是查詢仍然要發送到服務器端,服務器端還須要作一點點工做。所以能夠直接在客戶端進行緩存。

5.13 總結

  • 分區表:分區表是一種粗粒度的、簡易的索引策略,適用於大數據量的過濾場景。最適合的場景是,在沒有合適的索引時,對其中幾個分區進行全表掃描,或者是隻有一個分區和索引是熱點,並且這個分區和索引都可以在內存中;限制單表分區數不要超過150個,而且注意某些致使沒法作分區過濾的細節,分區表對於單條記錄的程序並無什麼優點,須要注意這類查詢的性能。
  • 視圖:對好幾個表的複雜查詢,使用視圖有時候會大大簡化問題。當視圖使用臨時表時,沒法將WHERE條件下推到各個具體的表,也不能使用任何索引,須要特別注意這類查詢的性能。若是爲了遍歷,使用視圖是很合適的。
  • 外鍵:外鍵限制會將約束放到MySQL中,這對於必須維護外鍵的場景,性能會更高。不過這也會帶來額外的複雜性和額外的索引消耗,還會增長多表之間的交互,會致使系統中有更多的鎖和競爭。外鍵能夠被看做是一個確保系統完整性的額哇的特性,可是若是設計的是一個高性能的系統,那麼外鍵就會顯得很臃腫了。不少人在更在乎系統的性能的時候都不會使用外鍵,而是經過應用程序來維護。
  • 存儲過程
  • 綁定變量
  • 插件
  • 字符集:字符集是一種字節到字符之間的映射,而校對規則是指一個字符集的排序方法。不少人都使用Latin1(默認字符集,對英語和某些歐洲語言有效)或者UTF-8。若是使用的是UTF-8,那麼在使用臨時表和緩衝區的時候須要注意:MySQL會按照每一個字符三個字節的最大佔用空間來分配存儲空間,這可能消耗更多的內存或者磁盤空間。注意讓字符集和MySQL字符集配置相符,不然可能會因爲字符集轉換讓某些索引沒法正常工做。
  • 全文索引
  • XA事務:不多會有人用MySQL的XA事務特性。除非你真正明白參數innodb_support_xa的意義,不然不要修改這個參數的值,並非只有顯示使用XA事務時才須要設置這個參數。InnoDB和二進制日誌也是須要使用XA事務來作協調的,從而確保在系統崩潰的時候,數據可以一致地恢復。
  • 查詢緩存:徹底相同的查詢在重複執行的時候,查詢緩存能夠當即放回結果,而無須在數據庫中從新執行一次。根據經驗,在高併發壓力環境中查詢緩存會致使系統性能的降低,甚至僵死。若是必定要使用查詢緩存,那麼不要設置太大內存,並且只有在明確收益的時候才使用。查詢緩存是一個很是方便的緩存,對應用程序徹底透明,無須任何額外的編碼,可是若是但願有更高效的查詢緩存,建議使用memacched等其餘緩存方案。
相關文章
相關標籤/搜索