高性能mysql讀書筆記(四) Mysql高級特性

分區表

對用戶來講,分區表是一個獨立的邏輯表,可是底層由多個物理子表組成。實現分區表的代碼其實是對一組底層表的句柄對象的封裝。對分區表的請求,都會經過句柄對象轉化成對存儲引擎的接口調用。node

  • mysql實現分區表的子表----對底層表的封裝----意味着索引也是按照分區的子表定義的,而沒有全局索引。

分區的一個主要目的是將數據按照一個較粗的粒度分在不一樣的表中,這樣作能夠將相關數據存放在一塊兒,另外,若是想一次批量刪除整個分區的數據也會變的很方便。mysql

下面的場景中,分區能夠起到很是大的做用:算法

  • 表很是大以致於沒法所有都放到內存中,或者只在表的最後部分有熱點數據,其餘均是歷史數據。
  • 分區表的數據更容易維護。例如想批量刪除大量數據可使用清除整個分區的方式。另外,還能夠對一個獨立分區進行優化,檢查,修復等操做。
  • 分區表的數據能夠分佈在不一樣的物理設備上,從而高效的利用多個硬件設備。
  • 可使用分區表來避免某些特殊的瓶頸,例如InnoDB的單個索引的互斥訪問,ext3文件系統的inode鎖競爭等。
  • 若是須要,還能夠備份和恢復獨立的分區,這在很是大的數據集的場景下效果很是好。

分區表自己也有一些限制:sql

  • 一個表最多隻能有1024個分區
  • 在mysql5.1中,分區表達式必須是整數,或者是返回整數的表達式,在mysql5.5 中,某些場景中能夠直接使用列來進行分區。
  • 若是分區字段中有主鍵或者惟一索引的列,那麼全部主鍵列和惟一索引列都必須包含進來。
  • 分區表中沒法使用外鍵約束。

分區表的原理

分區表上的操做按照下面的操做邏輯進行:數據庫

  • select查詢 當查詢一個分區表的時候,分區層先打開並鎖住全部的底層表,優化器先判斷是否能夠過濾部分分區,而後再調用對應的存儲引擎接口訪問各個分區的數據。
  • insert操做 當寫入一條記錄時,分區層先打開並鎖住全部的底層表,而後肯定哪一個分區接收這條記錄,再將記錄寫入對應底層表。
  • delete操做 當刪除一條記錄時,分區層先打開並鎖住全部的底層表,而後肯定數據對應的分區,最後對相應底層表進行刪除操做。
  • update操做 當更新一條記錄時,分區層先打開並鎖住全部的底層表,mysql先肯定須要更新的記錄在哪一個分區,而後取出數據並更新,再判斷更新後的數據應該放在哪一個分區,最後對底層表進行寫入操做,並對原數據所在的底層表進行刪除操做。

雖然每一個操做都會「先打開並鎖住全部的底層表」,但這並非說分區表在處理過程當中是鎖住全表的,若是存儲引擎可以本身實現行級鎖,如InnoDB,則會在分區層釋放對應表鎖。這個加鎖和解鎖過程與普通InnoDB上的查詢相似。緩存

分區表的類型

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分區子句中可使用各類函數,但有一個要求,表達式返回的值要是一個肯定的整數,且不能是一個常數。服務器

mysql還支持鍵值,哈希和列表分區,這其中有些還支持子分區,不過咱們在生產環境中不多見到。網絡

咱們還看到的一些其餘的分區技術包括:mysql優化

  • 根據鍵值進行分區,來減小InnoDB的互斥競爭。
  • 使用數據模函數來進行分區,而後將數據輪詢放入不一樣的分區。
  • 假設表有一個自增的主鍵列id,但願根據時間將最近的熱點數據集中存放,那麼必須將時間戳包含在主鍵當中才行,而這和主鍵自己的意義相矛盾,這種狀況下可使用這樣的分區表達式來實現相同的目的:Hash(id div 100000) ,這將爲100萬數據簡歷一個分區,這樣一方面實現了當初的分區目的,另外一方面比起使用時間範圍分區還避免了一個問題,就是當超過必定閥值時,若是使用時間範圍分區就必須新增分區。

如何使用分區表

假設咱們但願從一個很是大的表中查詢出一段時間的記錄,而這個表中包含了不少年的歷史數據,數據是按照時間排序的,例如但願查詢最近幾個月的數據,這大約有10億條記錄。 首先很確定:由於數據量巨大,確定不能在每次查詢的時候都掃描全表。考慮到索引在空間和維護上的消耗,也不但願使用索引。即便真的使用索引,你會發現數據並非按照想要的房市彙集的,並且會有大量的碎片產生,最終會致使一個查詢產生成千上萬的隨機I/O,應用程序也隨之僵死。 這時候有兩條路可選:讓全部的查詢都只在數據表上作順序掃描或者將數據表和索引所有都緩存在內存裏。

這裏須要再陳述一遍:在數據量大的時候,B-Tree索引就沒法起做用了。除非是索引覆蓋查詢,不然數據庫服務器須要根據索引掃描的結果回表,查詢全部符合條件的記錄,若是數據量巨大,浙江產生大量隨機I/O,隨之,數據庫的響應時間將達到不可接受的程度。 另外,索引維護的代價也很是高。

這正是分區要作的事情。理解分區時還能夠將其看成索引的最初形態,以代價很是小的房市定位到須要的數據在哪一片「區域」,在這篇區域中,你能夠作順序掃描,還能夠建索引,駭客淳將數據都緩存到內存,等等。 由於分區無需額外的數據結構記錄每一個分區有哪些數據--分區不須要精肯定位每條數據的位置,也就無需額外的數據結構--因此其代價很是低,只須要一個簡單的表達式就能夠表達每一個分區存放的是什麼數據。

爲了保證大數據量的可拓展性,通常有下面兩個策略:

  • 全量掃描數據,不須要任何索引 可使用簡單的分區方式存放表,不要任何索引,根據分區的規則大體定位須要的數據位置。只要可以使用where條件,將須要的數據限制在少數分區中,則效率是很高的。固然,也須要作一些簡單的運算保證查詢的響應時間可以知足需求。使用該策略假設不用將數據徹底放入到內存中,同事還假設須要的數據全都在磁盤上,由於內存相對很小,數據很快會被擠出內存,因此緩存起不了任何做用。這個策略適用於以正常的方式訪問大量數據的時候。
  • 索引數據,並分離熱點 若是數據有明顯的熱點,並且除了這部分數據,其餘數據不多被訪問到,那麼能夠將這部分熱點數據單獨放到一個分區中,讓這個分區的數據可以有機會都緩存在內存中。這樣查詢就能夠值訪問一個很小的分區表,可以使用索引,也可以有效的使用緩存。

什麼狀況下會出問題

上面咱們介紹的兩個分區策略都是基於兩個很是重要的假設:查詢可以過濾掉不少額外的分區,分區自己並不會帶來不少額外的代價。而事實證實,這兩個假設在某些場景下會有問題。

  • NULL值會使分區過濾無效 關於分區表,一個容易讓人誤解的地方就是分區的表達式的值能夠是null,第一個分區是一個特殊分區。假設按照partition by range year(order_date)分區,那麼全部order__date爲null或者是一個非法值的時候,記錄都會被存放到第一個分區。 若是第一個分區很是大,特別是當使用全量掃描數據,不要任何索引的策略時,代價會很是大。並且掃描兩個分區來查找列也不是咱們使用分區表的初衷。 爲了不這種狀況,能夠建立一個「無用」的第一個分區,例如上面的例子中可使用partition p_nulls values less than (0) 來建立第一個分區。若是插入表中的數據都是有效的,那麼第一個分區就是空的,這樣即便須要檢測第一個分區,代價也會很是小。在mysql5.5中就不須要這個優化技巧了,由於能夠直接使用列自己而不是基於列的函數進行分區。
  • 分區列和索引列不匹配 若是定義的索引列和分區列不匹配,會致使查詢沒法進行分區過濾。
  • 選擇分區的成本可能很高
  • 打開並鎖住全部底層表的成本可能很高 當查詢訪問分區表的時候,mysql須要打開並鎖住全部的底層表,這是分區表的另外一個開銷。這個操做在分區過濾以前發生,因此沒法經過分區過濾下降此開銷,而且該開銷也和分區類型無關,會影響全部的查詢。
  • 維護分區的成本可能很高

查詢優化

分區最大的有點是優化器能夠根據分區函數來過濾一些分區,根據粗粒度索引的優點,經過分區過濾一般能夠查詢掃描更少的數據。 對於分區表來講,很重要的一點是要在where條件中帶入分區列,有時候即便看似 多餘的也要帶上,這樣就可讓優化器可以過濾掉無需訪問的分區。若是沒有這些條件,mysql就須要讓對應的存儲引擎訪問這個表的全部分區,若是表很是大的話,就可能會很是慢。 mysql只能在使用分區函數的列自己進行比較時才能過濾分區,而不能根據表達式的值去過濾分區,即便這個表達式就是分區函數也不行。

視圖

mysql5.0版本以後開始引入視圖。視圖自己是一個虛擬表,不存聽任何數據,在使用sql語句訪問視圖的時候,它返回的數據是mysql從其餘表中生成的。 視圖和表是在同一個命名空間,mysql在不少地方對於視圖和表是一樣對待的。不過視圖和表也有不一樣,例如不能對視圖建立觸發器,也不能使用drop table命令刪除視圖。

mysql可使用兩種辦法處理視圖:合併算法和臨時表算法。若是可能儘量使用合併算法。

  • 使用MERGE策略,MySQL會先將輸入的查詢語句和視圖的聲明語句進行合併,而後執行合併後的語句並返回。可是若是輸入的查詢語句中不容許包含一些聚合函數如: MIN, MAX, SUM, COUNT, AVG, etc., or DISTINCT, GROUP BY, HAVING, LIMIT, UNION, UNION ALL, subquery。一樣若是視圖聲明沒有指向任何數據表,也是不容許的。若是出現以上任意狀況, MySQL默認會使用UNDEFINED策略。
  • 使用TEMPTABLE策略,MySQL先基於視圖的聲明建立一張temporary table,當輸入查詢語句時會直接查詢這張temporary table。因爲須要建立temporary table來存儲視圖的結果集, TEMPTABLE的效率要比MERGE策略低,另外使用temporary table策略的視圖是沒法更新的。

若是你想肯定mysql究竟是使用合併算法仍是臨時表算法,能夠explain一條針對視圖的簡單查詢:

explain select * from <view_name>
+----+-------------+------------+------------+--------+---------------+---------+---------+-------------------+------+----------+-------------+
| id | select_type | table      | partitions | type   | possible_keys | key     | key_len | ref               | rows | filtered | Extra       |
+----+-------------+------------+------------+--------+---------------+---------+---------+-------------------+------+----------+-------------+
| 1  | PRIMARY     | <derived2> | <null>     | ALL    | <null>        | <null>  | <null>  | <null>            | 34   | 100.0    | <null>      |
| 2  | DERIVED     | blog       | <null>     | index  | user_id       | user_id | 4       | <null>            | 34   | 100.0    | Using index |
| 2  | DERIVED     | u          | <null>     | eq_ref | PRIMARY       | PRIMARY | 4       | test.blog.user_id | 1    | 100.0    | Using where |
+----+-------------+------------+------------+--------+---------------+---------+---------+-------------------+------+----------+-------------+
複製代碼

這裏的select_type爲derived,說明該視圖是採用的臨時表算法實現的。

可更新視圖

可更新視圖是指能夠經過更新這個視圖來更新視圖涉及的相關表。只要指定了合適的條件,就能夠更新,刪除甚至向視圖中寫入數據。 若是視圖定義中包含了group by, union, 聚合函數,以及其餘一些特殊狀況,就不能被更新了。 更新視圖的查詢也能夠是一個關聯語句,可是有一個限制,被更新的列必須來自同一個表中,另外,全部使用臨時表算法實現的視圖都沒法被更新。

視圖對性能的影響

可使用視圖實現基於列的權限控制,卻不須要真正的在系統中建立列權限,所以沒有額外的開銷。

若是打算使用視圖來提高性能,須要作比較詳細的測試。即便合併算法實現的視圖也會有額外的開銷,並且視圖的性能很難預測。 在mysql優化器中,視圖的代碼執行路徑也徹底不一樣,這部分代碼測試還不透全面,可能會有一些音層缺陷和問題。因此咱們認爲視圖還不是那麼成熟。

視圖的限制

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

mysql視圖實現上也有一些讓人煩惱的地方,例如,mysql並不會保存視圖定義的原始sql語句,因此打算經過執行 show create view 後在簡單的修改其結果的方式來從新定義視圖,可能會大失所望。show create view出來的視圖建立語句將以一種不友好的內部格式呈現,充滿了各類轉義符和引號,沒有代碼格式,沒有註釋,也沒有縮進。

外鍵約束

使用外鍵是有成本的,好比外鍵一般都要求每次在修改數據時都要在另一張表中多執行一次查找操做。 雖然InnoDB強制外鍵使用索引,但仍是沒法消除這種約束檢查的開銷。若是外鍵列的選擇性很低,則會致使一個很是大且選擇性低的索引.

不過在某些場景下,外鍵會提高一些性能。若是想確保兩個相關表始終有一致的數據,那麼使用外鍵比在應用程序中檢查一致性的性能要高得多,此外,外鍵在相關數據的刪除和更新上,也比在應用中維護要更高效,不過外鍵維護操做是逐行進行的,因此這樣的更新會比批量刪除和更新要慢些。

外鍵約束使得查詢須要額外訪問一些別的表,這也意味着須要額外的鎖。若是向子表中插入一條記錄,外鍵約束會讓InnoDB檢查對應的父表記錄,也就須要對父表對應記錄進行加鎖操做,來確保這條記錄不會在這個事務完成之時就被刪除了。這回致使額外的鎖等待,甚至會致使一些死鎖。

若是隻是使用外鍵作約束,那一般在應用程序裏實現該約束會更好。外鍵會帶來很大的額外消耗。

在mysql內部存儲代碼

mysql容許經過觸發器,存儲過程,函數的形式來存儲代碼,從mysql5.1開始,還能夠在定時任務中存放代碼,這個定時任務也被稱爲「事件」。存儲過程和存儲函數都被統稱爲「存儲程序」。

在mysql中使用存儲代碼的優勢:

  • 它在服務器內部執行,離數據最近,在服務器上執行還能夠節省帶寬和網絡延遲
  • 這是一種代碼重用。能夠方便的統一業務規則,保證某些行爲老是一致,因此也能夠爲應用提供必定的安全性。
  • 它能夠簡化代碼的維護和版本更新
  • 它能夠幫助提高安全,好比提供更細粒度的權限控制。
  • 服務器能夠緩存存儲過程的執行計劃,這對於須要反覆調用的過程,會大大下降消耗
  • 由於是在服務器部署的,因此備份,維護均可以在服務器端完成。因此存儲程序的維護工做會很簡單。
  • 它能夠在應用開發和數據庫開發人員之間更好的分工。不過最好是由數據庫專家來開發存儲過程,由於不是每一個應用開發人員都能寫出高效的sql查詢。

存儲代碼也有以下缺點:

  • mysql自己沒有提供好用的開發和調試工具,因此編寫msyql的存儲代碼比其餘的數據要更難
  • 較之應用程序的代碼,存儲代碼效率要稍微差些。例如存儲代碼中可使用的函數很是有限,因此使用存儲代碼很難編寫複雜的字符串爲好功能,也很難實現太複雜的邏輯
  • 存儲代碼可能會給應用程序代碼的部署帶來額外的複雜性。本來只須要部署應用代碼和庫表結構,如今還要額外的部署mysql內部的存儲代碼
  • 由於存儲程序都部署在服務器,因此可能有安全隱患。若是將非標準的加密功能放在了存儲程序中,那麼若數據庫被攻破,數據也就泄漏了。可是若將加密函數放在應用程序代碼中,那麼攻擊者必須同時攻破程序和數據庫才能得到數據。
  • 存儲過程會給數據庫增長額外的壓力,而數據庫服務器的拓展性香餅應用服務器要差不少
  • mysql並無什麼選項能夠控制 存儲程序的資源消耗,因此在存儲過程當中的一個小錯誤,可能直接把服務器拖死
  • 存儲代碼在mysql中的實現也有不少限制--執行計劃是鏈接級別的,遊標的物化和臨時表相同,在mysql5.5版本以前,異常處理也很是困難,等等
  • 調試mysql的存儲過程是一件很困難的事情。
  • 它和基於語句的二進制日誌複製合做的並很差。

最後,存儲代碼是一種幫助應用隱藏複雜性,使得應用開發更簡單的方法。不過,它的性能可能更低,並且會給MySQL的複製等增長潛在的風險。因此當你打算使用存儲過程的時候,須要問問本身,到底但願程序邏輯在哪兒實現:是數據庫中仍是應用代碼中?這兩種作法均可以,也都很流行。只是當你編寫存儲代碼的時候,你須要明白這是將程序邏輯放在數據庫中。

綁定變量

當建立一個綁定變量sql時,客戶端向服務器發送了一個sql語句的原型。服務器收到這個sql語句框架後,解析並存儲這個sql語句的部分執行計劃,返回給客戶端一個sql語句處理句柄。之後每次執行這類查詢 ,客戶端都指定使用這個句柄。 綁定變量sql使用問號標記能夠接收參數的位置,當真正須要執行具體查詢的時候,則使用具體值代替這些問號。

  • 在服務器只解析一次sql語句
  • 在服務器端某些優化器的工做只須要執行一次,由於它會緩存一部分的執行計劃。
  • 以二進制的方式只發送參數和句柄,比起每次都發送ascii碼文本效率更高,一個二進制的日期字段只需3個字節,但若是是ascii則須要10個字節。
  • 僅僅是參數,而不是整個查詢語句,須要發送到服務器端,因此網絡開銷少
  • mysql在存儲參數的時候,直接將其存放到緩存中,再也不須要在內存中屢次複製。

綁定變量相對也更安全,無需在應用程序中處理轉義,一則更簡單了,二則也大大減小了sql注入和攻擊風險。

綁定變量的限制

  • 綁定變量是會話級別的,因此鏈接之間不能共用綁定變量句柄。
  • 並非全部的時候使用綁定變量都能得到更好的性能。若是隻是執行一次sql,那麼使用綁定變量無疑比直接執行多了一次額外的準備階段消耗,並且還須要一次額外的網絡消耗。
  • 若是老是忘記釋放綁定變量資源,則在服務器端很容易發生資源「泄漏」。綁定變量sql總數的限制是一個全侷限制,因此在某一個地方的錯誤可能會對全部其的線程都產生影響。

字符集和校對

字符集是指一種從二進制編碼到某類字符符號的映射,校對是指一組用於某個字符集的排序規則。

mysql如何使用字符集

每種字符集均可能有多種校對規則,而且都有一個默認的校對規則。

查詢緩存

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

查詢緩存系統會跟蹤查詢中涉及的每一個表,若是這些表發生變化,那麼和這個表相關的全部的緩存數據都將失效。

查詢緩存對應用程序是徹底透明的。

mysql如何判斷緩存命中

mysql判斷存換命中的方法很簡單,緩存存放在一個引用表中,經過一個哈希值引用,這個哈希值包括了以下因素:查詢自己,當前要查詢的數據庫,客戶端協議的版本以及一些其餘可能影響返回結果的信息。 當判斷緩存是否命中時,mysql不會解析,正規化或者參數化查詢語句,而是直接使用sql語句和客戶端發送過來的其餘原始信息。任何字符上的不一樣,都會致使緩存的不命中。

查詢語句中有一些不肯定的數據時,則不會被緩存。若是查詢中包含任何用戶自定義函數,存儲函數,用戶變量,臨時表,mysql庫中的系統表,或者任何包含列級別權限的表,都不會被緩存。

查詢緩存對讀寫都會帶來額外的消耗:

  • 讀查詢在開始以前必須先檢查是否命中緩存
  • 若是這個讀查詢能夠被緩存,那麼當完成執行後,mysql若發現查詢緩存中沒有這個查詢,會將其結果存入查詢緩存,這會帶來額外的系統消耗
  • 這對寫操做也會有影響,由於當向某個表寫入數據的時候,mysql必須將對應表的全部存換都設置失效。若是查詢緩存很是大或者碎片不少,這個操做就可能會帶來很大的系統開銷。

若是查詢緩存使用了很大量的內存,緩存失效操做就可能稱爲一個很是嚴重的問題瓶頸。若是緩存中存放了大量的查詢結果,那麼緩存失效操做時整個系統均可能會僵死一下子。

相關文章
相關標籤/搜索