最近聽了公司裏的同事作的技術分享,而後以爲對本身仍是挺有幫助的。都是一些平常須要注意的地方,咱們目前在開發過程當中,其實用不到MySQL太深的內容的。只是能適用咱們平常開發的知識就能夠了。因此我將本身的理解和學習總結也寫出來,供你們一塊兒分享。算法
大致分四部分:sql
作數據庫優化通常是由如下幾種方式:數據庫
成本和效果成反比。緩存
服務器硬件安全
加強服務器的硬件方式不一樣的方式:例如增長磁盤配置(SSD,PCRE),增大內存,增長CPU配置等。加強服務器硬件在必定的階段內確實能夠達到不錯的效果,可是並非長久之計,若是不注重下面的三種策略,一味的增長硬件配置,會拔苗助長。服務器
系統及數據庫配置架構
隨着系統硬件的不斷更新迭代,數據庫的配置也是不斷變化的。例如之前的機械硬盤性能並不很好,因此數據庫的配置並無設置過高。當服務器廣泛的都是SSD後數據庫的系統配配置也是能夠隨之變化的。另外隨着業務的變化以及數據量的增加,數據庫的配置也是隨着變化的。可是這部分的配置帶來的效果並不太明顯,和增長服務器硬件相似。併發
數據庫表設計及規劃分佈式
數據庫的表在設計之初就應該考慮好了之後的規劃。否則當發現數據庫產生瓶頸了再去優化,成本會很高。因此也須要開發人員能經過對業務的深入理解來對數據庫作好長遠的規劃。函數
SQL及索引優化
對SQL語句以及索引的優化能夠說是成本最低的了,效果也是很是顯著的。
這四部份內容,總有人以爲SQL及索引優化是最重要的,可是本人以爲最重要的是數據庫表設計以及規劃,若是能根據業務將表設計好了,根本是不須要進行索引優化的。若是數據庫沒有規劃好,再好的DBA給你作SQL優化,效果也是杯水車薪的。
上面這幅圖是MySQL的基本邏輯架構圖,主要分爲四層。
鏈接層
經過MySQL的鏈接地址去訪問MySQL的數據庫,以及對訪問信息的校驗。
服務層
對SQL語句的校驗,以及對SQL的優化和優化策略選擇,最後發送到執行器去執行SQL。還包括MySQL的查詢緩存也在這一層。
引擎層
MySQL是插件式存儲引擎,最終將數據存到硬盤時不一樣的引擎有不一樣的組織方式。上面列出了一些引擎,常見InnoDB,MyISAM等,只要符合MySQL的接口規範,MySQL是支持自定義的引擎。
存儲系統層
這部分主要是數據存儲,將數據存到磁盤,磁盤的IO讀寫等過程。
請使用InnoDB存儲引擎,慎用MyISAM引擎。
上圖是InnoDB引擎和MyISAM引擎的一些區別對比。
ACID事務支持:因爲咱們介紹此次介紹MySQL的時候是以OLTP(on-line transaction processing:聯機事務處理)爲主的,而非OLAP(On-Line Analytical Processing:聯機分析處理),因此事務處理是很重要的,這也就是爲何強烈要求使用InnoDB引擎的一個緣由。
鎖粒度:MyISAM支持的鎖粒度是表級鎖,表級鎖的意思是指當一張數據表被鎖住後,其餘的對這張表的操做(DML)都要等着前一個鎖釋放了才能夠執行。因此當併發量高時用戶體驗是很很差的。而InnoDB引擎的行級鎖,只是對錶的一部分數據進行加鎖,因此能很好的支持併發,下降了對同一張表的操做衝突。
外檢約束:雖然InnoDB支持外鍵,MyISAM不支持外鍵。可是也不建議在平常的使用過程當中用外鍵,由於每次操做外鍵時都要去檢查一下外鍵關聯的數據。
全文索引:InnoDB引擎不支持全文索引,可是MyISAM支持。可是在數據庫中創建全文索引其實並非什麼好的策略,仍是建議若是須要創建全文索引的時候考慮使用搜索引擎工具如:ElasticSearch,Solr等。
崩潰安全:InnoDB支持崩潰安全,MyISAM是不支持的崩潰安全的。
什麼是崩潰安全呢?
舉個例子🌰:當一臺服務器的上的數據庫忽然掛了,或是服務器崩潰了,甚至是忽然斷電了。這個時候若是MySQL使用的是InnoDB引擎,那麼在數據庫恢復後或是從新通電後,會執行崩潰恢復,就是未執行完的事務會繼續執行,該回滾的回滾,該執行完的執行完,能確保數據的一致性。可是若是MySQL是使用的MyISAM引擎,那麼首先MyISAM不支持事務,因此會形成數據的不一致性,並且若是在對錶進行操做時斷電,致使沒有正確的關閉表,還會致使存儲文件的損壞,在恢復通電後對這張表的任何讀寫操做都不能執行了。並且就算手動恢復數據也是比較麻煩的。
在設計表時要遵循幾個基本原則:
第一條基本原則,是爲了防止隨着業務的發展之後若是數據量大到必定程度了須要分表時,拆分帶有這些特性的表時成本是很是大的。
第二條、第三條 、第四條都是說大字段或大文件是不建議存儲到MySQL當中的,由於對這些數據的操做MySQL是有特殊的存儲方式的,性能不好。若是存儲了這些數據後,再有一些排序或者是聚合操做的話會直接在磁盤中創建臨時文件表,普通的字段類型例如varchar類型的,在有聚合操做時是會在內存中進行臨時存儲的。
第五條原則是要求對業務有長遠的規劃,不一樣的業務首先要分表,其次要分庫。雖然MySQL的很強大,可是單節點的能力是有限的。因此企業級的數據庫都是分佈式的,要爲之後業務的增加數據的訪問量增加作好充分的規劃。
例如:使用VARCHAR(5)和VARCHAR(200)存儲'hello'的空間開銷是同樣的,使用更短的列有什麼優點嗎?
雖然存儲開銷是同樣的,可是若是對這個字段進行聚合操做(order by、group by等),這個時候是須要先將臨時數據存儲到內存中的,可是申請內存空間時是按照字段的定義大小來申請的,也就是說VARCHAR(200)申請的內存空間是VARCHAR(5)的40倍。還有一種狀況是,當一個表的數據量很大時,要作數據遷移或是大數據分析時,是須要抽取全表數據的,這個時候讀全表數據是沒法靠申請內存空間來實現的,MySQL是會在磁盤中創建臨時文件表。而且是按照字段定義的大小來佔用磁盤空間的,若是一個200G的硬盤,可是表中的數據是50G,在抽取全表數據時會有可能將磁盤佔滿的。
因此,更大的定義列會消耗更多的內存,在使用內存臨時表進行排序或操做時會根據定義的長度進行內存分配。
在給字段選擇類型時,儘可能遵循【小而簡單】的原則,可是能夠根據能夠讀性等因素適當調整。
例如:在存儲時間字段時,有的人使用int類型(4個字節),有的人使用datetime(8個字節),雖說佔用的空間小了,可是可讀性也變差了。並且就即便是類型選擇的稍微不太合理,這部分也是能夠經過對SQL的優化等操做來減少影響的。
還有就是例如存儲性別的時候,我們使用tinyint,而不使用枚舉類型,由於若是之後又多了一種類型(😏),這種操做是須要進行改表的,成本比用tinyint類型大不少。
單個表的字段數到了必定程度是建議拆表的,可是具體的峯值是根據實際的業務來看的,還有就是一個表的記錄行數也是不建議不少,當到達必定量時再進行聚合操做是性能不好的。當表的數據量很大時增長字段也是須要消耗成本的,須要copy表中數據而後從新建表,這樣才能保證線上的數據在加字段時是熱處理。表物理文件的大小也是根據實際需求來考慮是否拆分的,若是表中只是追加操做,並且查詢操做很不頻繁,呢麼拆表就能夠慢慢考慮。這部份內容不作過多的討論。
上面的圖介紹的是InnoDB的索引結構,分爲兩部分聚簇索引和輔助索引。
聚簇索引也是主鍵索引,InnoDB表都是有主鍵的,就算是沒有給表建立主鍵,MySQL也會默認的建立一個主鍵,聚簇索引每個葉子節點表明的是主鍵的鍵值,最末端指向的是主鍵所在的那行數據記錄。
輔助索引也是非聚簇索引,輔助索引就是平常表中除了主鍵之外的其餘索引,每一個葉子節點都表明的是索引的字段值,最末端指向的是索引值的主鍵。
在建立索引時須要注意,經常使用的有int類型,bigInt類型。首先這些類型是佔用字節數少,而且是有序的。在創建輔助索引時能節省空間,由於每一個輔助索引記錄後面都帶着一個主鍵索引,若是主鍵是uuid或是MD5值一類的,那麼在創建輔助索引後會佔用很大的磁盤空間,而且在按照主鍵去查詢的時候主鍵值是要加載到內存中的,因此綜合考慮仍是int、bigInt更好一些。
例以下圖的例子。
主鍵以外將name字段設置爲索引。索引類型是varchar而且每一個索引記錄後面都跟着一個主鍵值,這個索引實際上是很耗性能的。
相對於InnoDB來講,MyISAM引擎的主鍵也是指向主鍵所在的記錄的,可是輔助索引就不同了,輔助索引最終也是指向數據記錄的。MyISAM引擎的在數據存儲的物理位置上有一個物理位置的編號。而後不管是主鍵仍是輔助索引都是指向這個編號的。
以下圖的例子所示:
表必須有主鍵。
不使用更新頻繁的列。
忌用字符串列作主鍵。
不使用UUID/MD5等生成的隨機數作主鍵。
推薦用獨立於業務的AUTO_INCREMENT列或全局ID生成器作代理主鍵。
表必須有主鍵,即便沒有主鍵InnoDB也會自動生成一個,若是使用頻繁更新的列作主鍵,那主鍵的B+樹不是一個穩定的結構,很耗磁盤開銷,以及主鍵性能大大下降,上面已經說了字符串類型作主鍵會佔用大量磁盤空間。不適用隨機數作主鍵,是爲了防止有磁盤空洞,產生不連續的空間。
目前的MySQL確實是有最左前綴的規則,即a_b_c索引,查詢b和c時不走聯合索引,可是隨着MySQL的不斷髮展,如今又出現了一種叫作「索引下推」的概念,雖然不是表明着b和c使用時就能走索引了,可是看趨勢可能之後會出現這種優化。最左前綴內容就不作過多的介紹了。
首先介紹一下,回表的概念,InnoDB引擎的表是必須有主鍵的,可是當存在輔助索引時,輔助索引在索引記錄中存儲的是主鍵值。當經過二級索引去查詢非輔助索引包含的字段時,是先根據輔助索引查詢到相應的主鍵值,而後再根據主鍵值去查詢到相應的記錄。這個查詢兩次的過程就是回表。若是一個聯合索引由a、b、c三個字段組成,那麼「select b,c from test where a = 100」這個SQL就不須要產生回表的,由於只查詢聯合索引就能獲得想要的結果了。
改善查詢效率
避免排序
數據率重
減慢插入和更新的效率。
索引添加的目的就是爲了改善查詢效率,添加索引時要避免出現using filesort,出現using filesort是指,當查詢操做中包含order by,沒法利用索引完成排序操做時,MySQL優化器不得不選擇相應的排序算法來實現,數據較少時從內存排序,不然從磁盤排序。
舉個例子:
仍是以上面的tb_user_test表爲例,"select b,c from tb_user_test where a=100 and b=200 order by c desc;"這個SQL語句在執行的時候若是tb_user_test沒有idx_a_b_c這個聯合索引那麼執行計劃是這樣的
注意Extra列的值,Using filesort 出現了,這說明MySQL將數據從新排序了。
若是將字段a和b建立了聯合索引後的執行計劃是這樣的
仍是會有Using filesort。
將字段a和b還有c建立了聯合索引後的執行計劃是這樣的
此次沒有Using filesort了,建立索引時注意避免出現重排序問題。
數據慮重是指在使用distinct或者group by的時候也是可使用索引進行優化查詢的。distinct或group by的列建立索引能提示查詢效率。
索引雖然能改善查詢效率,可是代價是犧牲了插入和更新的效率。
單張表索引數量建議不超過5個。
單個索引中的字段建議不超過5個。
字符串適度使用前綴索引。
索引不是越多越好,能不添加的索引儘可能不要添加。
索引的控制只是一些建議,並非強制要求。
不在低區分度的列上創建索引,例如:「性別」。
儘可能避免%前導查詢,如like "%ab"。
儘可能避免負向查詢,如not in /like。
避免全表掃描以及頻繁的回表操做
區分度低的列建立了索引後查詢速度確實提高了,可是當數據量變大後會產生大量的隨機IO和回表查詢。like前綴是不走索引的,索引對負向查詢的支持也很差。
其餘幾點須要注意的是,索引的創建要優先保證高頻查詢需求的效率,低頻需求儘量使用到最左前綴索引。索引也要隨着業務的演進更變化,不是建完索引就完事了。
SQL儘量簡單,線上儘量少使用大SQL,使用簡單小SQL。
儘量少使用存儲過程/觸發器/函數,減小MySQL端的數學運算和邏輯判斷。(不易於擴展)
使用預編譯語句,下降SQL注入機率。
儘可能少用select * ,只取須要的數據列。(可下降磁盤I/O,有機會只走複合索引,緩存使用下降。)
基本原則:where條件比較,字段類型和傳入值必須保證:數字對數字,字符對字符。
經過下面的例子就能夠看出來。
字段:`remark` varchar(50) NOT NULL COMMENT '備註,默認爲空',
MySQL>SELECT id, gift_ code FROM gift Where deal_ id = 640 AND remark=115127; 1 row in set (0.14 sec) MySQL>SELECT id, gift_ code FROM pool gift Where deal_ id = 640 AND remark='115127' ;1 row in set (0.005 sec)
當remark傳入int類型的值後,查詢時間0.14秒,傳入字符類型後只須要0.005秒。
基本原則:不在索引列進行數學運算和函數運算。
索引字段進行數學運算時,不走索引。能夠放到後面對值進行運算。
例如:
經過運行時間就能夠看出效果。
索引字段慎用函數運算,MySQL的優化器對函數運算識別不出來時會直接走全表掃描。
例子以下:
select * from table limt 10000,10;
limit 10000,10; 偏移量越大則越慢。查詢的時候要一步一步遍歷到第10010條記錄,而後取後10條記錄,前面的所有拋棄掉。
select * from table where id>=23424 limit 11;
#10+1(每頁10條)
select * from table where id>23434 limit 11;
分頁方式二
select * from table where id>=(select id from table limit 10000,1) limit 10;
分頁方式三
select * from table where Inner join (select id from table limit 10000,10) using (id);
分頁方式四
先取id:select id from table limit 10000,10; select * from table where id in (123,456,...);
具體示例:
MySQL> select sql_no_ cache * from post limit 10,10;10 row in set (0.01 sec) MySQL> select sql_ no_cache * from post limit 2000,10;10 row in set (0.13 sec) MySQL> select sql_no_cache * from post limit 80000,10;10 rows in set (0.58 sec) MySQL> select sql_no_ cache id from post limit 8000,10;10 rows in set (0.02 sec) MySQL> select sql_no_ cache * from post WHERE id> = 323423 limit 10;10 rows in set (0.01 sec) MySQL> select * from post WHERE id >= ( select sql_ no_ cache id from post limit8000,1 ) limit 10;10 rows in set (0.02 sec)
不一樣的業務使用不一樣的數據庫實例
不一樣的業務表拆分到不一樣的數據庫中,能夠根據不一樣的模塊,不一樣的功能將表拆分到不一樣個數據庫中。邏輯比較清晰,可是也要考慮到具體的狀況,若是有關聯查詢時,兩個表放在裏不一樣的庫中,這樣就拆分的不合理了,因此拆分的時候要對業務作深刻的瞭解。
一個表中的數據拆分到不一樣表中或不一樣的庫中。可是拆分的時候要慎重的考慮好了,要以哪一個鍵做爲惟一標識進行拆分。一但肯定下來最好不要隨意更改。
水平拆分+垂直拆分
(若是對分佈式事務要求不過高的可使用WTable,底層也是作了拆分。聚合操做也比較麻煩,要對每一個庫進行請求,而後再進行聚合操做。)
此次的知識總結的比較粗糙,之後會對每一塊作深刻研究。
文章會同步到個人公衆號上面,歡迎關注。