分區表是一個獨立的邏輯表,底層由多個物理子表構成。實現分區的代碼其實是對一組底層表的句柄對象的封裝。對分區表的請求,都會經過句柄對象轉化成對存儲引擎的接口調用。node
MySQL實現分區表的方式——對底層表的封裝——意味着索引也是按照分區的子表定義的,而沒有全局索引。mysql
MySQL在建立表的時使用PARTITION BY子句定義每一個分區存放的數據。在執行查詢的時候,優化器會根據分區定義過濾那些沒有咱們須要數據的分區,這樣查詢就無須掃描全部分區——只須要查找包含須要數據的分區就能夠了。算法
分區的一個主要目的是將數據按照一個較粗的粒度分在不一樣的表中。sql
使用場景:數據庫
分區表自己也有一些限制:編程
分區表由多個相關的底層表實現,這些底層表也是由句柄對象表示,因此也能夠直接訪問各個分區。存儲引擎管理分區的各個底層表和管理普通表同樣,分區表的索引只是在各個底層表上各自加上一個徹底相同的索引。從存儲引擎的角度來看,底層表和一個普通表沒有任何不一樣,存儲引擎也無需知道這是一個普通表仍是一個分區表的一部分。緩存
分區表上的操做按照下面的操做邏輯進行:安全
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須要打開並鎖住全部的底層表,這是分區表的另外一個開銷。這個操做在分區過濾以前發生,因此沒法經過分區過濾下降此開銷,而且改開銷也和分區類型無關,會影響全部的查詢。
維護分區的成本可能很高
引入分區給查詢優化帶來了一些新的思路。分區最大的優勢就是優化器能夠根據分區函數來過濾一些分區。根據粗粒度索引的優點,經過分區過濾一般可讓查詢掃描更少的數據。
對於訪問分區表來講,很重要的一點是要在WHERE條件中帶入分區列,有時候即便看似多餘的也要帶上,這樣就可讓優化器可以過濾無須訪問的分區。
這個查詢訪問全部的分區。
MySQL只能在使用分區函數的列自己進行比較時才能過濾分區,而不能根據表達式的值去過濾分區,即便這個表達式就是分區函數也不行。
這裏寫的WHERE條件中帶入的是分區列,而不是基於分區列的表達式,因此優化器可以利用這個條件過濾部分分區。一個很重要的原則是:即使在建立分區時可使用表達式,但在查詢時卻只能根據列來過濾分區。
合併表是一種早期的、簡單的分區實現,和分區表相比有一些不一樣的限制,而且缺少優化。分區表嚴格來講是一個邏輯上的概念,用戶沒法訪問底層的各個分區,對用戶來講分區是透明的。
合併表至關於一個容器,裏面包含了多個真實表。能夠在CREATE TABLE中使用一種特別的UNION 語法來指定包含哪些真實表。
最後創建的合併表和前面的各個真實表字段徹底相同,在合併表中有的索引各個真實子表也有,這是建立合併表的前提條件。各個子表在對應列上都有主鍵限制,可是最終的合併表中仍然出現了重複值。
INSERT_METHOD=LAST告訴MySQL,將全部的INSERT語句都發送給最後一個表。指定FIRST或者LAST關鍵字是惟一能夠控制行插入到合併表的哪個子表的方式。
INSERT語句的執行結果能夠在最終的合併表中看到,也能夠在對應的子表中看到:
刪除一個合併表,它的子表不受任何影響,而若是直接刪除其中一個子表則可能會有不一樣的後果,這要視操做系統而定。
在使用CREATE語句建立一個合併表的時候,並不會檢查各個子表的兼容性。若是子表的定義稍有不一樣,那麼MySQL就可能建立出一個後面沒法使用的合併表。
根據合併表的特性,不難發現,在合併表上沒法使用REPLACE語法,沒法使用自增字段。
若是一個查詢訪問合併表,那麼它須要訪問全部子表。這會讓根據鍵查找單行的查詢速度變慢,若是可以只訪問一個對應表,速度確定將更快。限制合併表中的子表數量特別重要,特別是當合並表是某個關聯查詢的一部分的時候,由於這時訪問一個表的記錄數可能會將比較操做傳遞到關聯的其餘表中,這時減小記錄的訪問就是減小整個關聯操做。
合併表的各個子表能夠直接被訪問,它還具備一些MySQL5.5分區所不能提供的特性:
視圖自己是一個虛擬表,不存聽任何數據。在使用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容許經過觸發器、存儲過程、函數的形式來存儲代碼。從MySQL5.1開始,還能夠在定時任務中存放代碼,這個定時任務也被稱爲"事件"。存儲過程和存儲函數都被統稱爲"存儲函數"。
不一樣類型的存儲代碼的主要區別在於其執行的上下文——也就是輸入和輸出。存儲過程和存儲函數均可以接收參數而後返回值,可是觸發器和事件不行。
存儲代碼的優勢:
存儲代碼的缺點:
MySQL的架構自己和優化器的特性使得存儲代碼有一些自然限制,他的性能也必定程度受限於此。
咱們一般會但願存儲程序越小、越簡單越好。但願將更加複雜的處理邏輯交給上層的應用實現,一般這樣會使代碼更易讀、易維護,也會更靈活。
存儲過程要快不少,很大程度由於它無須網絡通訊開銷、解析開銷和優化器開銷等。
觸發器可讓你在執行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注入和攻擊的風險。
對使用綁定變量的SQL,MySQL可以緩存其部分執行計劃,若是某些執行計劃須要根據傳入的參數來計算時,MySQL就沒法緩存這部分的執行計劃。
在準備階段 服務器解析SQL語句,移除不可能的條件,而且重寫子查詢。
在第一次執行的時候
若是可能的話,服務器先簡化嵌套循環的關聯,並將外關聯轉化成內關聯。
在每次SQL語句執行時
服務器作如下事情
MySQL支持了SQL接口的綁定變量。不使用二進制傳輸協議也能夠直接以SQL的方式使用綁定變量。
最主要的用途就是在存儲過程當中使用。在MySQL5.0版本中,就能夠在存儲過程當中使用綁定變量,其語法和前面介紹的SQL接口和綁定變量相似。這意味着,能夠在存儲過程當中構建並執行"動態"的SQL語句,這裏的"動態"是指能夠經過靈活的拼接字符串等參數構建SQL語句。
編寫存儲過程時,SQL接口的綁定變量一般能夠很大程度地幫助咱們調試綁定變量,若是不是在存儲過程當中,SQL接口的綁定變量就不是那麼有用了。由於SQL接口的綁定變量,它既沒有使用二進制傳輸協議,也沒有可以節省帶寬,相反還老是須要增長至少一額外網絡傳輸才能完成一次傳輸。
三種綁定變量的區別:
客戶端的驅動程序接收一個帶參數的SQL,在將指定的值帶入其中,最後將完整的查詢發送到服務器端。
客戶端使用特殊的二進制協議將帶參數的字符串發送到服務器端口,而後使用二進制協議將具體的參數值發送給服務器端並執行。
客戶端先發送一個帶參數的字符串到服務端,這相似於使用PREPARE的SQL語句,而後發送設置參數的SQL,最後使用EXECUTE來執行SQL。全部這些都使用普通的文本傳輸協議。
MySQL支持用戶自定義函數(UDF)。存儲過程只能使用SQL來編寫,而UDF沒有這個限制,你可使用支持C語言調用約定的任何編程語言來實現。
UDF必須事先編譯好並動態連接到服務器上,這種平臺相關性使得UDF在不少方面都很強大。UDF速度很是快,並且能夠訪問大量操做系統的功能,還可使用大量函數庫。
能力越大,責任越大。因此在UDF中的一個錯誤極可能會讓服務器直接崩潰,甚至擾亂服務器的內存或者數據,另外,全部C語言具備的潛在風險,UDF也都有。
字符集是指一種從二進制編碼到某類字符符號的映射,能夠參考如何使用一個字節表來表示英文字母。"校對"是指一組用於某個字符集的排序規則。MySQL4.1和以後的版本中,每一類編碼字符都有其對應的字符集和校對規則。
#####MySQL如何使用字符集
每種字符集均可能有多種校對規則,而且都有一個默認的校對規則。每一個校對規則都是針對某個特定的字符集的,和其餘的字符集沒有關係。校對規則和字符集老是一塊兒使用。
只有基於字符的值才真正的"有"字符集的概念。對於其餘類型的值,字符集只是一個設置,指定用哪種字符集來作比較或者其餘操做。
MySQL的設置能夠分爲兩類:建立對象時的默認值、在服務器和客戶端通訊時的設置。
建立對象時的默認設置
MySQL服務器有默認的字符集和校對規則,每一個數據庫也有本身的默認值,每一個表也有本身的默認值。這是一個逐層繼承的默認設置,最終最靠底層的默認設置將影響你建立的對象。
真正存放數據的是列,更高"階梯"的設置只是指定默認值。一個表的默認字符集設置沒法影響存儲在這個表中某個列的值。只有當建立列而沒爲列指定字符集的時候,若是沒有指定字符集,表的默認字符集纔有做用。
服務器和客戶端通訊時的設置
當服務器和客戶端通訊的時候,它們可能使用不一樣的字符集。服務器端將進行必要的翻譯轉換工做:
根據須要,可使用SET NAMES或者SET CHARACTER SET語句來改變上面的設置。不過在服務器上使用這個命令只能改變服務器端的設置。客戶端程序和客戶端的API也須要使用正確的字符集才能避免在通訊時出現問題。
MySQL如何比較兩個字符串的大小
若是比較的兩個字符串的字符集不一樣,MySQL會先將其轉成同一個字符集再進行比較。若是兩個字符集不兼容的話,則會拋出錯誤。MySQL5.0和更新的版本常常會作這樣的癮式轉換。
一些特殊狀況
對於校對規則一般須要考慮的一個問題是,是否以大小寫敏感的方式比較字符串,或者是以字符串編碼的二進制值來比較大小。他們對應的校對規則的前綴分別是_cs、_ci和_bin,根據須要很容易選擇。大小寫敏感和二進制校對規則的不一樣之處在於,二進制校對規則直接使用字符的字節進行比較,而大小寫敏感的校對規則在多字節字符集時,有更復雜的比較規則。
某些字符集和校對規則可能會須要更多的CPU操做,可能會消耗更多的內存和存儲空間,甚至還會影響索引的正常使用。
全文索引能夠支持各類字符內容的搜索(包括CHAR、VARCHAR和TEXT類型),也支持天然語言搜索和布爾搜索。標準的MySQL中,只有MyISAM引擎支持全文索引。在MySQL5.6中,InnoDB已經實驗性質的支持全文索引了。MyISAM對全文索引的支持有不少限制,例如表級別鎖對性能的影響、數據文件的崩潰、崩潰後的恢復等。
MyISAM的全文索引是一類特殊的B-Tree索引,共有兩層。第一層是全部關鍵字,而後對於每個關鍵字的第二層,包含的是一組相關的"文檔指針"。
天然語言搜索引擎將計算每個文檔對象和查詢的相關度。這裏,相關度是基於匹配的關鍵詞個數,以及關鍵詞在文檔中出現的次數。在整個索引中出現次數越少的詞語,匹配時的相關度就越高。
全文索引的語法和普通查詢略有不一樣。能夠根據WHERE子句中的MATCH AGAINST來區分查詢是否使用全文索引。
函數MATCH()將返回關鍵詞匹配的相關度,是一個浮點數字。在一個查詢中使用兩次MATCH()函數並不會有額外的消耗,MySQL會自動識別並只進行一次搜索。若是將MATCH()函數放到ORDER BY子句中,MySQL將會使用文件排序。
在MATCH()函數中指定的列必須和在全文索引中指定的列徹底相同,不然就沒法使用全文索引。這是由於全文索引不會記錄關鍵字是那一列的。
短語搜索的速度會比較慢。只使用全文索引是沒法判斷是否精確匹配短語的,一般還須要查詢原文肯定記錄中是否包含完整的短語。因爲須要進行回表過濾,因此速度會很慢。
在MySQL5.1Z中引入了一些和全文索引相關的改進,包括一些性能上的提高和新增插件式的解析。
MySQL全文索引中只有一種判斷相關性的方法:詞頻。索引也不會記錄索引詞在字符串中的位置,因此位置也就沒法用在相關性上。
數據量大小也是一個問題。MySQL的全文索引只有所有在內存中的時候,性能才很是好。若是內存沒法裝載所有索引,那麼搜索速度可能會很是慢。當你使用精確短語搜索時,想要好的性能,數據和索引都須要在內存中。相比其餘的索引類型,當INSERT、UPDATE和DELETE操做進行時,全文索引的操做代價都很大:
全文索引還會影響查詢優化器的工做。索引選擇、WHERE子句、ORDER BY都有可能不是按照你所預想的方式來工做
假若有一百萬個文檔記錄,在文檔的做者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來創建全文索引。由於全文索引的更新是一個消耗很大的操做,因此上面的細節會幫你節省大量時間。
若是數據集特別大,則須要對數據進行手動分區,而後將數據分佈到不一樣的節點,再作並行的搜索。
存儲引擎的事物特性可以保證在存儲引擎級別實現ACID,而分佈式事務則讓存儲引擎級別的ACID能夠擴展到數據庫層面,甚至能夠擴展到多個數據庫之間——這須要經過兩階段提交實現。MySQL5.0和更新版本的數據庫已經開始支持XA事務了。
XA事務中須要一個事務協調器來保證全部的事務參與者都完成了準備工做。若是協調器收到全部的參與者都準備好的消息,就會告訴全部的事務能夠提交了。
MySQL中有兩種XA事務。一方面,MySQL能夠參與到外部的分佈式事務中;另外一方面,還能夠經過XA事務來協調存儲引擎和二進制日誌。
MySQL自己的插件式架構致使在其內部須要使用XA事物。MySQL中各個存儲引擎是徹底獨立的,彼此不知道對方的存在,因此一個跨存儲引擎的事物就須要一個外部的協調者。若是不使用XA協議,例如跨存儲引擎的事務提交就只是順序地要求每一個存儲引擎各自提交。若是在某個存儲提交過程當中發生系統崩潰,就會破壞事物的特性。
XA事務爲MySQL帶來巨大的性能降低。從MySQL5.0開始,它破壞了MySQL內部的"批量提交",使得MySQL不得不進行屢次額外的fsync()調用。
MySQL可以做爲參與者完成一個外部的分佈式事務。但它對XA協議支持並不完整。由於通訊延遲和參與者自己可能失敗,因此外部XA事務比內部消耗會更大。若是在廣域網中使用XA事務,一般會由於不可預測的網絡性能致使事務失敗。若是有太多不可控因素,則最好避免使用XA事務。任何可能讓事務提交發生延遲的操做代價都很大,由於它影響的不只僅是本身自己,它還會讓全部參與者都在等待。
MySQL查詢緩存保存查詢返回的完整結果。當查詢命中該緩存,MySQL會馬上返回結果,跳過了解析、優化和執行階段。
查詢緩存系統會跟蹤查詢中涉及的每一個表,若是這些表發生變化,那麼和這個表相關的全部的緩存數據都將失效。這種機制效率看起來比較低,由於數據表變化時頗有可能對應的查詢結果並無更改,可是這種簡單實現代價很小,而這點對於一個很是繁忙的系統來講很是重要。
查詢緩存對應用程序是徹底透明的。應用程序無須關係MySQL是經過查詢緩存返回的結果仍是實際執行返回的結果。
隨着如今的通用服務器愈來愈強大,查詢緩存被發現是一個影響服務器擴展性的因素。它可能成爲整個服務器的資源競爭單點,在多核服務器上還可能致使服務器僵死。
MySQL判斷緩存命中的方法很簡單:緩存存放在一個引用表中,經過一個哈系值引用,這個哈希值包括了以下因素,即查詢自己、當前要查詢的數據庫、客戶端協議的版本等一些其餘可能會影響返回結果的信息。
當判斷緩存是否命中時,MySQL不會解析、"正視化"或者參數化查詢語句,而是直接使用SQL語句和客戶端發送過來的其餘原始信息。任何字符上的不一樣都會致使緩存的不命中。
當查詢語句中有一些不肯定的數據時,則不會被緩存。若是查詢中包含任何用戶自定義函數、存儲函數、用戶變量、臨時表、mysql庫中的系統表,或者任何包含列級別權限的表,都不會被緩存。
在檢查查詢緩存的時候,尚未解析SQL語句,因此MySQL並不知道查詢語句中是否包含這類函數。在檢查查詢緩存以前,MySQL只作一件事情,就是經過一個大小寫不敏感的檢查看看SQL語句是否是以SEL開頭。
若是查詢語句中包含任何的不肯定函數,那麼在查詢緩存中是不可能找到緩存結果的。
打開查詢緩存對讀和寫操做都會帶來額外的消耗:
若是查詢緩存使用了大量的內存,緩存失效操做就可能成爲一個很是嚴重的問題瓶頸。若是緩存中存放了大量的查詢結果,那麼緩存失效操做時整個系統均可能會僵死一下子。由於這個操做是靠一個全局鎖操做保護的,全部須要作該操做的查詢都要等待這個鎖,並且不管是檢測是否命中緩存、仍是緩存失效檢測都須要等待這個全局鎖。
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。
一般能夠經過觀察查詢緩存內存的實際使用狀況,來肯定是否須要縮小或者擴大查詢緩存。若是查詢緩存空間長時間都有剩餘,那麼建議縮小;若是常常因爲空間不足而致使查詢緩存失效,那麼則須要增大查詢緩存。另外還須要和系統的其餘緩存一塊兒考慮。
最好的判斷查詢緩存是否有效的辦法仍是經過查看某類查詢時間消耗是否增大或者減小來判斷。
減小碎片 提升查詢緩存的使用率
事務是否能夠訪問查詢緩存取決於當前事務ID,以及對應的數據表上是否有鎖。每個InnoDB表的內存數據字典都保存了一個事物ID號,若是當前事務ID小於該事務ID,則沒法訪問查詢緩存。
若是表上有任何的鎖,那麼對這個表的任何查詢語句都是沒法被緩存的。
客戶端的緩存能夠很大程度上幫你分擔MySQL服務器的壓力。