上一篇文章《萬字總結:學習MySQL優化原理,這一篇就夠了!》文末給你們留有兩個開放的問題:html
有很是多的程序員在分享時都會拋出這樣一個觀點:儘量不要使用存儲過程,存儲過程很是不容易維護,也會增長使用成本,應該把業務邏輯放到客戶端。既然客戶端都能幹這些事,那爲何還要存儲過程?前端
JOIN自己也挺方便的,直接查詢就行了,爲何還須要視圖呢?mysql
本文會試着回答這兩個問題,但願能給你一些參考。git
如今能夠思考一個問題,若是數據量很是大的狀況下,您根據業務選擇了合適的字段,精心設計了表和索引,還仔細檢查了全部的SQL,並確認已經沒什麼問題,但性能仍然不能知足您的要求,該怎麼辦?還有其它優化策略嗎?答案是確定的。接下來繼續和您討論一些經常使用的MySQL高級特性以及其背後的工做原理。程序員
分區表github
合理地使用索引能夠極大提高MySQL的查詢性能,但若是單表數據量達到必定的程度,索引就沒法起做用,由於在數據量超大的狀況下,除非覆蓋索引,因回表查詢會產生大量的隨機I/O,數據庫的響應時間可能會達到不可接受的程度。並且索引維護(磁盤空間、I/O操做)的代價也會很是大。算法
所以,當單表數據量達到必定程度時(在MySQL4.x時代,MyISAM存儲引擎業內公認的性能拐點是500W行,MySQL5.x時代的性能拐點則爲1KW - 2KW行級別,具體需根據實際狀況測試),爲了提高性能,最爲經常使用的方法就是分表。sql
分表的策略能夠是垂直拆分(好比:不一樣訂單狀態的訂單拆分到不一樣的表),也能夠是水平拆分(好比:按月將訂單拆分到不一樣表)。但總的來講,分表能夠看做是從業務角度來解決大數據量問題,它在必定程度上能夠提高性能,但也大大提高了編碼的複雜度,有過這種經歷的同窗可能深有體會。數據庫
在業務層分表大大增長了編碼的複雜程度,並且處理數據庫的相關代碼會大量散落在應用各處,維護困難。那是否能夠將分表的邏輯抽象出來,統一處理,這樣業務層就不用關心底層是否分表,只須要專一在業務便可。答案固然是確定的,目前有很是多的數據庫中間件均可以屏蔽分表後的細節,讓業務層像查詢單表同樣查詢分表後的數據。若是再將抽象的邏輯下移到數據庫的服務層,就是咱們今天要講的分區表。編程
分區能夠看做是從技術層面解決大數據問題的有效方法,簡單理解能夠認爲是MySQL底層幫咱們實現分表,分區表是一個獨立的邏輯表,底層由多個物理子表組成。存儲引擎管理分區的各個底層表和管理普通表同樣(全部底層表必須使用相同的存儲引擎),分區表的索引也是在各個底層表上各自加上一個徹底相同的索引。從存儲引擎的角度來看,底層表和普通表沒有任何不一樣,存儲引擎也無須知道。在執行查詢時,優化器會根據分區的定義過濾那些沒有咱們須要數據的分區,這樣查詢就無需掃描全部分區,只須要查找包含須要數據的分區就能夠了。
爲了更好地理解分區表,咱們從一個示例入手:一張訂單表,數據量大概有10TB,如何設計才能使性能達到最優?
首先能夠確定的是,由於數據量巨大,確定不能走全表掃描。使用索引的話,你會發現數據並非按照想要的方式彙集,並且會產生大量的碎片,最終會致使一個查詢產生成千上萬的隨機I/O,應用隨之僵死。因此須要選擇一些更粗粒度而且消耗更少的方式來檢索數據。好比先根據索引找到一大塊數據,而後再在這塊數據上順序掃描。
這正是分區要作的事情,理解分區時還能夠將其看成索引的最初形態,以代價很是小的方式定位到須要的數據在哪一片「區域」,在這片「區域」中,你能夠順序掃描,能夠建索引,還能夠將數據都緩存在內存中。由於分區無須額外的數據結構記錄每一個分區有哪些數據,因此其代價很是低。只須要一個簡單的表達式就能夠表達每一個分區存放的是什麼數據。
對錶分區,能夠在建立表時,使用以下語句:
分區子句中可使用各類函數,但表達式的返回值必須是一個肯定的整數,且不能是一個常數。MySQL還支持一些其它分區,好比鍵值、哈希、列表分區,但在生產環境中不多見到。在MySQL5.5之後可使用RANGE COLUMNS類型分區,這樣即便是基於時間分區,也無需再將其轉化成一個整數。
下面簡單看看分區表上的各類操做邏輯:
SELECT:當查詢一個分區表時,分區層先打開並鎖住全部的底層表,優化器先判斷是否能夠過濾部分分區,而後在調用對應的存儲引擎接口訪問各個分區的數據。
INSERT:當插入一條記錄時,分區層先打開並鎖住全部的底層表,而後肯定哪一個分區接收這條記錄,再將記錄寫入對應的底層表,DELETE操做與其相似。
UPDATE:當更新一條數據時,分區層先打開並鎖住全部的底層表,而後肯定數據對應的分區,而後取出數據並更新,再判斷更新後的數據應該存放到哪一個分區,最後對底層表進行寫入操做,並對原數據所在的底層表進行刪除操做。
有些操做是支持條件過濾的。例如,當刪除一條記錄時,MySQL須要先找到這條記錄,若是WHERE條件剛好和分區表達式匹配,就能夠將全部不包含這條記錄的分區都過濾掉,這對UPDATE語句一樣有效。若是是INSERT操做,自己就只命中一個分區,其它分區都會被過濾。
雖然每一個操做都會 「先打開並鎖住全部的底層表」,但這並非說分區表在處理過程當中是鎖住全表的。若是存儲引擎可以本身實現行級鎖,例如InnoDB,則會在分區層釋放對應表鎖。這個加鎖和解鎖的操做過程與普通InnoDB上的查詢相似。
在使用分區表時,爲了保證大數據量的可擴展性,通常有兩個策略:
策略一:全量掃描數據,不用索引。即只要可以根據WHERE條件將須要查詢的數據限制在少數分區中,效率是不錯的。
策略二:索引數據,分離熱點。若是數據有明顯的「熱點」,並且除了這部分數據,其它數據不多被訪問到,那麼能夠將這部分熱點數據單獨存放在一個分區中,讓這個分區的數據可以有機會都緩存在內存中。這樣查詢就能夠只訪問一個很小的分區表,可以使用索引,也可以有效地利用緩存。
分區表的優勢是優化器能夠根據分區函數來過濾一些分區,但很重要的一點是要在WHERE條件中帶入分區列,有時即便看似多餘的也要帶上,這樣就可讓優化器可以過濾掉無須訪問的分區,若是沒有這些條件,MySQL就須要讓對應的存儲引擎訪問這個表的全部分區,若是表很是大的話,就可能會很是慢。
上面兩個分區策略基於兩個很是重要的前提:查詢都可以過濾掉不少額外的分區、分區自己並不會帶來不少額外的代價。而這兩個前提在某些場景下是有問題的,好比:
1. NULL值會使分區過濾無效
假設按照PARTITION BY RANGE YEAR(order_date)分區,那麼全部order_date爲NULL或者非法值時,記錄都會被存放到第一個分區。因此WHERE order_date BETWEEN '2017-05-01' AND ‘2017-05-31’,這個查詢會檢查兩個分區,而不是咱們認爲的2017年這個分區(會額外的檢查第一個分區),是由於YEAR()在接收非法值時會返回NULL。若是第一個分區的數據量很是大,並且使用全表掃描的策略時,代價會很是大。爲了解決這個問題,咱們能夠建立一個無用的分區,好比:PARTITION p_null values less than (0)。若是插入的數據都是有效的話,第一個分區就是空的。
在MySQL5.5之後就不須要這個技巧了,由於能夠直接使用列自己而不是基於列的函數進行分區:PARTITION BY RANGE COLUMNS(order_date)。直接使用這個語法可避免這個問題。
2. 分區列和索引列不匹配
當分區列和索引列不匹配時,可能會致使查詢沒法進行分區過濾,除非每一個查詢條件中都包含分區列。假設在列a上定義了索引,而在列b上進行分區。由於每一個分區都有其獨立的索引,因此在掃描列b上的索引就須要掃描每個分區內對應的索引,固然這種速度不會太慢,可是可以跳過不匹配的分區確定會更好。這個問題看起來很容易避免,但須要注意一種狀況就是,關聯查詢。若是分區表是關聯順序的第2張表,而且關聯使用的索引與分區條件並不匹配,那麼關聯時對第一張表中符合條件的每一行都須要訪問並搜索第二張表的全部分區(關聯查詢原理,可點擊連接查看第一篇文章)
3. 選擇分區的成本可能很高
分區有不少種類型,不一樣類型的分區實現方式也不一樣,因此它們的性能也不盡相同,尤爲是範圍分區,在確認這一行屬於哪一個分區時會掃描全部的分區定義,這樣的線性掃描效率並不高,因此隨着分區數的增加,成本會愈來愈高。特別是在批量插入數據時,因爲每條記錄在插入前,都須要確認其屬於哪個分區,若是分區數太大,會形成插入性能的急劇降低。所以有必要限制分區數量,但也不用太過擔憂,對於大多數系統,100個左右的分區是沒有問題的。
4. 打開並鎖住全部底層表的成本在某些時候會很高
前面說過,打開並鎖住全部底層表並不會對性能有太大的影響,但在某些狀況下,好比只須要查詢主鍵,那麼鎖住的成本相對於主鍵的查詢來講,成本就略高。
5. 維護分區的成本可能會很高
新增和刪除分區的速度都很快,可是修改分區會形成數據的複製,這與ALTER TABLE的原理相似,須要先建立一個歷史分區,而後將數據複製到其中,最後刪除原分區。所以,設計數據庫時,考慮業務的增加須要,合理的建立分區表是一個很是好的習慣。在MySQL5.6之後的版本可使用ALTER TABLE EXCHAGE PARTITION語句來修改分區,其性能會有很大提高。
分區表還有一些其餘限制,好比全部的底層表必須使用相同的存儲引擎,某些存儲引擎也不支持分區。分區通常應用於一臺服務器上,但一臺服務器的物理資源老是有限的,當數據達到這個極限時,即便分區,性能也可能會很低,因此這個時候分庫是必須的。但無論是分區、分庫仍是分表,它們的思想都是同樣的,你們能夠好好體會下。
視圖
對於一些關聯表的複雜查詢,使用視圖有時候會大大簡化問題,所以在許多場合下均可以看到視圖的身影,但視圖真如咱們所想那樣簡單嗎?它和直接使用JOIN的SQL語句有何區別?視圖背後的原理又瞭解多少?
視圖自己是一個虛擬表,不存聽任何數據,查詢視圖的數據集由其餘表生成。MySQL底層經過兩種算法來實現視圖:臨時表算法(TEMPTABLE)和合並算法(MERGE)。所謂臨時表算法就是將SELECT語句的結果存放到臨時表中,當須要訪問視圖的時候,直接訪問這個臨時表便可。而合併算法則是重寫包含視圖的查詢,將視圖定義的SQL直接包含進查詢SQL中。經過兩個簡單的示例來體會兩個算法的差別,建立以下視圖:
現要從未支付訂單中查詢購買者爲csc的訂單,可使用以下查詢:
使用臨時表來模擬視圖:
使用合併算法將視圖定義的SQL合併進查詢SQL後的樣子:
MySQL能夠嵌套定義視圖,即在一個視圖上在定義另外一個視圖,能夠在EXPLAN EXTENDED以後使用SHOW WARNINGS來查看使用視圖的查詢重寫後的結果。若是採用臨時表算法實現的視圖,EXPLAIN中會顯示爲派生表(DERIVED),注意EXPLAIN時須要實際執行併產生臨時表,因此有可能會很慢。
明顯地,臨時表上沒有任何索引,並且優化器也很難優化臨時表上的查詢,所以,若有可能,儘可能使用合併算法會有更好的性能。那麼問題來了:合併算法(相似於直接查詢)有更好的性能,爲何還要使用視圖?
首先視圖能夠簡化應用上層的操做,讓應用更專一於其所關心的數據。其次,視圖可以對敏感數據提供安全保護,好比:對不一樣的用戶定義不一樣的視圖,可使敏感數據不出如今不該該看到這些數據的用戶視圖上;也可使用視圖實現基於列的權限控制,而不須要真正的在數據庫中建立列權限。再者,視圖能夠方便系統運維,好比:在重構Schema的時候使用視圖,使得在修改視圖底層表結構的時候,應用代碼還能夠繼續運行不報錯。
基於此,使用視圖其實更多的是基於業務或者維護成本上的考慮,其自己並不會對性能提高有多大做用(注意:此處只是基於MySQL考慮,其餘關係性數據庫中視圖可能會有更好的性能,好比Oracle和MS SQL Server都支持物化視圖,它們都比MySQL視圖有更好的性能)。並且使用臨時表算法實現的視圖,在某些時候性能可能會很是糟糕,好比:
現要統計每日的收入與支出,有相似於上面的收入表,可使用以下SQL:
這個查詢中,MySQL先執行視圖的SQL,生成臨時表,而後再將sale_per_day表和臨時表進行關聯。這裏WHERE字句中的BETWEEN條件並不能下推到視圖中,於是視圖在建立時,會將全部的數據放到臨時表中,而不是一個月數據,而且這個臨時表也不會有索引。
固然這個示例中的臨時表數據不會太大,畢竟日期的數量不會太多,但仍然要考慮生成臨時表的性能(若是costs表數據過大,GROUP BY有可能會比較慢)。並且本示例中索引也不是問題,經過上一篇咱們知道,若是MySQL將臨時表做爲關聯順序中的第一張表,仍然可使用sale_per_day中的索引。但若是是對兩個視圖作關聯的話,優化器就沒有任何索引可使用,這時就須要嚴格測試應用的性能是否知足需求。
咱們不多會在實際業務場景中去更新視圖,所以印象中,視圖是不能更新的。但實際上,在某些狀況下,視圖是能夠更新的。可更新視圖是指經過更新這個視圖來更新視圖涉及的相關表,只要指定了合適的條件,就能夠更新、刪除甚至是向視圖中插入數據。經過上文的瞭解,不難推斷出:更新視圖的實質就是更新視圖關聯的表,將建立視圖的WHERE子句轉化爲UPDATE語句的WHERE子句,只有使用合併算法的視圖才能更新,而且更新的列必須來自同一個表中。回顧上文建立視圖的SQL語句,其中有一句:WITH CHECK OPTION,其做用就是表示經過視圖更新的行,都必須符合視圖自己的WHERE條件定義,不能更新視圖定義列之外的列,不然就會拋出check option failed錯誤。
視圖還有一個容易形成誤解的地方:「對於一些簡單的查詢,視圖會使用合併算法,而對於一些比較複雜的查詢,視圖就會使用臨時表算法。」但實際上,視圖的實現算法是視圖自己的屬性決定的,跟做用在視圖上的SQL沒有任何關係。那何時視圖採用臨時表算法,何時採用合併算法呢?通常來講,只要原表記錄和視圖中的記錄沒法創建一一映射的關係時,MySQL都將使用臨時表算法來實現視圖。好比建立視圖的SQL中包含GROUP BY、DISTINCT、UNION、聚合函數、子查詢的時候,視圖都將採用臨時表算法(這些規則在之後的版本中,可能會發生改變,具體請參考官方手冊)。
相比於其它關係型數據庫的視圖,MySQL的視圖在功能上會弱不少,好比Oracle和MS SQL Server都支持物化視圖。物化視圖是指將視圖結果數據存放在一個能夠查詢的表中,並按期從原始表中刷新數據到這張表中,這張表和普通物理表同樣,能夠建立索引、主鍵約束等等,性能相比於臨時表會有質的提高。但遺憾的是MySQL目前並不支持物化視圖,固然MySQL也不支持在視圖中建立索引。
存儲過程與觸發器
回到第二個問題,有很是多的人在分享時都會拋出這樣一個觀點:儘量不要使用存儲過程,存儲過程很是不容易維護,也會增長使用成本,應該把業務邏輯放到客戶端。既然客戶端都能幹這些事,那爲何還要存儲過程?
若是有深刻了解過存儲過程,就會發現存儲過程並無你們描述的那麼不堪。我曾經經歷過一些重度使用存儲過程的產品,依賴到什麼程度呢?就這麼說吧,上層的應用基本上只處理交互與動效的邏輯,全部的業務邏輯,甚至是參數的校驗均在存儲過程當中實現。曾經有出現過一個超大的存儲過程,其文件大小達到驚人的80K,可想而知,其業務邏輯有多麼複雜。在大多數人眼中,這樣的技術架構簡直有點不可理喻,但實際上這款產品很是成功。
其成功的緣由在必定程度上得益於存儲過程的優勢,因爲業務層代碼沒有任何侵入業務的代碼,在不改變前端展現效果的同時,能夠很是快速的修復BUG、開發新功能。因爲這款產品須要部署在客戶的私有環境上,快速響應客戶的需求就變得尤其重要,正是得益於這種架構,能夠在客戶出現問題或者提出新需求時,快速響應,極端狀況下,咱們能夠在1小時內修復客戶遇到的問題。正是這種快速響應機制,讓咱們得到大量的客戶。
固然存儲過程還有其餘的優勢,好比,能夠很是方便的加密存儲過程代碼,而不用擔憂應用部署到私有環境形成源代碼泄露、能夠像調試其餘應用程序同樣調試存儲過程、能夠設定存儲過程的使用權限來保證數據安全等等。一切都很是美好,但咱們的產品是基於MS SQL Server實現的,其能夠經過T-SQL很是方便的實現複雜的業務邏輯。你能夠把T-SQL看作是一門編程語言,其包含SQL的全部功能,還具有流程控制、批處理、定時任務等能力,你甚至能夠用其來解析XML數據。關於T-SQL的更多信息能夠參考MSDN,主流的關係型數據庫目前只有MS SQL Server支持T-SQL,所以,MySQL並不具有上文描述的一些能力,好比,MySQL的存儲過程調試很是不方便(固然能夠經過付費軟件來得到很好的支持)。
除此以外,MySQL存儲過程還有一些其餘的限制:
優化器沒法評估存儲過程的執行成本。
每一個鏈接都有獨立的存儲過程執行計劃緩存,若是有多個鏈接須要調用同一個存儲過程,將會浪費緩存空間來緩存相同的執行計劃。
所以,在MySQL中使用存儲過程並非一個太好的策略,特別是在一些大數據、高併發的場景下,將複雜的邏輯交給上層應用實現,能夠很是方便的擴展已有資源以便得到更高的計算能力。並且對於熟悉的編程語言,其可讀性會比存儲過程更好一些,也更加靈活。不過,在某些場景下,若是存儲過程比其餘實現會快不少,而且是一些較小的操做,能夠適當考慮使用存儲過程。
和存儲過程相似的,還有觸發器,觸發器可讓你在執行INSERT、UPDATE和DELETE時,執行一些特定的操做。在MySQL中能夠選擇在SQL執行以前觸發仍是在SQL執行後觸發。觸發器通常用於實現一些強制的限制,這些限制若是在應用程序中實現會讓業務代碼變得很是複雜,並且它也能夠減小客戶端與服務器之間的通訊。MySQL觸發器的實現很是簡單,因此功能很是有限,若是你在其餘數據庫產品中已經重度依賴觸發器,那麼在使用MySQL觸發器時候須要注意,由於MySQL觸發器的表現和預想的不一致。
首先對一張表的每個事件,最多隻能定義一個觸發器,並且它只支持「基於行的觸發」,也就是觸發器始終是針對一條記錄的,而不是針對整個SQL語句。若是是批量更新的話,效率可能會很低。其次,觸發器能夠掩蓋服務器本質工做,一個簡單的SQL語句背後,由於觸發器,可能包含了不少看不見的工做。再者,觸發器出現問題時很難排查。最後,觸發器並不必定能保證原子性,好比MyISAM引擎下觸發器執行失敗了,也不能回滾。在InnoDB表上的觸發器是在同一個事務中執行完成的,因此她們的執行是原子的,原操做和觸發器操做會同時失敗或者成功。
雖然觸發器有這麼多限制,但它仍有適用的場景,好比,當你須要記錄MySQL數據的變動日誌,這時觸發器就很是方便了。
外鍵約束
目前在大多數互聯網項目,特別是在大數據的場景下,已經不建議使用外鍵了,主要是考慮到外鍵的使用成本:外鍵一般要求每次修改數據時都要在另一張表中執行一次查找操做。在InnoDB存儲引擎中會強制外鍵使用索引,但在大數據的狀況下,仍然不能忽略外鍵檢查帶來的開銷,特別是當外鍵的選擇性很低時,會致使一個很是大且選擇性低的索引。
若是向子表中插入一條記錄,外鍵約束會讓InnoDB檢查對應的父表的記錄,也就須要對父表對應記錄進行加鎖操做,來確保這條記錄不會在這個事務完成之時就被刪除了。這會致使額外的鎖等待,甚至會致使一些死鎖。
高併發場景下,數據庫很容易成爲性能瓶頸,天然而然的就但願數據庫能夠水平擴展,這時就須要把數據的一致性控制放到應用層,也就是讓應用服務器能夠承擔壓力,這種狀況下,數據庫層面就不能使用外鍵。
所以,當不用過多考慮數據庫的性能問題時,好比一些內部項目或傳統行業項目(其使用人數有限,並且數據量通常不會太大),使用外鍵是一個不錯的選擇,畢竟想要確保相關表始終有一致的數據,使用外鍵要比在應用程序中檢查一致性方便簡單許多,此外,外鍵在相關數據的刪除和更新操做上也會比在應用中要高效。
綁定變量
可能你們看到「綁定變量」這個詞時,會有一點陌生,換個說法可能會熟悉一些:PreparedStatement。綁定變量的SQL,使用問號標記能夠接收參數的位置,當真正須要執行具體查詢的時候,則使用具體的數值代替這些問號,好比:
爲何要使用綁定變量?衆所周知的緣由是能夠預先編譯,減小SQL注入的風險,除了這些呢?
當建立一個綁定變量SQL時,客戶端向服務器發送了一個SQL語句原型,服務器收到這個SQL語句的框架後,解析並存儲這個SQL語句的部分執行計劃,返回給客戶端一個SQL語句處理句柄,今後之後,客戶端經過向服務器發送各個問號的取值和這個句柄來執行一個具體查詢,這樣就能夠更高效地執行大量重複語句。緣由以下:
服務器只須要解析一次SQL語句。
服務器某些優化器的優化工做也只須要作一次,由於MySQL會緩存部分執行計劃。
通訊中僅僅發送的是參數,而不是整個語句,網絡開銷也會更小,並且以二進制發送參數和句柄要比發送ASCII文本的效率更高。
須要注意的是,MySQL並非總能緩存執行計劃,若是某些執行計劃須要根據參入的參數來計算時,MySQL就沒法緩存這部分執行計劃。
使用綁定變量的最大陷阱是:你知道其原理,但不知道它是如何實現的。有時候,很難解釋以下3種綁定變量類型之間的區別:
客戶端模擬的綁定變量:客戶端的驅動程序接收一個帶參數的SQL,再將參數的值帶入其中,最後將完整的查詢發送到服務器。
服務器綁定變量:客戶端使用特殊的二進制協議將帶參數的SQL語句發送到服務器端,而後使用二進制協議將具體的參數值發送給服務器並執行。
SQL接口的綁定變量:客戶端先發送一個帶參數的SQL語句到服務器端,這相似於使用prepared的SQL語句,而後發送設置的參數,最後在發送execute指令來執行SQL,全部這些都是用普通的文本傳輸協議。
好比某些不支持預編譯的JDBC驅動,在調用connection.prepareStatement(sql)時,並不會把SQL語句發送給數據庫作預處理,而是等到調用executeQuery方法時才把整個語句發送到服務器,這種方式就相似於第1種狀況。所以,在程序中使用綁定變量時,理解你使用的驅動經過哪一種方式來實現就顯得頗有必要。延伸開來講,對於本身使用的框架、開源工具,不該僅僅停留在會使用這個層面,有時間能夠深刻了解其原理和實現,否則有可能被騙了都不知道哦。
用戶自定義函數
MySQL自己內置了很是多的函數,好比SUM、Count、AVG等等,可實際應用中,咱們經常須要更多。大多數狀況下,更強大的功能都是在應用層面實現,但實際上MySQL也提供了機會讓咱們能夠去擴展MySQL函數,這就是用戶自定義函數(user-defined function),也稱爲:UDF。須要注意UDF與存儲過程和經過SQL建立函數的區別,存儲過程只能使用SQL來編寫,而UDF沒有這個限制,可使用支持C語言調用約定的任何編程語言來實現。
UDF必須事先編譯好並動態連接到服務器上,這種平臺相關性使得UDF在不少方面都很強大,UDF速度很是快,並且能夠訪問大量操做系統功能,還可使用大量庫函數。若是須要一個MySQL不支持的統計聚合函數,而且沒法使用存儲過程來實現,並且還想不一樣的語言均可以調用,那麼UDF是不錯的選擇,至少不須要每種語言都來實現相同的邏輯。
所謂能力越大,責任也就越大,UDF中的一個錯誤可能直接讓服務器崩潰,甚至擾亂服務器的內存和數據,所以,使用時須要注意其潛在的風險。在MySQL版本升級時也須要注意,由於你可能須要從新編譯或者修改這些UDF,以便讓它們能在新版本中工做。
這裏有一個簡單的示例來展現如何建立UDF:將結果集轉化爲JSON,具體的代碼請參考:lib_mysqludf_json。
(連接 https://github.com/mysqludf/lib_mysqludf_json)
其大體的實現流程:使用C語言實現邏輯 -> 編譯成.so文件 -> 建立函數 -> 使用函數。UDF在實際工做中可能不多使用,但做爲開發者的咱們,瞭解這麼一款強大的工具,在解決棘手問題時,也讓咱們有了更多的選擇。
字符集
關於字符集大多數人的第一印象可能就是:數據庫字符集儘可能使用UTF8,由於UTF8字符集是目前最適合於實現多種不一樣字符集之間的轉換的字符集,能夠最大程度上避免亂碼問題,也能夠方便之後的數據遷移。But why?
字符集是指一種從二進制編碼到某類字符符號的映射,能夠參考如何使用一個字節來表示英文字母。校對規則是指一組用於某個字符集的排序規則,即採用何種規則對某類字符進行排序。MySQL每一類編碼字符都有其對應的字符集和校對規則。MySQL對各類字符集的支持都很是完善,但同時也帶來一些複雜性,某些場景下甚至會有一些性能犧牲。
一種字符集可能對應多種校對規則,且都有一個默認校對規則,那在MySQL中是如何使用字符集的?在MySQL中能夠經過兩種方式設置字符集:建立對象時設置默認值、客戶端與服務器通訊時顯式設置。
MySQL採用「階梯」式的方式來設定字符集默認值,每一個數據庫,每張表都有本身的默認值,它們逐層繼承,最終最靠底層的默認設置將影響你建立的對象。好比,建立數據庫時,將根據服務器上的character_set_server來設置數據庫的默認字符集。一樣的道理,根據database的字符集來指定庫中全部表的字符集,無論是對數據庫,仍是表和列,只有當它們沒有顯式指定字符集時,默認字符集纔會起做用。
當客戶端與服務器通訊時,它們可使用不一樣的字符集,這時候服務器將進行必要的轉換工做。當客戶端向服務器發送請求時,數據以character_set_client設置的字符集進行編碼;而當服務器收到客戶端的SQL或者數據時,會按照character_set_connection設置的字符集進行轉換;當服務器將要進行增刪改查等操做前會再次將數據轉換成character_set_database(數據庫採用的字符集,沒有單獨配置即便用默認配置,具體參考上文),最後當服務器返回數據或者錯誤信息時,則將數據按character_set_result設置的字符集進行編碼。服務器端可使用SET CHARACTER SET來改變上面的配置,客戶端也能夠根據對應的API來改變字符集配置。客戶端和服務器端都使用正確的字符集才能避免在通訊中出現問題。
一、如何選擇字符集?
在考慮使用何種字符集時,最主要的衡量因素是存儲的內容,在可以知足存儲內容的前提下,儘可能使用較小的字符集。由於更小的字符集意味着更少空間佔用、以及更高的網絡傳輸效率,也間接提升了系統的性能。若是存儲的內容是英文字符等拉丁語系字符的話,那麼使用默認的latin1字符集徹底沒有問題,若是須要存儲漢字、俄文、阿拉伯語等非拉丁語系字符,則建議使用UTF8字符集。固然不一樣字符在使用UTF8字符集所佔用的空間是不一樣的,好比英文字符在UTF8字符集中只使用一個字節,而一個漢字則佔用3個字節。
除了字符集,校對規則也是咱們須要考慮的問題。對於校對規則,通常來講只須要考慮是否以大小寫敏感的方式比較字符串或者是否用字符串編碼的二進制來比較大小,其對應的校對規則的後綴分別是_cs、_ci和_bin。大小寫敏感和二進制校對規則的不一樣之處在於,二進制校對規則直接使用字符的字節進行比較,而大小寫敏感的校對規則在多字節字符集時,如德語,有更復雜的比較規則。舉個簡單的例子,UTF8字符集對應校對規則有三種:
utf8_bin將字符串中的每個字符用二進制數據存儲,區分大小寫。
utf8_general_ci不區分大小寫,ci爲case insensitive的縮寫,即大小寫不敏感。
utf8_general_cs區分大小寫,cs爲case sensitive的縮寫,即大小寫敏感。
好比,建立一張表,使用UTF8編碼,且大小寫敏感時,可使用以下語句:
所以,在項目中直接使用UTF8字符集是徹底沒有問題的,但須要記住的是不要在一個數據庫中使用多個不一樣的字符集,不一樣字符集之間的不兼容問題很難纏。有時候,看起來一切正常,可是當某個特殊字符出現時,一切操做都會出錯,並且你很難發現錯誤的緣由。
二、字符集對數據庫性能有影響嗎?
某些字符集和校對規則可能會須要多個的CPU操做,可能會消耗更多的內存和存儲空間,這點在前文已經說過。特別是在同一個數據庫中使用不一樣的字符集,形成的影響可能會更大。
不一樣字符集和校對規則之間的轉換可能會帶來額外的系統開銷,好比,數據表sales在buyer字段上有索引,則能夠加速下面的ORDER BY操做:
只有當SQL查詢中排序要求的字符集與服務器數據的字符集相同時,才能使用索引進行排序。你可能會說,這不是廢話嗎?其實否則,MySQL是能夠單獨指定排序時使用的校對規則的,好比:
當使用兩個字符集不一樣的列來關聯兩張表時,MySQL會嘗試轉換其中一個列的字符集。這和在數據列外面封裝一個函數同樣,會讓MySQL沒法使用這個列上的索引。關於MySQL字符集還有一些坑,但在實際應用場景中遇到的字符集問題,其實不是特別的多,因此就此打住。
結語
MySQL還有一些其它高級特性,但在大多數場景下咱們不多會使用,所以這裏也沒有討論,但多瞭解一些老是好的,至少在須要時,你知道有這樣一個東西。咱們老是會認爲本身所學的知識就像碎片同樣不成體系,又找不到解決辦法,那你有沒有想過也許是碎片不夠多的緣故?點太少,天然不能鏈接成線,線太少,天然不能結成網。於是,沒有其它辦法,保持好奇心、多學習、多積累,量變總有一天會質變,寫在這兒,與你們共勉吧。
前面我寫的一些文章裏面會有提到過,架構設計是一種平衡的藝術,其實質應該是一種妥協,是對現有資源的一種妥協。有時候咱們會不自覺陷入某一個點,好比爲了追求數據的擴展性,不少人一上來就開始分庫分表,而後把應用搞得很是複雜,到最後表裏尚未裝滿數據,項目就已經死了。因此在資源有限或者將來還不可知的狀況下,儘可能使用數據庫、語言自己的特性來完成相應的工做,是否是會更好一點。解決大數據問題,也不僅是分庫分表,你還應該還能夠想到分區;有些業務即便在分佈式環境下也不必定非要在業務層完成,合理使用存儲過程和觸發器,也許會讓你更輕鬆。
參考資料
Baron Scbwartz 等著;寧海元 周振興等譯;高性能MySQL(第三版); 電子工業出版社, 2013
經做者贊成受權轉載,來源:簡書