1. 程式人生 > >高效能mysql:MySQL高階特性

高效能mysql:MySQL高階特性

分割槽表


分割槽表是一個獨立的邏輯表,底層由多個物理子表構成。實現分割槽的程式碼實際上是對一組底層表的控制代碼物件的封裝。對分割槽表的請求,都會通過控制代碼物件轉化成對儲存引擎的介面呼叫。

MySQL實現分割槽表的方式——對底層表的封裝——意味著索引也是按照分割槽的子表定義的,而沒有全域性索引。

MySQL在建立表的時使用PARTITION BY子句定義每個分割槽存放的資料。在執行查詢的時候,優化器會根據分割槽定義過濾那些沒有我們需要資料的分割槽,這樣查詢就無須掃描所有分割槽——只需要查詢包含需要資料的分割槽就可以了。

分割槽的一個主要目的是將資料按照一個較粗的粒度分在不同的表中。

使用場景:

  • 表非常大以至於無法全部都放在記憶體中,或者只在表的最後部分有熱點資料,其他均是歷史資料。
  • 分割槽表的資料更容易維護。例如,想大批量刪除大量資料可以使用清除整個分割槽的方式。另外,還可以對一個獨立分割槽進行優化、檢查、修復等操作。
  • 分割槽表的資料可以分佈在不同的物理裝置上,從而高效地利用多個硬體裝置。
  • 可以使用分割槽表來避免某些特殊的瓶頸,例如InnoDB的單個索引的互斥訪問、ext3檔案系統的inode鎖競爭等。
  • 如果需要,還可以備份和恢復獨立的分割槽,這在非常大的資料集的場景下效果非常好。

分割槽表本身也有一些限制:

  • 一個表最多隻能有1024個分割槽。
  • 在MySQL5.1中,分割槽表示式必須是整數,或者是返回整數的表示式。在MySQL5.5中,某些場景中可以直接使用列來進行分割槽。
  • 如果分割槽欄位中有主鍵或唯一索引的列,那麼所有主鍵列和唯一索引列都必須包含進來。
  • 分割槽表中無法使用外來鍵約束。
分割槽表的原理

分割槽表由多個相關的底層表實現,這些底層表也是由控制代碼物件表示,所以也可以直接訪問各個分割槽。儲存引擎管理分割槽的各個底層表和管理普通表一樣,分割槽表的索引只是在各個底層表上各自加上一個完全相同的索引。從儲存引擎的角度來看,底層表和一個普通表沒有任何不同,儲存引擎也無需知道這是一個普通表還是一個分割槽表的一部分。

分割槽表上的操作按照下面的操作邏輯進行:

SELECT查詢

當查詢一個分割槽表的時候,分割槽層先開啟並鎖住所有的底層表,優化器先判斷是否可以過濾部分分割槽,然後在呼叫對呀的儲存引擎介面訪問各個分割槽的資料。

INSERT操作

當寫入一條記錄時,分割槽層先開啟並鎖住所有的底層表,然後確定哪個分割槽接手這條記錄,在將記錄寫入對應底層表。

DELETE操作

當刪除一條記錄時,分割槽層先開啟並鎖住所有的底層表,然後確立資料對應的分割槽,最後對相應底層表進行刪除操作。

UPDATE操作

當更新一條記錄時,分割槽層先開啟並鎖住所有的底層表,MySQL先確定需要更新的記錄在哪個分割槽,然後取出資料並更新,在判斷更新後的資料應該放在哪個區,最後對底層表進行寫入操作,並對原資料所在的底層表進行刪除操作。

有些操作是支援過濾的。MySQL先確定需要更新的記錄在哪個分割槽,再將記錄寫入對應的底層分割槽表,無需對任何其他分割槽進行操作。

雖然每個操作都會“先開啟並鎖住所有的底層表”,但這並不是說分割槽表在處理過程中是鎖住全表的。如果儲存引擎能夠自己實現行級鎖,則會在分割槽釋放對應表鎖。

分割槽表的型別

MySQL支援鍵值、雜湊、範圍、列表分割槽,這其中有些還支援子分割槽。根據範圍進行分割槽,每個分割槽儲存落在某個範圍的記錄,分割槽表示式可以是列,也可以是包含列的表示式。

CREATE TABLE sales(
order_date DATETIME NOT NULL,
-- Other columns omitted
) ENGINE=InnoDB PARTITION BY RANGE(YEAR(order_date))(
PARTITION p_2010 VALUES LESS THAN(2010),
PARTITION p_2011 VALUES LESS THAN(2011),
PARTITION p_2012 VALUES LESS THAN(2012),
PARTITION p_catchall VALUES LESS THAN MAXVALUE
);

PARTITION分割槽子句中可以使用各種函式。但有一個要求,表示式返回的值是一個確定的整數,且不能是一個常數。在MySQL5.5中,還可以使用RANGE COLUMNS型別的分割槽,這樣即使是基於時間的分割槽也無須在將其轉化成一個整數。

按時間分割槽的InnoDB表,系統通過子分割槽可降低索引的互斥訪問的競爭。最近一年的分割槽的資料會被非常頻繁地訪問,這會導致大量的互斥量的競爭。使用雜湊子分割槽可以將資料切成多個小片,大大降低互斥量的競爭問題。

其他的分割槽技術包括: 根據鍵值進行分割槽,來減少InnoDB的互斥量競爭。 使用數學模函式來進行分割槽,然後將資料輪詢放入不同的分割槽。 假設表有一個自增的主鍵列id,希望根據時間最近的熱點資料集中存放。那麼必須將時間戳包含在主鍵當中才行,這和主鍵本身的意義相矛盾。這種情況可以這樣達到目的HASH(id DIV 1000000)

如何使用分割槽表

假設從一個非常大的表中查詢出一段時間的記錄,因為資料量巨大,肯定不能在每次查詢的時候都掃描全表。考慮到索引在空間和維護上的消耗,也不希望使用索引。即使真的使用索引,你會發現資料並不是按照想要的方式聚集的,而且會有大量的碎片產生,最終會導致一個查詢產生成千上萬的隨機I/O,應用程式也隨機僵死。情況好一點的時候,也許可以通過一兩個索引解決一些問題。不過多數情況下,索引不會有任何作用。這時候只有兩條路可選:讓所有的查詢都只在資料表上做順序掃描,或者將資料表和索引全部都快取在記憶體裡。

在資料量超大的時候,B-Tree索引就無法起作用了。除非是索引的覆蓋查詢,否則資料庫伺服器需要根據索引掃描的結果回表,查詢所有符合條件的記錄,如果資料量巨大,這將產生大量隨機I/O,資料庫的響應時間將大到不可接手的程度。

理解分割槽時還可以將其當做索引的最初形態,以代價非常小的方式定位到需要的資料在哪一片"區域"。在這片"區域"中,可以做順序掃描、索引、將資料快取到記憶體等等。因為分割槽無須額外的資料結構記錄每個分割槽有哪些資料——分割槽不需要精確定位每條資料的位置,也就無須額外的資料結構——所以代價非常低。只需要一個簡單的表示式就可以表達每個分割槽存放的是什麼資料。

保證大資料量的可擴充套件性,一般有下面兩個策略:

  • 全量掃描資料,不要任何索引 可以使用簡單的分割槽存放表,不要任何索引,根據分割槽的規則大致定位需要的資料位置。只要能夠使用WHERE條件,將需要的資料限制在少數分割槽中,則效率是很高的。這個策略適用於以正常的方式訪問大量資料的時候。但是必須將查詢需要掃描的分割槽個數限制在一個很小的數量

  • 索引資料,並分離熱點 如果資料有明顯的"熱點",而且除了這部分資料,其他資料很少被訪問到,那麼可以將這部分熱點資料單獨放在一個分割槽中,讓這個分割槽的資料能夠有機會都快取在記憶體中。這樣查詢就可以只訪問一個很小的分割槽表,能夠使用索引,也能夠有效地使用快取。

什麼情況下會出問題

上面介紹的兩個分割槽策略都基於兩個非常重要的假設:查詢都能夠過濾掉很多額外分割槽、分割槽本身並不會帶來很多額外的代價。而事實證明,這兩個假設在某些場景下會有問題。

NULL值會使分割槽過濾無效

關於分割槽表一個容易讓人誤解的地方就是分割槽的表示式的值可以是NULL:第一個分割槽是一個特殊分割槽。假設按照PARTITION BY RANGE YEAR(order_date)分割槽,那麼所有order_date為NULL或者是一個非法值的時候,記錄都會被存放到第一個分割槽。現在假設有下面的查詢:WHERE order_date BETWEEN '2012-01-01' AND '2012-01-31'。實際上,MySQL會檢查兩個分割槽,檢查第一個分割槽是因為YEAR()函式在接收非法值的時候可能會返回NULL值,那麼這個範圍的值可能會返回NULL而被存放到第一個分割槽了。

如果第一個分割槽非常大,特別是當使用"全量掃描資料,不要任何索引"的策略時,代價會非常大。而且掃描兩個分割槽來查詢列也不是我們使用分割槽的初衷。

分割槽列和索引列不匹配

如果定義的索引列和分割槽列不匹配,會導致查詢無法進行分割槽過濾。假設在列a上定義了索引,而在列b上進行了分割槽。因為每個分割槽都有其獨立的索引,所以掃描列b上的索引就需要掃描每一個分割槽對應的索引。

選擇分割槽的成本可能很高

分割槽有很多型別,不同型別分割槽的實現方式也不同,所以他們的效能也各不相同。尤其是範圍分割槽,對於回答"這一行屬於哪個分割槽"、"這些符合查詢條件的行在哪些分割槽"這樣的問題成本可能會非常高,因為伺服器需要掃描所有的分割槽定義的列表來找到正確答案。類似這樣的線性搜尋的效果不高,所以隨著分割槽數的增長,成本會越來越高。

開啟並鎖住所有底層表的成本可能很高

當查詢訪問分割槽表的時候,MySQL需要開啟並鎖住所有的底層表,這是分割槽表的另一個開銷。這個操作在分割槽過濾之前發生,所以無法通過分割槽過濾降低此開銷,並且改開銷也和分割槽型別無關,會影響所有的查詢。

維護分割槽的成本可能很高

  • 目前分割槽實現中的一些其他限制:
  • 所有分割槽都必須使用相同的儲存引擎。
  • 分割槽函式中可以使用的函式和表示式也有一些限制。
  • 某些儲存引擎不支援分割槽。
  • 對於MyISAM的分割槽表,不能再使用LOAD INDEX INTO CACHE操作
  • 對於MyISAM表,使用分割槽表時需要開啟更多的檔案描述符。雖然看起來是一個表,其實背後有很多獨立的分割槽,每一個分割槽對於儲存引擎來說都是一個獨立的表。這樣即使分割槽表只佔用一個表快取條目,檔案描述符還是需要多個。因此,即使已經配置了合適的表快取,以確保不會超過作業系統的單個程序可以開啟的檔案描述符的個數,但對於分割槽表而言,還是會出現超過檔案描述符限制的問題。
查詢優化

引入分割槽給查詢優化帶來了一些新的思路。分割槽最大的優點就是優化器可以根據分割槽函式來過濾一些分割槽。根據粗粒度索引的優勢,通過分割槽過濾通常可以讓查詢掃描更少的資料。

對於訪問分割槽表來說,很重要的一點是要在WHERE條件中帶入分割槽列,有時候即使看似多餘的也要帶上,這樣就可以讓優化器能夠過濾無須訪問的分割槽。

這個查詢訪問所有的分割槽。

MySQL只能在使用分割槽函式的列本身進行比較時才能過濾分割槽,而不能根據表示式的值去過濾分割槽,即使這個表示式就是分割槽函式也不行。

這裡寫的WHERE條件中帶入的是分割槽列,而不是基於分割槽列的表示式,所以優化器能夠利用這個條件過濾部分分割槽。一個很重要的原則是:即便在建立分割槽時可以使用表示式,但在查詢時卻只能根據列來過濾分割槽。

合併表

合併表是一種早期的、簡單的分割槽實現,和分割槽表相比有一些不同的限制,並且缺乏優化。分割槽表嚴格來說是一個邏輯上的概念,使用者無法訪問底層的各個分割槽,對使用者來說分割槽是透明的。

合併表相當於一個容器,裡面包含了多個真實表。可以在CREATE TABLE中使用一種特別的UNION 語法來指定包含哪些真實表。

最後建立的合併表和前面的各個真實表字段完全相同,在合併表中有的索引各個真實子表也有,這是建立合併表的前提條件。各個子表在對應列上都有主鍵限制,但是最終的合併表中仍然出現了重複值。

INSERT_METHOD=LAST告訴MySQL,將所有的INSERT語句都發送給最後一個表。指定FIRST或者LAST關鍵字是唯一可以控制行插入到合併表的哪一個子表的方式。

INSERT語句的執行結果可以在最終的合併表中看到,也可以在對應的子表中看到:

刪除一個合併表,它的子表不受任何影響,而如果直接刪除其中一個子表則可能會有不同的後果,這要視作業系統而定。

  • 在使用CREATE語句建立一個合併表的時候,並不會檢查各個子表的相容性。如果子表的定義稍有不同,那麼MySQL就可能創建出一個後面無法使用的合併表。

  • 根據合併表的特性,不難發現,在合併表上無法使用REPLACE語法,無法使用自增欄位。

  • 如果一個查詢訪問合併表,那麼它需要訪問所有子表。這會讓根據鍵查詢單行的查詢速度變慢,如果能夠只訪問一個對應表,速度肯定將更快。限制合併表中的子表數量特別重要,特別是當合並表是某個關聯查詢的一部分的時候,因為這時訪問一個表的記錄數可能會將比較操作傳遞到關聯的其他表中,這時減少記錄的訪問就是減少整個關聯操作。

    • 執行範圍查詢時,需要在每一個子表各執行一次,這比直接訪問單個表的效能要差很多,而且子表越多,效能越差。
    • 全表掃描和普通表的全表掃描速度相同。
    • 在合併表上做某一鍵和主鍵查詢時,一旦找到一行資料就會停止。
    • 子表的讀取順序和CREATE TABLE語句中的順序相同。

合併表的各個子表可以直接被訪問,它還具有一些MySQL5.5分割槽所不能提供的特性:

  • 一個MyISAM表可以是多個合併表的子表。
  • 可以通過直接複製.frm、.MYI、.MYD檔案,來實現在不同的伺服器之間複製各個子表。
  • 在合併表中可以很容易的新增新的子表:直接修改合併表的定義就可以了。
  • 可以建立一個合併表,讓它只包含需要的資料。
  • 如果想對某個子表做備份、恢復、修改、複製或者別的操作時,可以先將其從合併表中刪除,操作結束後再將其加回去。
  • 可以使用myisampack來壓縮所有的子表。

檢視


檢視本身是一個虛擬表,不存放任何資料。在使用SQL語句訪問檢視的時候,它返回的資料是MySQL從其他表中生成的。檢視和表是在同一個名稱空間,MySQL在很多地方對於檢視和表是同樣對待的。不過檢視和表也有不同,例如,不能對檢視建立觸發器,也不能使用DROP TABLE命令刪除檢視。

CREATE VIEW Oceania AS SELECT * FROM Country WHERE Continent = 'Oceania' WITH CHECK OPTION;

實現檢視最簡單的方法是將SELECT 語句的結果存放到臨時表中。當需要訪問檢視的時候,直接訪問這個臨時表就可以了。

SELECT Code,Name FROM Oceania WHERE Name ='BeiJing';
CREATE VIEW Oceania AS SELECT * FROM Country WHERE Continent = 'Oceania' WITH CHECK OPTION;
SELECT Code,Name FROM Oceania WHERE Name ='BeiJing';

這樣做會有明顯的效能問題,優化器也很難優化在這個臨時表上的查詢。實現檢視更好的方法是,重寫含有檢視的查詢,將檢視的定義SQL直接包含進查詢的SQL中。

SELECT Code,Name FROM Country WHERE Continent = 'Oceania' AND Name ='BeiJing';

MySQL使用合併演算法和臨時表演算法處理檢視。

如果檢視中包含GROUP BY、DISTINCT、任何聚合函式、UNION、子查詢等,只要無法在原表記錄和檢視記錄中建立一一對映的場景中,MySQL都將使用臨時表演算法來實現檢視。

檢視的實現演算法是檢視本身的屬性,和作用在檢視上的查詢語句無關。

可更新檢視

可更新檢視是指可以通過更新這個檢視來更新這個檢視來更新檢視涉及的相關表。只要指定了合適的條件,就可以更新、刪除甚至向檢視寫入資料。

UPDATE Oceania SET Population = Population * 1.1 WHERE Name = 'BeiJing';

如果檢視定義中包含了GROUP BY、UNION、聚合函式,以及其他一些特殊情況,就不能更新了。檢視更新的查詢也可以是一個關聯語句,但是有個一限制,被更新的列必須來自同一個表中。所有使用臨時表演算法實現的檢視都無法被更新。

檢視對效能的影響

在MySQL中某些情況下檢視也可以幫助提升效能。而且檢視還可以提升效能的方式疊加使用。例如,在重構schema的時候可以使用檢視,使得在修改檢視底層表結構的時候,應用程式碼還可能繼續不報錯的執行。

可以使用檢視實現基於列的許可權控制,卻不需要真正的在系統中建立列許可權,因此沒有額外的開銷。

CREATE VIEW public.employeeinfo AS SELECT firstname,lastname FROM private.employeeinfo;
GRANT SELECT ON public.* TO pulic_user;

我們這裡使用連線ID作為檢視名字的一部分來避免衝突。

MySQL先執行檢視的SQL生成臨時表,然後再將sales_per_day和臨時表關聯。這裡的WHERE子句中的BETWEEN條件並不能下推到檢視中,所以檢視在建立的時候仍需要將所有的資料都放到臨時表中。而且臨時表中不會有索引。

如果打算使用檢視來提升效能,需要做比較詳細的測試。即使是合併演算法實現的檢視也可能會有額外的開銷,而且檢視的效能很難預測。

檢視的限制

MySQL還不支援物化檢視(物化檢視是指將檢視結果資料放在一個可以檢視的表中,並定期從原始表中重新整理資料到這個表中)。MySQL也不支援在檢視中建立索引。

可以找到定義檢視原始SQL語句

外來鍵約束


InnoDB是目前MySQL中唯一支援外來鍵的內建儲存引擎。

使用外來鍵是有成本的。比如外來鍵通常都要求每次在修改資料時都要在另外一張表中多執行一次檢查操作。在某些場景下,外來鍵會提升一些效能。如果想確保兩個相關表始終有一致的資料,那麼使用外來鍵比在應用程式檢查一致性的效能要高得多,外來鍵在相關資料的刪除和更新上,比應用在維護要更高效。

外來鍵約束使得查詢需要額外訪問一些特別的表,意味著需要額外的開銷。如果向子表中寫入一條記錄,外來鍵約束會讓InnoDB檢查對應的父表的記錄,也就需要對父表對應記錄進行加鎖操作,來確保這條記錄不會在這個事物完成之時就被刪除了。這會導致額外的鎖等待,甚至會導致一些死鎖。

對於相關資料的同時更新外來鍵更合適,但是如果外來鍵只是用作數值約束,那麼觸發器或者顯式地限制取值會更好些。

如果只是使用外來鍵約束,那通常在應用程式裡實現該約束會更好。外來鍵會帶來很大的額外消耗。

在MySQL內部儲存程式碼


MySQL允許通過觸發器、儲存過程、函式的形式來儲存程式碼。從MySQL5.1開始,還可以在定時任務中存放程式碼,這個定時任務也被稱為"事件"。儲存過程和儲存函式都被統稱為"儲存函式"。

不同型別的儲存程式碼的主要區別在於其執行的上下文——也就是輸入和輸出。儲存過程和儲存函式都可以接收引數然後返回值,但是觸發器和事件不行。

儲存程式碼的優點:

  • 它在伺服器內部執行,離資料最近,另外在伺服器上執行還可以節省寬頻和網路延遲。
  • 這是一種程式碼重用。可以方便地統一業務規則,保證某些行為總是一致,所以也可以為應用提供一定的安全性。
  • 它可以簡化程式碼的維護和版本更新。
  • 他可以幫助提升安全,比如提供更細粒度的許可權控制。
  • 伺服器端可以快取儲存過程的執行計劃,這對於需要反覆呼叫的過程,會大大降低消耗。
  • 在伺服器端部署的,備份、維護都可以在伺服器端完成。
  • 它可以在應用開發和資料庫開發人員之間更好地分工。

儲存程式碼的缺點:

  • MySQL本身沒有提供好用的開發和除錯工具,所以編寫MySQL的儲存程式碼比其他資料庫要更難些。
  • 較之應用程式的程式碼,儲存程式碼效率要稍微差些。儲存程式碼中可以使用的函式非常有限,使用儲存程式碼很難編寫複雜的字串維護功能,也能難實現太複雜的邏輯。
  • 儲存程式碼可能會給應用程式程式碼的部署帶來額外的複雜性。
  • 因為儲存程式都部署在伺服器內,所以可能有安全隱患。如果將非標準的加密功能放在儲存過程中,那麼若資料庫被攻破,資料也就洩漏了。
  • 儲存過程會給資料庫伺服器增加額外的壓力,而資料庫伺服器的效能擴充套件性相比應用伺服器要差很多。
  • MySQL並沒有什麼選項可以控制儲存過程的資源消耗,所以在儲存過程中的一個小錯誤,可能直接把伺服器拖死。
  • MySQL的儲存程式碼功能還非常非常弱。
  • 除錯MySQL的儲存過程是一件很困難的事情。
  • 它和基於語句的二進位制日誌複製合作得並不太好。
儲存過程和函式

MySQL的架構本身和優化器的特性使得儲存程式碼有一些天然限制,他的效能也一定程度受限於此。

  • 優化器無法使用關鍵字DETERMINISTIC來優化單個查詢中多次呼叫儲存函式的情況。
  • 優化器無法評估儲存函式的執行成本。
  • 每個連線都有獨立的儲存過程的執行計劃快取。如果有多個連線需要呼叫同一個儲存過程,將會浪費快取空間來反覆快取同樣的執行計劃。
  • 儲存程式和複製是一組詭異的組合。最好不要複製對儲存程式的呼叫。

我們通常會希望儲存程式越小、越簡單越好。希望將更加複雜的處理邏輯交給上層的應用實現,通常這樣會使程式碼更易讀、易維護,也會更靈活。

儲存過程要快很多,很大程度因為它無須網路通訊開銷、解析開銷和優化器開銷等。

觸發器

觸發器可以讓你在執行INSERT、UPDATE、或者DELETEA的時候,執行一些特定的操作。可以在MySQL中指定是在SQL語句執行之前觸發還是在執行後觸發。觸發器本身沒有返回值,不過他們可以讀取或者改變觸發SQL語句所影響的資料。

因為觸發器可以減少客戶端和伺服器之間的通訊,所以觸發器可以簡化應用邏輯,還可以提高效能。

MySQL觸發器的實現非常簡單,所以功能也有限。

  • 對每一個表的每一個事件,最多隻能定義一個觸發器。

  • MySQL只支援"基於行的觸發"——也就是說,觸發器始終是針對一條記錄的,而不是針對整個SQL語句的。如果變更的資料集非常大的話,效率會很低。

  • 觸發器可以掩蓋伺服器背後的工作,一個簡單的SQL語句背後,因為觸發器,可能包含了很多看不見的工作。

  • 觸發器的問題也很難排查,如果某個效能問題和觸發器相關,會很難分析和定位。

  • 觸發器可能導致死鎖和鎖等待。如果觸發器失敗,那麼原來的SQL語句也會失敗。

因為效能的原因,很多時候無法使用觸發器來維護彙總和快取表。使用觸發器而不是批量更新的一個重要原因就是,使用觸發器可以保證資料總是一致的。

觸發器並不能一定保證更新的原子性。一個觸發器在更新MyISAM表的時候,如果遇到什麼錯誤,是沒有辦法做回滾操作的。

在InnoDB表上的觸發器是在同一個事物中完成的,所以它們執行的操作是原子的,原子操作和觸發器操作會同時失敗或者成功。不過,如果在InnoDB表上建觸發器去檢查資料的一致性,需要特別小心MVCC,稍不小心,你可能會獲得錯誤的結果。你想實現外來鍵約束,但是不打算使用InnoDB的外來鍵約束。若打算編寫一個BEFOREN INSERT觸發器來檢查寫入的資料對應列在另一個表中是否是存在的,但若你在觸發器中沒有使用SELECT FOR UPDATE,那麼併發的更新語句可能會立刻更新對應記錄,導致資料不一致。

觸發器非常有用,尤其是實現一些約束、系統維護任務、以及更新反正規化化資料的時候。還可以使用觸發器來記錄資料變更日誌。這對實現一些自定義的複製會非常方便。

事件

事件指定MySQL在某個時候執行一段SQL程式碼,或者每隔一個時間間隔執行一段SQL程式碼。通常,會把複雜的SQL都封裝到一個儲存過程中,這樣事件在執行的時候只需要做一個簡單的CALL呼叫。

事件在一個獨立事件排程執行緒中被初始化,這個執行緒和處理連線的執行緒沒有任何關係。它不接受任何引數,也沒有任何的返回值。可以在MySQL的日誌中看到命令的執行日誌。還可以在表INFORMATION_SCHEMA.EVENTS中看到各個事件狀態。

事件實現機制本身的開銷並不大,但是事件需要執行SQL,則肯能會對效能有很大的影響。更進一步,事件和其他的儲存程式一樣,在和基於語句的複製一起工作時,可也能會觸發同樣的問題。事件的一些典型應用包括定期地維護任務、重建快取、構建彙總表來模擬物化檢視,或者儲存用於監控和診斷的狀態值。

在儲存程式中保留註釋

儲存過程、儲存函式、觸發器、事件通常都會包含大量的重要程式碼,在這些程式碼中加上註釋非常有必要。

一個將註釋儲存到儲存程式中的技巧就是使用版本相關的註釋,因為這樣的註釋可能被MySQL伺服器執行。伺服器和客戶端都知道這不是普通的註釋,所以也就不會刪除這些註釋。

遊標

MySQL在伺服器端提供只讀、單向的遊標,而且只能在儲存過程或者更底層的客戶端API中使用。因為MySQL遊標中指向的物件都是儲存在臨時表中而不是實際查詢到的資料,所以MySQL遊標總是隻讀的。它可以逐行指向查詢結果,然後讓程式做進一步的處理。在一個儲存過程中,可以有多個遊標,也可以在迴圈中"巢狀"地使用遊標。

因為是使用臨時表實現的,所以它在效率上給開發人員一個錯覺。當你開啟一個遊標的時候需要執行整個查詢。

Oracle或者SQL Server的使用者不會認為這個儲存過程有什麼問題,但是在MySQL中,這會帶來很多不必要的額外操作。

遊標也會讓MySQL執行一些額外的I/O操作,而這些操作的效率可能非常低。因為臨時記憶體表不支援BLOB和TEXT型別,如果遊標返回的結果包含這樣的列的話,MySQL就必須建立臨時磁碟表來存放。當臨時表大於tmp_table_size的時候,MySQL也還是會在磁碟上建立臨時表。

繫結變數


當建立一個繫結變數SQL時,客戶端向伺服器傳送了一個SQL語句的原型。伺服器端收到這個SQL語句框架後,解析並存儲這個SQL語句的部分執行計劃,返回給客戶端一個SQL語句處理控制代碼。以後每次執行這類查詢,客戶端都指定使用這個控制代碼。

繫結變數的SQL,使用問好標記可以接收引數的位置,當真正需要執行具體查詢的時候,則使用具體值代替這些問號。

MySQL在使用繫結變數的時候可以更高效地執行大量的重複語句:

  • 在伺服器端只需要解析一次SQL語句。
  • 在伺服器端某些優化器的工作只需要執行一次,因為他會快取一部分的執行計劃。
  • 以二進位制的方式只發送引數和控制代碼,比起每次都發送ASCII碼文字效率更高,一個二進位制的日期欄位只需要三個位元組,但如果是ASCII碼則需要十個位元組。
  • 僅僅是引數——而不是整個查詢語句——需要傳送到伺服器端,所以網路開銷會更小。
  • MySQL在儲存引數的時候,直接將其存放到快取中,不再需要再記憶體中多次複製。

繫結變數相對也更安全。無須再應用程式中處理轉義,一則更簡單了,二則也大大減少了SQL注入和攻擊的風險。

繫結變數的優化

對使用繫結變數的SQL,MySQL能夠快取其部分執行計劃,如果某些執行計劃需要根據傳入的引數來計算時,MySQL就無法快取這部分的執行計劃。

在準備階段 伺服器解析SQL語句,移除不可能的條件,並且重寫子查詢。

在第一次執行的時候

如果可能的話,伺服器先簡化巢狀迴圈的關聯,並將外關聯轉化成內關聯。

在每次SQL語句執行時

伺服器做以下事情

  • 過濾分割槽
  • 如果可能的話,儘量移除COUNT()、MIN()和MAX()
  • 移除常數表示式
  • 檢測常量表
  • 做必要的等值傳播
  • 分析和優化ref,range和索引優化等訪問資料的方法
  • 優化關聯順序。
SQL介面的繫結變數

MySQL支援了SQL介面的繫結變數。不使用二進位制傳輸協議也可以直接以SQL的方式使用繫結變數。

最主要的用途就是在儲存過程中使用。在MySQL5.0版本中,就可以在儲存過程中使用繫結變數,其語法和前面介紹的SQL介面和繫結變數類似。這意味著,可以在儲存過程中構建並執行"動態"的SQL語句,這裡的"動態"是指可以通過靈活的拼接字串等引數構建SQL語句。

編寫儲存過程時,SQL介面的繫結變數通常可以很大程度地幫助我們除錯繫結變數,如果不是在儲存過程中,SQL介面的繫結變數就不是那麼有用了。因為SQL介面的繫結變數,它既沒有使用二進位制傳輸協議,也沒有能夠節省頻寬,相反還總是需要增加至少一額外網路傳輸才能完成一次傳輸。

繫結變數的限制
  • 繫結變數是會話級別的,所以連線之間不能共用繫結變數控制代碼。同樣地,一旦連線斷開,則原來的控制代碼也不能再使用了。
  • 在MySQL5.1版本之前,繫結變數的SQL是不能使用查詢快取的。
  • 並不是所有的時候使用繫結變數都能獲得更好的效能。如果只是執行一次SQL,那麼使用繫結變數方式無疑比直接執行多了一次額外的準備階段消耗,而且還需要一次額外的網路開銷。
  • 當前版本下,還不能在儲存函式中使用繫結變數。
  • 如果總是忘記釋放繫結變數資源,則在伺服器端很容易發生資源"洩漏"。繫結變數SQL總數的限制是一個全侷限制,所以某一個地方的錯誤可能會對所有其他的執行緒都產生影響。
  • 有些操作,如BEGIN,無法在繫結變數中完成。

三種繫結變數的區別:

  • 客戶端模擬的繫結變數

客戶端的驅動程式接收一個帶引數的SQL,在將指定的值帶入其中,最後將完整的查詢傳送到伺服器端。

  • 伺服器端的繫結變數

客戶端使用特殊的二進位制協議將帶引數的字串傳送到伺服器埠,然後使用二進位制協議將具體的引數值傳送給伺服器端並執行。

  • SQL介面的繫結變數

客戶端先發送一個帶引數的字串到服務端,這類似於使用PREPARE的SQL語句,然後傳送設定引數的SQL,最後使用EXECUTE來執行SQL。所有這些都使用普通的文字傳輸協議。

使用者自定義函式


MySQL支援使用者自定義函式(UDF)。儲存過程只能使用SQL來編寫,而UDF沒有這個限制,你可以使用支援C語言呼叫約定的任何程式語言來實現。

UDF必須事先編譯好並動態連結到伺服器上,這種平臺相關性使得UDF在很多方面都很強大。UDF速度非常快,而且可以訪問大量作業系統的功能,還可以使用大量函式庫。

能力越大,責任越大。所以在UDF中的一個錯誤很可能會讓伺服器直接崩潰,甚至擾亂伺服器的記憶體或者資料,另外,所有C語言具有的潛在風險,UDF也都有。

外掛


  • 儲存過程外掛 儲存過程外掛可以幫你在儲存過程執行後在處理一次執行結果。
  • 後臺外掛 後臺外掛可以讓你的程式在MySQL中執行,可以實現自己的網路監聽、執行自己的定期任務。
  • INFORMATION_SCHEMA外掛 這個外掛可以提供一個新的記憶體INFORMATION_SCHEMA表。
  • 全文解析外掛 這個外掛提供一種處理文字的功能,可以根據自己的需求來對一個文件進行分詞,所以如果給定一個PDF文件目錄,可以使用這個外掛對這個文件進行分詞處理。也可以用此來增強查詢執行過程中的詞語匹配功能。
  • 審計外掛 審計外掛在查詢執行的過程中的某些固定點被呼叫,所以它可以用作記錄MySQL的事件日誌。
  • 認證外掛 認證外掛既可以在MySQL客戶端也可以在它的服務端,可以使用這類外掛來擴充套件MySQL的認證功能。

字符集和校對


字符集是指一種從二進位制編碼到某類字元符號的對映,可以參考如何使用一個位元組表來表示英文字母。"校對"是指一組用於某個字符集的排序規則。MySQL4.1和之後的版本中,每一類編碼字元都有其對應的字符集和校對規則。

#####MySQL如何使用字符集

每種字符集都可能有多種校對規則,並且都有一個預設的校對規則。每個校對規則都是針對某個特定的字符集的,和其他的字符集沒有關係。校對規則和字符集總是一起使用。

只有基於字元的值才真正的"有"字符集的概念。對於其他型別的值,字符集只是一個設定,指定用哪一種字符集來做比較或者其他操作。

MySQL的設定可以分為兩類:建立物件時的預設值、在伺服器和客戶端通訊時的設定。

建立物件時的預設設定

MySQL伺服器有預設的字符集和校對規則,每個資料庫也有自己的預設值,每個表也有自己的預設值。這是一個逐層繼承的預設設定,最終最靠底層的預設設定將影響你建立的物件。

  • 建立資料庫的時候,將根據伺服器上的character_set_server,設定來設定該資料庫的預設字符集。
  • 建立表的時候,將根據資料庫的字符集設定指定這個表的字符集設定。
  • 建立列的時候,將根據表的設定指定列的字符集設定。

真正存放資料的是列,更高"階梯"的設定只是指定預設值。一個表的預設字符集設定無法影響儲存在這個表中某個列的值。只有當建立列而沒為列指定字符集的時候,如果沒有指定字符集,表的預設字符集才有作用。

伺服器和客戶端通訊時的設定

當伺服器和客戶端通訊的時候,它們可能使用不同的字符集。伺服器端將進行必要的翻譯轉換工作:

  • 伺服器端總是假設客戶端是按照character_set_client設定的字元來傳輸資料額SQL語句的。
  • 當伺服器收到客戶端的SQL語句時,它先將其轉換成字符集character_set_connection。它還使用這個設定來決定如何將資料轉換成字串。
  • 當伺服器端返回資料或者錯誤資訊給客戶端時,它會將其轉換成character_set_result。

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

MySQL如何比較兩個字串的大小

如果比較的兩個字串的字符集不同,MySQL會先將其轉成同一個字符集再進行比較。如果兩個字符集不相容的話,則會丟擲錯誤。MySQL5.0和更新的版本經常會做這樣的癮式轉換。

一些特殊情況

  • 詭異的character_set_database設定 character_set_database設定的預設值和預設資料庫的設定相同。當改變預設資料庫的時候,這個變數也會跟著變。
  • LOAD DATA INFINE 當使用LOAD DATA INFINE的時候,資料庫總是將檔案中的字元按照字符集character_set_database來解析。在MySQL5.0和更新版本中,可以在LOAD DATA INFINE中使用子句CHARACTER SET來設定子集,不過最好不要依賴這個設定。我們發現制定字符集最好的方式是先使用USE指定資料庫,在執行SET NAMES來設定字符集,最後再載入資料。MySQL在載入資料的時候,總是以同樣的字符集處理所有資料,而不管表中的列是否有不同的字符集設定。
  • SELECT INTO OUTFILE MySQL會將SELECT INTO OUTFILE的結果不做任何轉碼地寫入檔案。
  • 嵌入式轉義序列 MySQL會跟據character_set_client的設定來解析轉義序列,即使是字串中包含字首或者COLLATE子句也一樣。這是因為解析器在處理字串的轉義字元時,完全不關心校對規則——對解析器來說,字首並不是一個指令,它只是一個關鍵字而已。
選擇字符集和校對規則

對於校對規則通常需要考慮的一個問題是,是否以大小寫敏感的方式比較字串,或者是以字串編碼的二進位制值來比較大小。他們對應的校對規則的字首分別是_cs、_ci和_bin,根據需要很容易選擇。大小寫敏感和二進位制校對規則的不同之處在於,二進位制校對規則直接使用字元的位元組進行比較,而大小寫敏感的校對規則在多位元組字符集時,有更復雜的比較規則。

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

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

全文索引


全文索引可以支援各種字元內容的搜尋(包括CHAR、VARCHAR和TEXT型別),也支援自然語言搜尋和布林搜尋。標準的MySQL中,只有MyISAM引擎支援全文索引。在MySQL5.6中,InnoDB已經實驗性質的支援全文索引了。MyISAM對全文索引的支援有很多限制,例如表級別鎖對效能的影響、資料檔案的崩潰、崩潰後的恢復等。

MyISAM的全文索引是一類特殊的B-Tree索引,共有兩層。第一層是所有關鍵字,然後對於每一個關鍵字的第二層,包含的是一組相關的"文件指標"。

  • 停用詞列表中的詞都不會被索引。預設的停用詞根據通用英語的使用來設定,可以使用引數ft_stopwrod_file指定一組外部檔案來使用自定義的停用詞。
  • 對於長度大於ft_min_word_len的詞語和長度小於ft_max_word_len的語句,都不會被索引。
自然語言的全文索引

自然語言搜尋引擎將計算每一個文件物件和查詢的相關度。這裡,相關度是基於匹配的關鍵詞個數,以及關鍵詞在文件中出現的次數。在整個索引中出現次數越少的詞語,匹配時的相關度就越高。

全文索引的語法和普通查詢略有不同。可以根據WHERE子句中的MATCH AGAINST來區分查詢是否使用全文索引。

函式MATCH()將返回關鍵詞匹配的相關度,是一個浮點數字。在一個查詢中使用兩次MATCH()函式並不會有額外的消耗,MySQL會自動識別並只進行一次搜尋。如果將MATCH()函式放到ORDER BY子句中,MySQL將會使用檔案排序。

在MATCH()函式中指定的列必須和在全文索引中指定的列完全相同,否則就無法使用全文索引。這是因為全文索引不會記錄關鍵字是那一列的。

布林全文索引

短語搜尋的速度會比較慢。只使用全文索引是無法判斷是否精確匹配短語的,通常還需要查詢原文確定記錄中是否包含完整的短語。由於需要進行回表過濾,所以速度會很慢。

MySQL5.1中全文索引的變化

在MySQL5.1Z中引入了一些和全文索引相關的改進,包括一些效能上的提升和新增外掛式的解析。

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

MySQL全文索引中只有一種判斷相關性的方法:詞頻。索引也不會記錄索引詞在字串中的位置,所以位置也就無法用在相關性上。

資料量大小也是一個問題。MySQL的全文索引只有全部在記憶體中的時候,效能才非常好。如果記憶體無法裝載全部索引,那麼搜尋速度可能會非常慢。當你使用精確短語搜尋時,想要好的效能,資料和索引都需要在記憶體中。相比其他的索引型別,當INSERT、UPDATE和DELETE操作進行時,全文索引的操作代價都很大:

  • 修改一段文字中的100個單詞,需要100次索引操作,而不是一次。
  • 一般來說列長度並不會太影響其他的索引型別,但是如果是全文索引,三個單詞的文字和10000個單詞的文字,效能可能會相差幾個數量級。
  • 全文索引會有更多的碎片,可能需要做更多的OPTIMIZE TABLE操作。

全文索引還會影響查詢優化器的工作。索引選擇、WHERE子句、ORDER BY都有可能不是按照你所預想的方式來工作

  • 如果查詢中使用了MATCH AGAINST子句,而對應列上又有可用的全文索引,那麼MySQL就一定會使用這個全文索引。即使有其他的索引,MySQL不會去比較哪個索引的效能更好。所以,即使這時有更合適的索引可以使用,MySQL仍會置之不理。
  • 全文索引只能用作全文搜尋匹配。任何其他操作,如WHERE條件比較,都必須在MySQL完成全文搜尋返回記錄後才能進行。
  • 全文索引不儲存索引列的實際值。也就不可能用作索引覆蓋掃描。
  • 除了相關性排序,全文索引不能用作其他的排序。如果查詢需要做相關性以外的排序操作,都需要使用檔案排序。

假如有一百萬個文件記錄,在文件的作者author欄位上有一個普通的索引,在文件內容欄位content上有全文索引。現在我們要搜尋作者是123,文件中又包含特定詞語的文件。很多人可能會按照下面的方式來寫查詢語句:

...... WHERE MATCH(content) AGAINST('High Performance MySQL') AND author = 123;

而實際上,這樣做的效率非常低。因為這裡使用了MATCH AGAINST,而且恰好上面有全文索引,所以MySQL優先選擇使用全文索引,即先搜尋所有的文件,查詢是否有包含關鍵詞的文件,然後返回記錄看看作者是否是123。所以這裡也就沒有使用author欄位上的索引。

一個代替方案是將author列包含到全文索引中。可以在author列的值前面附上一個不常見的字首,然後將這個帶字首的值存放到一個單獨的filters列中,並單獨維護該列。

...... WHERE MATCH(content,filters) AGAINST('High Performance MySQL + author_id_123' IN BOOLEAN MODE);

如果author列的選擇性非常高,那麼MySQL能夠根據作者資訊很快地將需要過濾的文件記錄限制在一個很小的範圍內,這個查詢的效率也就非常好。如果author列的選擇性很低,那麼這個替代方案的效率會比前面那個更糟。

使用全文索引的時候,通常會返回大量結果併產生大量隨機I/O,如果和GROUP BY一起使用的話,還需要通過臨時表或者檔案進行排序分組,效能會非常非常糟糕。

全文索引的配置和優化

全文索引的日常維護能夠大大提升效能。"雙B-Tree"的特殊結構、在某些文件中比其他文件要包含多得多的關鍵字,這都使得全文索引比起普通索引有更多的碎片問題。所以需要經常使用OPTIMIZE TABLE來減少碎片。如果應用是I/O密集型的,那麼定期的進行全文索引重建可以讓效能提升很多。

如果希望全文索引能夠高校地工作,還需要保證索引快取足夠大,從而保證所有的全文索引都能快取在記憶體中。通常,可以為全文索引設定單獨的鍵快取,保證不會被其他的快取快取擠出記憶體。

提供一個好的停用詞。預設的停用詞表對常用英語來說可能很不錯,但是如果是其他語言或者某些專業文件就不合適了,例如技術文件。

忽略一些太短的單詞也可以提升全文索引的效率。索引單詞的最小長度可以通過引數ft_min_word_len配置。修改該引數可以過濾更多的單詞,讓查詢速度更快,但是也會降低精確度。

當向一個有全文索引的表中匯入大量資料的時候,最好先通過命令DISABLE KEYS來禁用全文索引,然後在匯入結束後使用ENABLE KEYS來建立全文索引。因為全文索引的更新是一個消耗很大的操作,所以上面的細節會幫你節省大量時間。

如果資料集特別大,則需要對資料進行手動分割槽,然後將資料分佈到不同的節點,再做並行的搜尋。

分散式(XA)事物


儲存引擎的事物特效能夠保證在儲存引擎級別實現ACID,而分散式事務則讓儲存引擎級別的ACID可以擴充套件到資料庫層面,甚至可以擴充套件到多個數據庫之間——這需要通過兩階段提交實現。MySQL5.0和更新版本的資料庫已經開始支援XA事務了。

XA事務中需要一個事務協調器來保證所有的事務參與者都完成了準備工作。如果協調器收到所有的參與者都準備好的訊息,就會告訴所有的事務可以提交了。

MySQL中有兩種XA事務。一方面,MySQL可以參與到外部的分散式事務中;另一方面,還可以通過XA事務來協調儲存引擎和二進位制日誌。

內部XA事物

MySQL本身的外掛式架構導致在其內部需要使用XA事物。MySQL中各個儲存引擎是完全獨立的,彼此不知道對方的存在,所以一個跨儲存引擎的事物就需要一個外部的協調者。如果不使用XA協議,例如跨儲存引擎的事務提交就只是順序地要求每個儲存引擎各自提交。如果在某個儲存提交過程中發生系統崩潰,就會破壞事物的特性。

XA事務為MySQL帶來巨大的效能下降。從MySQL5.0開始,它破壞了MySQL內部的"批量提交",使得MySQL不得不進行多次額外的fsync()呼叫。

外部XA事物

MySQL能夠作為參與者完成一個外部的分散式事務。但它對XA協議支援並不完整。因為通訊延遲和參與者本身可能失敗,所以外部XA事務比內部消耗會更大。如果在廣域網中使用XA事務,通常會因為不可預測的網路效能導致事務失敗。如果有太多不可控因素,則最好避免使用XA事務。任何可能讓事務提交發生延遲的操作代價都很大,因為它影響的不僅僅是自己本身,它還會讓所有參與者都在等待。

查詢快取


MySQL查詢快取儲存查詢返回的完整結果。當查詢命中該快取,MySQL會立刻返回結果,跳過了解析、優化和執行階段。

查詢快取系統會跟蹤查詢中涉及的每個表,如果這些表發生變化,那麼和這個表相關的所有的快取資料都將失效。這種機制效率看起來比較低,因為資料表變化時很有可能對應的查詢結果並沒有更改,但是這種簡單實現代價很小,而這點對於一個非常繁忙的系統來說非常重要。

查詢快取對應用程式是完全透明的。應用程式無須關係MySQL是通過查詢快取返回的結果還是實際執行返回的結果。

隨著現在的通用伺服器越來越強大,查詢快取被發現是一個影響伺服器擴充套件性的因素。它可能成為整個伺服器的資源競爭單點,在多核伺服器上還可能導致伺服器僵死。

MySQL如何判斷快取命中

MySQL判斷快取命中的方法很簡單:快取存放在一個引用表中,通過一個哈系值引用,這個雜湊值包括瞭如下因素,即查詢本身、當前要查詢的資料庫、客戶端協議的版本等一些其他可能會影響返回結果的資訊。

當判斷快取是否命中時,MySQL不會解析、"正視化"或者引數化查詢語句,而是直接使用SQL語句和客戶端傳送過來的其他原始資訊。任何字元上的不同都會導致快取的不命中。

當查詢語句中有一些不確定的資料時,則不會被快取。如果查詢中包含任何使用者自定義函式、儲存函式、使用者變數、臨時表、mysql庫中的系統表,或者任何包含列級別許可權的表,都不會被快取。

在檢查查詢快取的時候,還沒有解析SQL語句,所以MySQL並不知道查詢語句中是否包含這類函式。在檢查查詢快取之前,MySQL只做一件事情,就是通過一個大小寫不敏感的檢檢視看SQL語句是不是以SEL開頭。

如果查詢語句中包含任何的不確定函式,那麼在查詢快取中是不可能找到快取結果的。

開啟查詢快取對讀和寫操作都會帶來額外的消耗:

  • 讀查詢在開始之前必須先檢查是否命中快取。
  • 如果這個讀查詢可以被快取,那麼當完成執行後,MySQL若發現查詢快取中沒有這個查詢,會將其結果存入查詢快取,這會帶來額外的系統消耗。
  • 這對寫操作也會有影響,因為當向某個表寫入資料的時候,MySQL必須將對應表的所有快取都設定失效。

如果查詢快取使用了大量的記憶體,快取失效操作就可能成為一個非常嚴重的問題瓶頸。如果快取中存放了大量的查詢結果,那麼快取失效操作時整個系統都可能會僵死一會兒。因為這個操作是靠一個全域性鎖操作保護的,所有需要做該操作的查詢都要等待這個鎖,而且無論是檢測是否命中快取、還是快取失效檢測都需要等待這個全域性鎖。

查詢快取如何使用記憶體

MySQL用於查詢快取的記憶體被分成一個個的資料快,資料塊是變長的。沒一個數據塊中,儲存了自己的型別、大小和儲存的資料本身,還外加指向前一個和後一個數據塊的指標。資料塊的型別有:儲存查詢結果、儲存查詢、和資料表的對映、儲存查詢文字等等。

當有查詢結果需要快取的時候,MySQL先從大的空間塊中申請一個數據塊用於儲存結果。這個資料塊需要大於引數query_cache_min_res_unit的配置,即使查詢結果遠遠小於此,仍需要至少申請query_cache_min_res_unit空間。因為需要在查詢開始返回結果的時候就分配空間,而此時是無法預知查詢結果到底多大,所以MySQL無法為每一個查詢結果精確分配大小恰好匹配的快取空間。

當需要快取一個查詢結果的時候,它先選擇一個儘可能小的記憶體塊,然後將結果存入其中。如果資料塊全部用完,但仍有剩餘資料需要儲存,那麼MySQL會申請一塊新資料塊——仍然是儘可能小的資料塊——繼續儲存結果資料。當查詢完成時,如果申請的記憶體空間還有剩餘,MySQL會將其釋放,並放入空閒記憶體部分。

分配記憶體塊不是指通過函式malloc()向作業系統申請記憶體,這個操作只在初次建立查詢快取的時候執行一次。分配記憶體塊是指空閒塊列表中找到一個合適的記憶體塊,或者從正在使用的、待淘汰的記憶體塊中回收再使用。也就是說,這裡MySQL自己管理一大塊記憶體,而不依賴作業系統的記憶體管理。

什麼情況下查詢快取能發揮作用

並不是什麼情況下查詢快取都會提高系統性能的。快取和失效都會帶來額外的消耗,所以只有當快取帶來的資源節約大於其本身的資源消耗時才會給系統帶來效能提升。

理論上,可以通過觀察開啟或者關閉查詢快取時候的系統效率來決定是否需要開啟查詢快取。關閉查詢快取時,每個查詢都需要完整的執行,每一次寫操作執行完成後立刻返回;開啟查詢快取時,每次讀請求先檢查快取是否命中,如果命中則立刻返回,否則就完整的執行查詢,每次寫操作則需要檢查查詢快取中是否需要失效的快取,然後在返回。

評估開啟查詢快取是否能夠帶來效能提升卻並不容易。還有一些外部的因素需要考慮,例如查詢快取可以降低查詢執行的時間,但是卻不能減少查詢結果傳輸的網路消耗,如果這個消耗是系統的主要瓶頸,那麼查詢快取的作用也很小。

MySQL在SHOW STATUS中只能提供一個全域性的效能標準,所以很難根據此來判斷查詢快取是否能夠提升效能。

對於那些需要消耗大量資源的查詢通常都是非常適合快取的。不過需要注意的是,涉及的表上UPDATE、DELETE和INSERT操作相比SELECT來說要非常少才行。

一個判斷查詢快取是否有效的直接資料就是命中率,就是使用查詢快取返回結果佔總查詢的比率。Qcache_hits/(Qcache_hits+Com_select)。

  • 查詢語句無法被快取,可能是因為查詢中包含一個不確定的函式,或者查詢結果太大而無法快取。

  • MySQL從未處理這個查詢,所以結果也從不曾被快取過。

  • 之前快取了查詢結果,但是由於查詢快取的記憶體用完了,MySQL需要將某些快取"逐出",或者由於資料表被修改導致快取失效。

  • 查詢快取還沒有完成預熱。也就是說,MySQL還沒有機會將查詢結果都快取起來。

  • 查詢語句之前從未執行過。如果你的應用程式不會重複執行一條查詢語句,那麼即使完成預熱仍然會有很多快取未命中。

  • 快取失效操作太多了 快取碎片、記憶體不足、資料修改都會造成快取失效。如果配置了足夠的快取空間,而且query_cache_min_res_unit設定也合理的話,那麼快取失效應該主要是資料修改導致的。

"命中率"和"INSERTS和SELECT比率"都無法直觀地反應查詢快取的效率。另一個指標:"命中和寫入"比率,即Qcache_hits和Qcache_inserts的比值。根據經驗來看,這個比值大於3:1的通常查詢快取是有效的,不過這個比率最好能夠達到10:1。

通常可以通過觀察查詢快取記憶體的實際使用情況,來確定是否需要縮小或者擴大查詢快取。如果查詢快取空間長時間都有剩餘,那麼建議縮小;如果經常由於空間不足而導致查詢快取失效,那麼則需要增大查詢快取。另外還需要和系統的其他快取一起考慮。

最好的判斷查詢快取是否有效的辦法還是通過檢視某類查詢時間消耗是否增大或者減少來判斷。

如何配置和維護查詢快取
  • query_cache_type 是否開啟查詢快取。可以設定成OFF、ON或DEMAND。DEMAND表示只有在查詢語句中明確寫明SQL_CACHE的語句才放入查詢快取。
  • query_cache_size 查詢快取使用的總記憶體空間,單位是位元組。這個值必須是1024的整數倍。
  • query_cache_min_res_unit 在查詢快取中分配記憶體塊時的最小單位。
  • query_cache_limit MySQL能夠快取的最大查詢結果。
  • query_cache_wlock_invalidate如果某個資料表被其他的連線鎖住,是否仍然從查詢快取中返回結果。

減少碎片 提高查詢快取的使用率

InnoDB和查詢快取

事務是否可以訪問查詢快取取決於當前事務ID,以及對應的資料表上是否有鎖。每一個InnoDB表的記憶體資料字典都儲存了一個事物ID號,如果當前事務ID小於該事務ID,則無法訪問查詢快取。

如果表上有任何的鎖,那麼對這個表的任何查詢語句都是無法被快取的。

  • 所有大於該表計數器的事務才可以使用查詢快取。
  • 該表的計數器並不是直接更新為對該表進行加鎖的事務ID,而是被更新成一個系統事務ID。
通用查詢快取優化
  • 用多個小表代替一個大表對查詢快取有好處。
  • 批量寫入時只需要做一次快取失效,所以相比單條寫入效率更好。
  • 因為快取空間太大,在過期操作的時候可能會導致伺服器僵死。
  • 無法在資料庫或者表級別控制查詢快取,但是可以通過SQL_CACHE和SQL_NO_CACHE來控制某個SELECT語句是否需要進行快取。
  • <