高性能MySQL(呕心沥血整理万字长文)
高性能MySQL
文章目錄
- 高性能MySQL
- 一、 引言
- 二、 架構
- 2.1 談談技術選型依據
- 2.2 一次并不特別成功的技術選型
- 2.3 聊聊MySQL的邏輯架構
- 2.4 小結
- 三、 頂層設計
- 3.1 設計表的坑
- 3.1.1 太多的列
- 3.1.2 太多的關聯
- 3.1.3 過度使用枚舉
- 3.1.4 范式和反范式
- 3.1.5 緩存表、匯總表
- 3.1.6 Alter Table
- 3.1.7 小結
- 3.2 索引設計
- 3.2.1 還是那棵樹
- 3.2.2 三星系統
- 3.2.3 聚簇索引
- 3.2.4 覆蓋索引
- 3.2.5 索引和排序
- 3.2.6 冗余索引和重復索引
- 3.2.7 索引和鎖
- 3.2.8 實戰索引設計
一、 引言
筆者現在所在的項目常常會遇到慢查詢問題,龐大的數據量和沒有及時分庫分表導致現在生產環境一張表動輒上千萬,更有甚者已經上億。。。
在前東家沒有遇到的很多問題都在這個項目碰到了(前東家數據表也就幾十萬的數據量,且最初技術選型就將所有的輿情文章放到了Elasticsearch)。同時也讓筆者反思自己在數據庫優化這塊的短板,下定決心靜心開始拜讀《高性能MySQL》一書,并將其中受益匪淺的內容予以記錄,假以時日能將此文章分享到自己的博客造福他人也是一件幸事。
此文章筆者也不知道什么時候能寫完,但是會不斷優化演進,畢竟學無止境。
宗旨就是寫深寫透,深入淺出,讓我自己回看或是看官們閱讀時,能夠完全理解。
知識點可能會比較零散,因為筆者希望將此作為一本字典供自己和他人查閱。
引言至此,畢。
二、 架構
2.1 談談技術選型依據
說起技術選型其實筆者也很慚愧,畢竟對于架構設計這塊也是略懂皮毛、一知半解,所以不敢講大話,只能說說自己的拙見。
“MySQL并不完美,但是卻足夠靈活”。
這句話不是我說的,而是《高性能MySQL》一書中開篇明義講到的最核心的一句話,這也說明MySQL可能不是當下或未來的最優解,但是它是架構中的萬金油,龐雜非單一的項目中總會有它的用武之地。
在此之前,請允許我給大家普及兩個術語:
這是我們對自身系統的一個分類,你的系統是事務型的還是分析型的。我們常見的傳統關系型數據庫的應用幾乎都是OLTP,這也是MySQL的擅長項。OLAP更多是一些報表系統,比如筆者所在項目用的就是PowerBI,上面運行著大量的復雜的查詢,視圖以及存儲過程,當然現在PowerBI也存在著性能瓶頸,正計劃用大數據組件如Spark、Flink來破局。
所以是否選用MySQL可以從以下維度考量:
第三點確實是一個重要的考量依據,幾乎所有的程序員或多或少都接觸過MySQL,這個也會大幅降低團隊的學習成本,因為你不能要求一個從未接觸過Flink的團隊一個月就交付出高質量的流處理平臺,讀過《人月神話》或者接觸過十個女人一個月生產的甲方爸爸的小伙伴,應該深有體會。
對于更優解,我再啰嗦兩句,這個更優解可能是一個新技術,你從來沒有嘗試在項目中應用的技術(畢竟誰都有第一次)。有一次內部培訓中我問了我的Boss關于新技術選型的問題,我的Boss對于新技術的選型給出了以下建議:
如果同時滿足以上四個條件,那么就可以大膽嘗試了。
2.2 一次并不特別成功的技術選型
這一段和MySQL自身并無太大關系,只是筆者單純想吐個槽而已,不感興趣的同學可以自行跳過。
筆者項目使用的是JPA,而在實際應用過程中發現很多問題:
以上還是一小部分問題,并不是說JPA不好,JPA一直是一個優秀的ORM框架,但是同時也對使用者的要求較高。而產生問題的根本原因就是當初沒有對團隊成員實力有一個合理的評估,亦或是沒有針對性的培訓。
反觀數據庫的技術選型時,你自身或者你的團隊對于被選擇的產品(Oracle、MySQL、PowerBI、PostgreSQL等),不光是看產品本身,還要綜合上你和你的團隊對于這個數據庫產品的了解,才能做出明智的選型。
我的Boss常常在面試部門架構師的時候會問:“有那么多可以實現的技術,你為什么選擇了這個技術?”
有超過90%的人會回答:“因為我只會這個技術”,或者“這個技術是現在最流行的”。
很顯然這個回答并不能讓我的Boss滿意,因為同樣的話是說服不了客戶的。
想象你在和眾多優秀的公司一起BD(競標)一個大項目,我們假設我們只做技術咨詢而非交付,那么下面那一句話更容易打動客戶?
最后第二種說辭打動了客戶,也幫助我的Boss在BD過程中成功擊敗了別的競爭對手。
技術是由需求驅動的,所以滿足需求才是技術的初衷,不可為了求新求異而本末倒置。
就好比客人去一家餐廳想要喝湯,你給一雙筷子告訴他:“用筷子可能同時調動幾十塊肌肉,健身益腦”云云,即使說得天花亂墜,最終還是沒有解決客人的需求。
我在學習微服務架構的時候,講師的一句話也打動了我:“技術沒有好與壞,只有適合與不適合”。
希望我這段篇幅不長的文字能引起大家的共鳴。
2.3 聊聊MySQL的邏輯架構
#mermaid-svg-nDiWSasIpaluuCSf .label{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);fill:#333;color:#333}#mermaid-svg-nDiWSasIpaluuCSf .label text{fill:#333}#mermaid-svg-nDiWSasIpaluuCSf .node rect,#mermaid-svg-nDiWSasIpaluuCSf .node circle,#mermaid-svg-nDiWSasIpaluuCSf .node ellipse,#mermaid-svg-nDiWSasIpaluuCSf .node polygon,#mermaid-svg-nDiWSasIpaluuCSf .node path{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-nDiWSasIpaluuCSf .node .label{text-align:center;fill:#333}#mermaid-svg-nDiWSasIpaluuCSf .node.clickable{cursor:pointer}#mermaid-svg-nDiWSasIpaluuCSf .arrowheadPath{fill:#333}#mermaid-svg-nDiWSasIpaluuCSf .edgePath .path{stroke:#333;stroke-width:1.5px}#mermaid-svg-nDiWSasIpaluuCSf .flowchart-link{stroke:#333;fill:none}#mermaid-svg-nDiWSasIpaluuCSf .edgeLabel{background-color:#e8e8e8;text-align:center}#mermaid-svg-nDiWSasIpaluuCSf .edgeLabel rect{opacity:0.9}#mermaid-svg-nDiWSasIpaluuCSf .edgeLabel span{color:#333}#mermaid-svg-nDiWSasIpaluuCSf .cluster rect{fill:#ffffde;stroke:#aa3;stroke-width:1px}#mermaid-svg-nDiWSasIpaluuCSf .cluster text{fill:#333}#mermaid-svg-nDiWSasIpaluuCSf div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:12px;background:#ffffde;border:1px solid #aa3;border-radius:2px;pointer-events:none;z-index:100}#mermaid-svg-nDiWSasIpaluuCSf .actor{stroke:#ccf;fill:#ECECFF}#mermaid-svg-nDiWSasIpaluuCSf text.actor>tspan{fill:#000;stroke:none}#mermaid-svg-nDiWSasIpaluuCSf .actor-line{stroke:grey}#mermaid-svg-nDiWSasIpaluuCSf .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333}#mermaid-svg-nDiWSasIpaluuCSf .messageLine1{stroke-width:1.5;stroke-dasharray:2, 2;stroke:#333}#mermaid-svg-nDiWSasIpaluuCSf #arrowhead path{fill:#333;stroke:#333}#mermaid-svg-nDiWSasIpaluuCSf .sequenceNumber{fill:#fff}#mermaid-svg-nDiWSasIpaluuCSf #sequencenumber{fill:#333}#mermaid-svg-nDiWSasIpaluuCSf #crosshead path{fill:#333;stroke:#333}#mermaid-svg-nDiWSasIpaluuCSf .messageText{fill:#333;stroke:#333}#mermaid-svg-nDiWSasIpaluuCSf .labelBox{stroke:#ccf;fill:#ECECFF}#mermaid-svg-nDiWSasIpaluuCSf .labelText,#mermaid-svg-nDiWSasIpaluuCSf .labelText>tspan{fill:#000;stroke:none}#mermaid-svg-nDiWSasIpaluuCSf .loopText,#mermaid-svg-nDiWSasIpaluuCSf .loopText>tspan{fill:#000;stroke:none}#mermaid-svg-nDiWSasIpaluuCSf .loopLine{stroke-width:2px;stroke-dasharray:2, 2;stroke:#ccf;fill:#ccf}#mermaid-svg-nDiWSasIpaluuCSf .note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-nDiWSasIpaluuCSf .noteText,#mermaid-svg-nDiWSasIpaluuCSf .noteText>tspan{fill:#000;stroke:none}#mermaid-svg-nDiWSasIpaluuCSf .activation0{fill:#f4f4f4;stroke:#666}#mermaid-svg-nDiWSasIpaluuCSf .activation1{fill:#f4f4f4;stroke:#666}#mermaid-svg-nDiWSasIpaluuCSf .activation2{fill:#f4f4f4;stroke:#666}#mermaid-svg-nDiWSasIpaluuCSf .mermaid-main-font{font-family:"trebuchet ms", verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-nDiWSasIpaluuCSf .section{stroke:none;opacity:0.2}#mermaid-svg-nDiWSasIpaluuCSf .section0{fill:rgba(102,102,255,0.49)}#mermaid-svg-nDiWSasIpaluuCSf .section2{fill:#fff400}#mermaid-svg-nDiWSasIpaluuCSf .section1,#mermaid-svg-nDiWSasIpaluuCSf .section3{fill:#fff;opacity:0.2}#mermaid-svg-nDiWSasIpaluuCSf .sectionTitle0{fill:#333}#mermaid-svg-nDiWSasIpaluuCSf .sectionTitle1{fill:#333}#mermaid-svg-nDiWSasIpaluuCSf .sectionTitle2{fill:#333}#mermaid-svg-nDiWSasIpaluuCSf .sectionTitle3{fill:#333}#mermaid-svg-nDiWSasIpaluuCSf .sectionTitle{text-anchor:start;font-size:11px;text-height:14px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-nDiWSasIpaluuCSf .grid .tick{stroke:#d3d3d3;opacity:0.8;shape-rendering:crispEdges}#mermaid-svg-nDiWSasIpaluuCSf .grid .tick text{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-nDiWSasIpaluuCSf .grid path{stroke-width:0}#mermaid-svg-nDiWSasIpaluuCSf .today{fill:none;stroke:red;stroke-width:2px}#mermaid-svg-nDiWSasIpaluuCSf .task{stroke-width:2}#mermaid-svg-nDiWSasIpaluuCSf .taskText{text-anchor:middle;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-nDiWSasIpaluuCSf .taskText:not([font-size]){font-size:11px}#mermaid-svg-nDiWSasIpaluuCSf .taskTextOutsideRight{fill:#000;text-anchor:start;font-size:11px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-nDiWSasIpaluuCSf .taskTextOutsideLeft{fill:#000;text-anchor:end;font-size:11px}#mermaid-svg-nDiWSasIpaluuCSf .task.clickable{cursor:pointer}#mermaid-svg-nDiWSasIpaluuCSf .taskText.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-nDiWSasIpaluuCSf .taskTextOutsideLeft.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-nDiWSasIpaluuCSf .taskTextOutsideRight.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-nDiWSasIpaluuCSf .taskText0,#mermaid-svg-nDiWSasIpaluuCSf .taskText1,#mermaid-svg-nDiWSasIpaluuCSf .taskText2,#mermaid-svg-nDiWSasIpaluuCSf .taskText3{fill:#fff}#mermaid-svg-nDiWSasIpaluuCSf .task0,#mermaid-svg-nDiWSasIpaluuCSf .task1,#mermaid-svg-nDiWSasIpaluuCSf .task2,#mermaid-svg-nDiWSasIpaluuCSf .task3{fill:#8a90dd;stroke:#534fbc}#mermaid-svg-nDiWSasIpaluuCSf .taskTextOutside0,#mermaid-svg-nDiWSasIpaluuCSf .taskTextOutside2{fill:#000}#mermaid-svg-nDiWSasIpaluuCSf .taskTextOutside1,#mermaid-svg-nDiWSasIpaluuCSf .taskTextOutside3{fill:#000}#mermaid-svg-nDiWSasIpaluuCSf .active0,#mermaid-svg-nDiWSasIpaluuCSf .active1,#mermaid-svg-nDiWSasIpaluuCSf .active2,#mermaid-svg-nDiWSasIpaluuCSf .active3{fill:#bfc7ff;stroke:#534fbc}#mermaid-svg-nDiWSasIpaluuCSf .activeText0,#mermaid-svg-nDiWSasIpaluuCSf .activeText1,#mermaid-svg-nDiWSasIpaluuCSf .activeText2,#mermaid-svg-nDiWSasIpaluuCSf .activeText3{fill:#000 !important}#mermaid-svg-nDiWSasIpaluuCSf .done0,#mermaid-svg-nDiWSasIpaluuCSf .done1,#mermaid-svg-nDiWSasIpaluuCSf .done2,#mermaid-svg-nDiWSasIpaluuCSf .done3{stroke:grey;fill:#d3d3d3;stroke-width:2}#mermaid-svg-nDiWSasIpaluuCSf .doneText0,#mermaid-svg-nDiWSasIpaluuCSf .doneText1,#mermaid-svg-nDiWSasIpaluuCSf .doneText2,#mermaid-svg-nDiWSasIpaluuCSf .doneText3{fill:#000 !important}#mermaid-svg-nDiWSasIpaluuCSf .crit0,#mermaid-svg-nDiWSasIpaluuCSf .crit1,#mermaid-svg-nDiWSasIpaluuCSf .crit2,#mermaid-svg-nDiWSasIpaluuCSf .crit3{stroke:#f88;fill:red;stroke-width:2}#mermaid-svg-nDiWSasIpaluuCSf .activeCrit0,#mermaid-svg-nDiWSasIpaluuCSf .activeCrit1,#mermaid-svg-nDiWSasIpaluuCSf .activeCrit2,#mermaid-svg-nDiWSasIpaluuCSf .activeCrit3{stroke:#f88;fill:#bfc7ff;stroke-width:2}#mermaid-svg-nDiWSasIpaluuCSf .doneCrit0,#mermaid-svg-nDiWSasIpaluuCSf .doneCrit1,#mermaid-svg-nDiWSasIpaluuCSf .doneCrit2,#mermaid-svg-nDiWSasIpaluuCSf .doneCrit3{stroke:#f88;fill:#d3d3d3;stroke-width:2;cursor:pointer;shape-rendering:crispEdges}#mermaid-svg-nDiWSasIpaluuCSf .milestone{transform:rotate(45deg) scale(0.8, 0.8)}#mermaid-svg-nDiWSasIpaluuCSf .milestoneText{font-style:italic}#mermaid-svg-nDiWSasIpaluuCSf .doneCritText0,#mermaid-svg-nDiWSasIpaluuCSf .doneCritText1,#mermaid-svg-nDiWSasIpaluuCSf .doneCritText2,#mermaid-svg-nDiWSasIpaluuCSf .doneCritText3{fill:#000 !important}#mermaid-svg-nDiWSasIpaluuCSf .activeCritText0,#mermaid-svg-nDiWSasIpaluuCSf .activeCritText1,#mermaid-svg-nDiWSasIpaluuCSf .activeCritText2,#mermaid-svg-nDiWSasIpaluuCSf .activeCritText3{fill:#000 !important}#mermaid-svg-nDiWSasIpaluuCSf .titleText{text-anchor:middle;font-size:18px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-nDiWSasIpaluuCSf g.classGroup text{fill:#9370db;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:10px}#mermaid-svg-nDiWSasIpaluuCSf g.classGroup text .title{font-weight:bolder}#mermaid-svg-nDiWSasIpaluuCSf g.clickable{cursor:pointer}#mermaid-svg-nDiWSasIpaluuCSf g.classGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-nDiWSasIpaluuCSf g.classGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-nDiWSasIpaluuCSf .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5}#mermaid-svg-nDiWSasIpaluuCSf .classLabel .label{fill:#9370db;font-size:10px}#mermaid-svg-nDiWSasIpaluuCSf .relation{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-nDiWSasIpaluuCSf .dashed-line{stroke-dasharray:3}#mermaid-svg-nDiWSasIpaluuCSf #compositionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-nDiWSasIpaluuCSf #compositionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-nDiWSasIpaluuCSf #aggregationStart{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-nDiWSasIpaluuCSf #aggregationEnd{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-nDiWSasIpaluuCSf #dependencyStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-nDiWSasIpaluuCSf #dependencyEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-nDiWSasIpaluuCSf #extensionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-nDiWSasIpaluuCSf #extensionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-nDiWSasIpaluuCSf .commit-id,#mermaid-svg-nDiWSasIpaluuCSf .commit-msg,#mermaid-svg-nDiWSasIpaluuCSf .branch-label{fill:lightgrey;color:lightgrey;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-nDiWSasIpaluuCSf .pieTitleText{text-anchor:middle;font-size:25px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-nDiWSasIpaluuCSf .slice{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-nDiWSasIpaluuCSf g.stateGroup text{fill:#9370db;stroke:none;font-size:10px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-nDiWSasIpaluuCSf g.stateGroup text{fill:#9370db;fill:#333;stroke:none;font-size:10px}#mermaid-svg-nDiWSasIpaluuCSf g.statediagram-cluster .cluster-label text{fill:#333}#mermaid-svg-nDiWSasIpaluuCSf g.stateGroup .state-title{font-weight:bolder;fill:#000}#mermaid-svg-nDiWSasIpaluuCSf g.stateGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-nDiWSasIpaluuCSf g.stateGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-nDiWSasIpaluuCSf .transition{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-nDiWSasIpaluuCSf .stateGroup .composit{fill:white;border-bottom:1px}#mermaid-svg-nDiWSasIpaluuCSf .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px}#mermaid-svg-nDiWSasIpaluuCSf .state-note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-nDiWSasIpaluuCSf .state-note text{fill:black;stroke:none;font-size:10px}#mermaid-svg-nDiWSasIpaluuCSf .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.7}#mermaid-svg-nDiWSasIpaluuCSf .edgeLabel text{fill:#333}#mermaid-svg-nDiWSasIpaluuCSf .stateLabel text{fill:#000;font-size:10px;font-weight:bold;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-nDiWSasIpaluuCSf .node circle.state-start{fill:black;stroke:black}#mermaid-svg-nDiWSasIpaluuCSf .node circle.state-end{fill:black;stroke:white;stroke-width:1.5}#mermaid-svg-nDiWSasIpaluuCSf #statediagram-barbEnd{fill:#9370db}#mermaid-svg-nDiWSasIpaluuCSf .statediagram-cluster rect{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-nDiWSasIpaluuCSf .statediagram-cluster rect.outer{rx:5px;ry:5px}#mermaid-svg-nDiWSasIpaluuCSf .statediagram-state .divider{stroke:#9370db}#mermaid-svg-nDiWSasIpaluuCSf .statediagram-state .title-state{rx:5px;ry:5px}#mermaid-svg-nDiWSasIpaluuCSf .statediagram-cluster.statediagram-cluster .inner{fill:white}#mermaid-svg-nDiWSasIpaluuCSf .statediagram-cluster.statediagram-cluster-alt .inner{fill:#e0e0e0}#mermaid-svg-nDiWSasIpaluuCSf .statediagram-cluster .inner{rx:0;ry:0}#mermaid-svg-nDiWSasIpaluuCSf .statediagram-state rect.basic{rx:5px;ry:5px}#mermaid-svg-nDiWSasIpaluuCSf .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#efefef}#mermaid-svg-nDiWSasIpaluuCSf .note-edge{stroke-dasharray:5}#mermaid-svg-nDiWSasIpaluuCSf .statediagram-note rect{fill:#fff5ad;stroke:#aa3;stroke-width:1px;rx:0;ry:0}:root{--mermaid-font-family: '"trebuchet ms", verdana, arial';--mermaid-font-family: "Comic Sans MS", "Comic Sans", cursive}#mermaid-svg-nDiWSasIpaluuCSf .error-icon{fill:#522}#mermaid-svg-nDiWSasIpaluuCSf .error-text{fill:#522;stroke:#522}#mermaid-svg-nDiWSasIpaluuCSf .edge-thickness-normal{stroke-width:2px}#mermaid-svg-nDiWSasIpaluuCSf .edge-thickness-thick{stroke-width:3.5px}#mermaid-svg-nDiWSasIpaluuCSf .edge-pattern-solid{stroke-dasharray:0}#mermaid-svg-nDiWSasIpaluuCSf .edge-pattern-dashed{stroke-dasharray:3}#mermaid-svg-nDiWSasIpaluuCSf .edge-pattern-dotted{stroke-dasharray:2}#mermaid-svg-nDiWSasIpaluuCSf .marker{fill:#333}#mermaid-svg-nDiWSasIpaluuCSf .marker.cross{stroke:#333}:root { --mermaid-font-family: "trebuchet ms", verdana, arial;}#mermaid-svg-nDiWSasIpaluuCSf {color: rgba(0, 0, 0, 0.75);font: ;}MySQL核心服務查詢緩存連接/線程處理解析器優化器MySQL客戶端存儲引擎上面就是典型的MySQL三層架構:
MySQL客戶端我們并不用過多關注,因為它與其他很多的產品都雷同,連接處理、認證授權、安全等。
而核心服務這一層除了優化、緩存、函數外,存儲過程、觸發器、視圖也在這一層,因為這些都是跨存儲引擎的部分。
最底層是我們重點關注的一層,即存儲引擎層,存儲引擎只負責數據的存儲和提取,MySQL核心服務則通過API與存儲引擎通信。聽起來很簡單對吧?但是“數據的存儲和提取”則是MySQL中的重中之重(不然數據庫是干嘛的),這里面的學問很多,但核心是兩點:
圍繞著這核心的兩點,就衍生出了很多復雜的數據結構、架構理論和加解鎖算法。
減少IO次數中比較典型的就是B+ Tree;高并發下比較典型的就是MVCC。
這個屬于技術細節,會在后文中用大量篇幅講解,這里先不贅述。
2.4 小結
這里為了省事兒就直接引用《高性能MySQL》中的說辭了(不是原文,加了一些筆者個人理解):
存儲引擎作為三層架構中最核心的部分,掌握核心服務與存儲引擎之間如何通過API來回交互,就抓住了MySQL基礎架構的精髓。
三、 頂層設計
想了半天沒有想好這章叫什么,姑且就叫做頂層設計吧,聽起來高大上一點。
其實筆者本章主要是想講一些平時設計表、索引、列等需要注意的點而非去深挖技術細節(后面會另起一章講細節)。
3.1 設計表的坑
3.1.1 太多的列
究其原因還是因為MySQL的工作機制:
MySQL核心服務和存儲引擎之間會通過緩沖格式來拷貝數據,核心服務會對API返回的內容進行解碼(有點類似于RPC調用中的Serialization,大道至簡,技術之間都是互通的),而解碼的過程開銷是非常高的。當然也不可太過于極端,為了減少列的數量而導致更多的表關聯,得不償失。這里的列太多一般指數千個字段(《高性能MySQL》作者遇到的一個客戶案例,活久見),其實正常的表不會有什么大問題,對于過多極少用到的列,可以考慮另建一張表,做冷熱列的分離。
3.1.2 太多的關聯
這個可以理解為上面太多列的反向操作,把數據拆分的過于細,導致想要獲取一條完整的業務數據需要關聯N張表,而MySQL單次最多只能關聯61張表(會有這種查詢嗎?筆者至今沒見過),建議一次關聯查詢控制在12張表以內。對于關聯過多的筆者之前的做法都是查一些冗余數據到內存里用代碼去過濾,僅供參考。
書中提到了一種EAV的設計模式,并且說這種設計模式“糟糕透頂”,筆者帶著無比的好奇去查閱了下什么設計模式會讓書中作者如此嗤之以鼻,一查明白這種設計模式幾乎只存在于幻想之中:
Entitty-Atribute-Value是EAV的全稱,在介紹中舉了一個典型的例子,即醫院中會檢測很多指標,還有一些指標甚至在設計表的時候都不得而知,為了應對將來可能陸續出現的檢測指標,通過設計以下的表來記錄指標
| 病人 | 血壓 | 250 |
可以看出EAV不光分離了完整的業務邏輯,還會導致大量的關聯查詢,違背了數據庫設計范式。
筆者比較納悶,這樣的場景為什么不嘗試NoSQL或者列數據庫呢?
3.1.3 過度使用枚舉
筆者從來沒用過MySQL里的枚舉,故略過
3.1.4 范式和反范式
范式一詞筆者感覺不像是中文,聽起來雖然高大上,但是道理很簡單
以下是一張反范式的表
| 小明 | 技術部 | Tony |
| 小紅 | 技術部 | Tony |
| 小花 | 技術部 | Tony |
我們可以看到上表存了三次Tony,我們可以將上表拆成員工表+部門表,來滿足范式要求
員工表:
| 小明 | 技術部 |
| 小紅 | 技術部 |
| 小花 | 技術部 |
部門表:
| 技術部 | Tony |
但是在實際運用中,我們也要合理看待范式和反范式,常常都是混用的模式。
這個筆者無法給出論斷,還得各位根據項目實際需求去抉擇。通常我們還是采用一些反范式的設計來增加一些可以接受的冗余來換取性能,同樣我們也可以把一些關聯查詢較少的進行范式分解。
3.1.5 緩存表、匯總表
愛因斯坦生前最偉大的成就就是將時間與空間統一了起來。計算機里亦是如此,在常見的設計中就有“時間換空間,空間換時間”的說法。實際我們可以通過建立緩存或者匯總表來犧牲一定的空間,并且允許少量“臟數據”的存在來換取性能的提升(也就是換時間)。這些表也被叫做“累積表(Roll-UP Table)”。必要的時候我們還可以把MySQL數據同步到ElasticSearch來換取快速檢索和數據聚合分析的能力。
3.1.6 Alter Table
在設計好的表結構上進行更改是一項令人頭疼的工作,而這個常常是不可避免的,因為需求無時無刻不在變化,雖然前期可以通過設計一些冗余字段來解決,但是架不住后期需求的爆發式增長。這里NoSQL或者列數據庫的優勢就體現出來了,筆者認為將來傳統的關系型數據庫終會被取代,當然這個過程會比較漫長,畢竟現在還有不少大型系統用的是JDK1.6,對于他們來說,就如同升級JDK一般,升級數據庫會產生巨大的成本。
對于線上的alter table,為了不導致業務中斷,我們常見的方式有兩種:
對于修改某一列的操作,MODIFY COLUMN是會去重建整張表的,而ALTER COLUMN則不會。
對于書中記載的一些未經官方認證的騷操作筆者這里也不記錄了,書中作者也坦言存在一定風險,data is valuable,對于數據的操作還是謹慎一點的好。
3.1.7 小結
原則是放之四海而皆準的東西,所以記住一些原則可能比一些細節更加重要,因為細節是volatile的
3.2 索引設計
索引是保證MySQL高效查詢的利器,合理使用能大大加快查詢效率,但是如果設置不合理的索引可能只會白白增加update的開銷,數據量越大,索引效果越明顯。
索引不是孤立的,而是和查詢成對出現,最優的索引甚至比好的索引快兩個數量級,最優索引常常伴隨著查詢的重寫。
我們即使在使用一些ORM框架的時候,仍然需要注意索引的使用情況,除非是非常基本的查詢,ORM框架很難生成適合索引的查詢,這也是筆者詬病現在項目中的JPA的原因,因為你如果不打印出來sql然后去分析優化,你永遠不知道JPA給你生成的query有多么糟糕。
言已至此,筆者更愿意在此處分享給大家一個實際的例子:
許久以前,筆者接手龐大系統中的一個服務,在此基礎上進行開發,這個系統中已經實現了很多良好的設計,使用QueryDSL框架并且在此基礎上做了很多層的封裝。而筆者要做的就是在此基礎上封裝一個接口,關聯多張表查詢出若干條完整的業務數據。聽起來這是一個簡單的工作,完成調研后也很快就實現了這個功能。但是QA同事卻在測試過程中發現這個接口耗時特別長。經過了近一天的調研,才發現問題出在QueryDSL框架生成的語句上,在運行執行后發現雖然走進了索引,但是類型卻是DEPENDENT SUBQUERY,這種類型的索引效率會隨著父子查詢數量的增長而不斷降低。這是一個bad news,我和我的同事花費了大量的時間來修改DSL表達式,但是最終效果都不盡人意。如果想達到預期效果,我們就要放棄層層封裝,轉而自己封裝一套native的查詢。本是提升效率的框架最終卻讓我們做了許多無用功。
上面這個故事同時也印證了書中作者說的那句話:“很多時候,即使是查詢優化技術專家也很難兼顧到各種情況,更別說ORM了”
當然JPA并不是一無是處,筆者認為在數據量不大,數據關聯關系和數據操作不復雜的情況下,可以使用。更多時候還是建議使用像MyBatis一類能讓Dev直面sql的框架,能避免很多不必要的麻煩。
3.2.1 還是那棵樹
如果有人問MySQL底層索引數據結構是什么,不要著急回答B+樹
因為B+樹是InnoDB底層的數據結構,MySQL支持的索引數據類型有很多:
還有很多,因為索引是建立在存儲引擎上的,所以不同的存儲引擎可以根據自身特性實現不同的底層數據結構。
問的和用的最多的就是B+樹了,所以別的數據結構我們先不做討論,著重看下B樹和B+樹,對別的數據結構感興趣的小伙伴我會在此文發布后獨立發布一篇《MySQL索引機制及調優》,可以去看看。
雖然網絡上有很多B+樹的結構說明圖,但是我還是愿意用mermaid去重繪一套,希望能用盡量簡潔的圖形讓看官理解B+樹核心思想。
(不好意思上面flag立的我也實現不了,不是我懶,是筆者遍歷了下mermaid支持的各種圖,確實畫不出那種效果)
筆者找到了程序員小灰專欄上的一篇B+樹漫畫講解,自愧不如,這篇文章講得已經足夠細足夠好,筆者沒有必要再畫蛇添足了,建議同學們移步去看一下,相信仔細看都能看懂。
《高性能MySQL》一書中并沒有討論B+樹這種數據結構,因為他認為B+樹只是B樹的變種而已,其核心思想相同(事實也是如此)。
BTREE這個關鍵字在MySQL的CREATE TABLE和其他語句中常常使用,但是不同的存儲引擎實現是不同的,比如InnoDB是B+Tree,而NDB Cluster使用了T-Tree。BTREE對索引列是順序組織存儲的,所以很適合查詢范圍數據。另外使用BTREE索引需要注意“最左匹配原則”,這個在《MySQL索引機制及調優》中有詳細介紹,這里就不贅述了。
3.2.2 三星系統
講到這里不得不提評估一個索引是否適合某個查詢的“三星系統(three-star system)”(人們總是熱衷于打分,記得我所在Team里的某位大佬也是通過《DevOps成熟度模型》給爸爸們的DevOps平臺評分以及出改進方案,贏得了一致好評),三星系統的評估基準如下(星星越多越好):
書中對于三星系統的描述有點過于抽象,每個詞匯都能看懂,但是連起來讀就都不懂了。
為此筆者通過結合書中的context和大量查閱資料,將自己的理解記錄下來:(不一定完全準確)
一星指的是以下的情況:
根據書中原文:
有的“專家”會建議“把where條件里面的列都建上索引”,這個建議是非常錯誤的,這樣最好的情況下也只能是一星索引。
我們可以推斷,給必要的列建立適當的索引而非根據where條件的列都建上索引,即可滿足一星的標準。
二星的標準和書中“5.3.4 選擇合適的索引列順序”這一節有著一定的關聯性,即如果創建一個組合索引,那么索引首先會按照最左列進行排序,其次是第二列,如果select時候帶上了多列的order by,那么我們組合索引列的順序最好和order by保持一致。
三星索引的定義讓人摸不著頭腦,但是根據書中所載可以推斷一二:
有時如果無法設計一個三星索引,那么不如忽略掉WHERE子句,集中精力優化索引列的順序,或者創建一個全覆蓋索引。在多個列上建立獨立的單列索引大部分情況下并不能提高MySQL的查詢性能。
由原文我們可以得出以下幾點,設計一個三星索引的注意事項:
三星索引也就是最優索引,同時書中也建議將選擇性高的列放到組合索引的前面。所謂選擇性高即重復數據少,區分程度高的列,這樣索引才不會篩選出大量的重復數據。
小結:筆者認為三星系統是逐步遞增完善的,每個星級的遞增都會包含上一星級的優點,逐步完善直至設計出最優索引。而最優索引的設計沒有一個放之四海而皆準的法則,還需要筆者和大家不斷學習,視情況而定。
3.2.3 聚簇索引
這個網絡上提到的太多了,筆者不想浪費太多筆墨在此,只講重點:
聚簇索引并不是一種索引類型,而是數據存儲的方式。大家熟知的InnoDB就是聚簇,而MyISAM就是非聚簇,兩者區別就在于InnoDB在同一結構中保存了BTREE的索引+數據row(存放于葉子頁中)。對于沒有主鍵和唯一索引的表,InnoDB會隱式定義一個row_id來作為聚簇索引(你不給我我就自己造)。書中提到聚簇索引的缺點時引申出二級索引在引入聚簇后需要兩次索引查找,并且用了大量的篇幅講解二級索引,筆者這里就不做介紹了,感興趣的可以自行百度“MySQL 二級索引”或者看下書中“5.3.5 聚簇索引”的后半部分。
3.2.4 覆蓋索引
這是一個讓筆者著迷的話題,我們知道在InnoDB BTREE的葉子節點中包含了索引+數據row。如果一個查詢匹配的索引中已經包含了要查詢的數據,也就意味著不需要再回表查詢了。如果一個索引覆蓋(包含)了所有需要查詢的字段的值,我們就稱之為“覆蓋索引”。
覆蓋索引的好處有很多,筆者簡單說說:
MySQL中只能用BTREE做覆蓋索引,因為覆蓋索引的大前提是索引中必須存儲索引列的值,而hash、空間、全文索引等都不存儲索引列的值。如果使用的是覆蓋索引,我們在explain sql語句的時候會看到“Using index”的信息。
書中給了一個比較典型的例子:
首先給store_id和film_id兩列創建了一個組合索引“idx_store_id_film_id”
然后執行以下sql
explain select store_id, film_id from inventory我們會發現結果就是“Using index”,即走進了覆蓋索引
有一種情況即使走進了覆蓋索引依舊會回表:即索引覆蓋了where條件中的字段,但是沒有覆蓋到查詢的字段,即使where中的條件不滿足,也會回表將不滿足的行取出來然后過濾掉。好在MySQL 5.5(不含)之后的版本優化了這個bug(MySQL 5.6采用了一種“索引條件推送”機制,感興趣可以了解下)。
3.2.5 索引和排序
我們在實際開發過程中,如果有排序的需求時,可以將自己的排序sql explain一下。
如果type=index,則說明MySQL使用了索引掃描來做排序(區別于Extra的“Using index”,別搞混了)。但是排序過程中不是覆蓋索引的話(上面有講),每一條索引都需要回表查詢一次,甚至效率比順序全表掃描都慢(尤其是IO密集型系統)。
設計索引的時候如果既能滿足排序,又能用于查找,那是再好不過的了。設計索引的時候也要盡量往這上面靠。當且僅當索引和order by子句里列順序一致,且列的排序方向(正序或倒序)一致時,MySQL才能用索引對結果集進行排序。書中作者建議對于不同方向的排序,可以通過建立冗余列存儲排序列的反轉串或者相反數來實現。
對于關聯表查詢的排序,只有order by子句中的列全部在第一張表里的時候,才能用索引做排序。
3.2.6 冗余索引和重復索引
首先做下科普:
一張表有AB兩列,我在A上創建兩個不同名索引就叫重復索引,這個是設計里堅決不允許的,因為沒有任何意義,還會降低性能。如果我已經有了AB列的聯合索引,我再建個A列的索引,這就叫冗余索引(如果先建立的是BA而非AB,則A不叫冗余索引,自己去看最左匹配原則),這個在設計上是允許存在的。
大多數情況下我們都不需要冗余索引,書中作者建議盡量去擴展已有的索引而非創建新索引(因為MySQL會獨立維護每個索引,索引多了會降低性能)。但是在個別場景下,比如不希望擴展已有索引導致索引變得太大而影響原有索引性能時,可以添加冗余索引。
這個擴展原有索引導致索引太大的概念比較寬泛,比較簡單的例子就是我已經有了AB列的聯合索引,但是A列是個很長的varchar,那么我就可以將A列單獨做個索引,這樣就不會導致性能的急劇下降。
還有一種未使用索引,是書中作者不推薦保留的,建議刪除。
3.2.7 索引和鎖
鎖的種類很多,什么樂觀鎖、悲觀鎖、排他鎖、共享鎖、行鎖、表鎖、記錄鎖、間隙鎖、臨鍵鎖、死鎖等等,筆者在本文中就不一一說明了,一來是篇幅太長,二來也和主題不符。既然是高性能MySQL,筆者希望將更多的筆墨放在MVCC(多版本并發控制)上。之前有個MySQL大拿同事為我們做了一次內部技術分享,筆者也很樂意將其中的干貨整理并分享給大家,同時也可以根據弗曼學習法來測試下筆者到底掌握了沒有。
這里只講一點書中作者提到的現有人知的知識點:InnoDB在二級索引上使用共享讀鎖,但訪問主鍵索引需要排他寫鎖。這消除了使用覆蓋索引的可能性,并且使得select for update比lock in share mode或非鎖定查詢慢很多。(可能是筆者在實際場景中遇到的比較少,不是完全理解,總是少用select for update就對了)
關于MVCC各位看官不要著急,我會另起一章專門講解。
3.2.8 實戰索引設計
書中作者在此處舉了一個在線約會網站組合搜索用戶索引設計的案例,引起了筆者的共鳴。因為筆者在工作中遇到了極其相似的場景,并且在性能優化的需求上被反復按在地上摩擦。在筆者以下寫的案例中可能與原書中場景有較大差別,但是其核心思想相同,如果對于看官有些許幫助,我不勝榮幸:
有一張服務記錄record表,現在需要進行多條件篩選的同時排序。
首先考慮的事情是使用索引來排序還是檢索出數據后再排序:
如果采用前者,那么會嚴格限制索引和查詢的設計,因為我們不期望有回表的操作,正如3.2.5中所述:“設計索引的時候如果既能滿足排序,又能用于查找,那是再好不過的了”。當然這個實現起來很難。
如果采用后者,那么實現起來很容易,但是性能會不盡人意。
那么我們試試用書中同樣的方法對我們的索引和查詢進行優化(書中選擇了前者)。
1. 支持多種過濾條件
過濾條件有時間范圍,分類,操作人(們),Owner(s),狀態等。
首先需要對以上過濾條件在where條件里出現頻率做個排序,然后評估它們的選擇性,然后綜合評估后來選擇誰做索引的最左前綴(出現頻率越高,選擇性越高的越往左排)。
但是到這里書中講到了sex(性別)列的案例,作者將sex作為最左前綴,這個是反常識的,因為sex的選擇性是很低的(通常只有兩種:男或女),而索引設計的時候是不建議在選擇性低的列上創建索引的。作者說出了他的思考:sex出現頻率極高,且即使不帶這個篩選條件我們也可以通過sex in(男,女)來巧妙的規避,這樣和不加沒有什么區別,但是在加上sex篩選條件的時候就能篩選出去不少數據。(筆者感慨,即使掌握了書中知識也很難在實際應用中想到這點,當然這個前提是我們需要用代碼加工查詢條件來規避不走sex索引的情況)
當然筆者工作中的場景沒有類似sex這樣的神仙字段,只好作罷。并且這種只適用于極少的枚舉值的情況,因為你不可能把country(國家)列也搞個同樣的操作:不帶country篩選條件就去SELECT IN 200多個國家,瘋了。。。(每增加一個IN優化器需要做的組合都會呈指數增加,不可濫用)
同時書中也解釋了為什么總是把age(年齡)列放到組合索引的最后,因為年齡查詢大多數時候是范圍查詢(很少有程序猿用IN(10,11,…,99)去篩選10~99歲的用戶),我們要盡可能在此之前讓MySQL使用更多的索引列,因為MySQL優化器的機制(書中沒講具體是什么機制,留作課題后續研究),所以我們要將類似于=或者IN一類的等值查詢放在前面,范圍查詢放在后面。生僻的篩選條件可加可不加,這里就不再浪費筆墨。
而以上的字段按照筆者上述的排序規則則是(從左至右):操作人(們)→ Owners → 分類 → 狀態 → 時間范圍
2. 避免多個范圍條件
比較下下面兩個sql
explain select actor_id from actor where actor_id > 45; explain select actor_id from actor where actor_id in (1, 4, 99);他們explain結果的type是相同的(range),但是第二個是多個等值查詢,所以會比第一條快上很多,即使explain無法很好區分它們。
MySQL無法同時使用兩個范圍查詢條件對應的兩個索引,那如果存在“查詢age在10~99歲并且7天之內登錄過的用戶”這個需求時,該怎么辦呢?
作者采用了曲線救國的方式,age仍然作為范圍查詢條件,然后在表中加入一個新列active,來把0每次登錄用戶都更新為1,并且將連續7天沒有登錄的用戶設置為0(筆者認為是否需要再加一個計數列?)。最后巧妙的把范圍查詢變成了等值查詢。
– 截止目前筆者已經寫了9200+字,就這樣才勉強總結了此書一半,后續還會有很多精彩的內容,筆者也會不忘初心,盡可能多得將自己所見所聞書寫進來,先發稿一版,之后不斷迭代,碼字不易,若耐心的看官能看到這行文字,麻煩隨手點個贊,不勝感激! –
總結
以上是生活随笔為你收集整理的高性能MySQL(呕心沥血整理万字长文)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 前端学习(493):script之延迟脚
- 下一篇: 高性能mysql第一章——架构