日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

高性能MySQL(呕心沥血整理万字长文)

發(fā)布時(shí)間:2023/12/10 数据库 66 豆豆
生活随笔 收集整理的這篇文章主要介紹了 高性能MySQL(呕心沥血整理万字长文) 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

高性能MySQL

文章目錄

  • 高性能MySQL
    • 一、 引言
    • 二、 架構(gòu)
      • 2.1 談?wù)劶夹g(shù)選型依據(jù)
      • 2.2 一次并不特別成功的技術(shù)選型
      • 2.3 聊聊MySQL的邏輯架構(gòu)
      • 2.4 小結(jié)
    • 三、 頂層設(shè)計(jì)
      • 3.1 設(shè)計(jì)表的坑
        • 3.1.1 太多的列
        • 3.1.2 太多的關(guān)聯(lián)
        • 3.1.3 過度使用枚舉
        • 3.1.4 范式和反范式
        • 3.1.5 緩存表、匯總表
        • 3.1.6 Alter Table
        • 3.1.7 小結(jié)
      • 3.2 索引設(shè)計(jì)
        • 3.2.1 還是那棵樹
        • 3.2.2 三星系統(tǒng)
        • 3.2.3 聚簇索引
        • 3.2.4 覆蓋索引
        • 3.2.5 索引和排序
        • 3.2.6 冗余索引和重復(fù)索引
        • 3.2.7 索引和鎖
        • 3.2.8 實(shí)戰(zhàn)索引設(shè)計(jì)

一、 引言

筆者現(xiàn)在所在的項(xiàng)目常常會(huì)遇到慢查詢問題,龐大的數(shù)據(jù)量和沒有及時(shí)分庫分表導(dǎo)致現(xiàn)在生產(chǎn)環(huán)境一張表動(dòng)輒上千萬,更有甚者已經(jīng)上億。。。

在前東家沒有遇到的很多問題都在這個(gè)項(xiàng)目碰到了(前東家數(shù)據(jù)表也就幾十萬的數(shù)據(jù)量,且最初技術(shù)選型就將所有的輿情文章放到了Elasticsearch)。同時(shí)也讓筆者反思自己在數(shù)據(jù)庫優(yōu)化這塊的短板,下定決心靜心開始拜讀《高性能MySQL》一書,并將其中受益匪淺的內(nèi)容予以記錄,假以時(shí)日能將此文章分享到自己的博客造福他人也是一件幸事。

此文章筆者也不知道什么時(shí)候能寫完,但是會(huì)不斷優(yōu)化演進(jìn),畢竟學(xué)無止境。

宗旨就是寫深寫透,深入淺出,讓我自己回看或是看官們閱讀時(shí),能夠完全理解。

知識(shí)點(diǎn)可能會(huì)比較零散,因?yàn)楣P者希望將此作為一本字典供自己和他人查閱。

引言至此,畢。

二、 架構(gòu)

2.1 談?wù)劶夹g(shù)選型依據(jù)

說起技術(shù)選型其實(shí)筆者也很慚愧,畢竟對(duì)于架構(gòu)設(shè)計(jì)這塊也是略懂皮毛、一知半解,所以不敢講大話,只能說說自己的拙見。

“MySQL并不完美,但是卻足夠靈活”。

這句話不是我說的,而是《高性能MySQL》一書中開篇明義講到的最核心的一句話,這也說明MySQL可能不是當(dāng)下或未來的最優(yōu)解,但是它是架構(gòu)中的萬金油,龐雜非單一的項(xiàng)目中總會(huì)有它的用武之地。

在此之前,請(qǐng)?jiān)试S我給大家普及兩個(gè)術(shù)語:

  • OLTP (聯(lián)機(jī)事務(wù)處理 On-Line Transaction Processing)
  • OLAP (聯(lián)機(jī)分析處理 On-Line Analytical Processing)
  • 這是我們對(duì)自身系統(tǒng)的一個(gè)分類,你的系統(tǒng)是事務(wù)型的還是分析型的。我們常見的傳統(tǒng)關(guān)系型數(shù)據(jù)庫的應(yīng)用幾乎都是OLTP,這也是MySQL的擅長項(xiàng)。OLAP更多是一些報(bào)表系統(tǒng),比如筆者所在項(xiàng)目用的就是PowerBI,上面運(yùn)行著大量的復(fù)雜的查詢,視圖以及存儲(chǔ)過程,當(dāng)然現(xiàn)在PowerBI也存在著性能瓶頸,正計(jì)劃用大數(shù)據(jù)組件如Spark、Flink來破局。

    所以是否選用MySQL可以從以下維度考量:

  • 是否是OLTP類型的應(yīng)用
  • 是否有更優(yōu)解(技術(shù)選型是沒有最優(yōu)解的)
  • 成員對(duì)于更優(yōu)解和MySQL的熟悉程度(實(shí)施成本)
  • 第三點(diǎn)確實(shí)是一個(gè)重要的考量依據(jù),幾乎所有的程序員或多或少都接觸過MySQL,這個(gè)也會(huì)大幅降低團(tuán)隊(duì)的學(xué)習(xí)成本,因?yàn)槟悴荒芤笠粋€(gè)從未接觸過Flink的團(tuán)隊(duì)一個(gè)月就交付出高質(zhì)量的流處理平臺(tái),讀過《人月神話》或者接觸過十個(gè)女人一個(gè)月生產(chǎn)的甲方爸爸的小伙伴,應(yīng)該深有體會(huì)。

    對(duì)于更優(yōu)解,我再啰嗦兩句,這個(gè)更優(yōu)解可能是一個(gè)新技術(shù),你從來沒有嘗試在項(xiàng)目中應(yīng)用的技術(shù)(畢竟誰都有第一次)。有一次內(nèi)部培訓(xùn)中我問了我的Boss關(guān)于新技術(shù)選型的問題,我的Boss對(duì)于新技術(shù)的選型給出了以下建議:

  • 這個(gè)技術(shù)是否有大廠背書
  • 這個(gè)技術(shù)是否廣泛應(yīng)用
  • 這個(gè)技術(shù)是否有活躍的社區(qū)
  • 你是否做了足夠的前置工作
  • 如果同時(shí)滿足以上四個(gè)條件,那么就可以大膽嘗試了。

    2.2 一次并不特別成功的技術(shù)選型

    這一段和MySQL自身并無太大關(guān)系,只是筆者單純想吐個(gè)槽而已,不感興趣的同學(xué)可以自行跳過。

    筆者項(xiàng)目使用的是JPA,而在實(shí)際應(yīng)用過程中發(fā)現(xiàn)很多問題:

  • 項(xiàng)目中的開發(fā)對(duì)于JPA or Hibernate的了解良莠不齊,導(dǎo)致大量多余查詢的出現(xiàn)
  • JPA N+1 問題頻現(xiàn)
  • 開發(fā)上生產(chǎn)前沒有對(duì)語句進(jìn)行explain,導(dǎo)致大量慢查詢
  • 有的服務(wù)引用了QueryDSL,而這是一個(gè)漏洞百出的框架
  • JPQL對(duì)于union查詢不支持,導(dǎo)致很多DEPENDENT SUBQUERY
  • 繁雜的Entity關(guān)系
  • 以上還是一小部分問題,并不是說JPA不好,JPA一直是一個(gè)優(yōu)秀的ORM框架,但是同時(shí)也對(duì)使用者的要求較高。而產(chǎn)生問題的根本原因就是當(dāng)初沒有對(duì)團(tuán)隊(duì)成員實(shí)力有一個(gè)合理的評(píng)估,亦或是沒有針對(duì)性的培訓(xùn)。

    反觀數(shù)據(jù)庫的技術(shù)選型時(shí),你自身或者你的團(tuán)隊(duì)對(duì)于被選擇的產(chǎn)品(Oracle、MySQL、PowerBI、PostgreSQL等),不光是看產(chǎn)品本身,還要綜合上你和你的團(tuán)隊(duì)對(duì)于這個(gè)數(shù)據(jù)庫產(chǎn)品的了解,才能做出明智的選型。

    我的Boss常常在面試部門架構(gòu)師的時(shí)候會(huì)問:“有那么多可以實(shí)現(xiàn)的技術(shù),你為什么選擇了這個(gè)技術(shù)?”

    有超過90%的人會(huì)回答:“因?yàn)槲抑粫?huì)這個(gè)技術(shù)”,或者“這個(gè)技術(shù)是現(xiàn)在最流行的”。

    很顯然這個(gè)回答并不能讓我的Boss滿意,因?yàn)橥瑯拥脑捠钦f服不了客戶的。

    想象你在和眾多優(yōu)秀的公司一起B(yǎng)D(競(jìng)標(biāo))一個(gè)大項(xiàng)目,我們假設(shè)我們只做技術(shù)咨詢而非交付,那么下面那一句話更容易打動(dòng)客戶?

  • 我們選用了微服務(wù)框架,因?yàn)檫@是時(shí)下最熱門的技術(shù),且我只會(huì)這門技術(shù)
  • 我們選用了微服務(wù)框架,因?yàn)榻Y(jié)合上現(xiàn)有項(xiàng)目體量和未來需要的延展性,以及實(shí)施成本綜合考量,這是目前眾多技術(shù)中最適合您的方案
  • 最后第二種說辭打動(dòng)了客戶,也幫助我的Boss在BD過程中成功擊敗了別的競(jìng)爭(zhēng)對(duì)手。

    技術(shù)是由需求驅(qū)動(dòng)的,所以滿足需求才是技術(shù)的初衷,不可為了求新求異而本末倒置。

    就好比客人去一家餐廳想要喝湯,你給一雙筷子告訴他:“用筷子可能同時(shí)調(diào)動(dòng)幾十塊肌肉,健身益腦”云云,即使說得天花亂墜,最終還是沒有解決客人的需求。

    我在學(xué)習(xí)微服務(wù)架構(gòu)的時(shí)候,講師的一句話也打動(dòng)了我:“技術(shù)沒有好與壞,只有適合與不適合”。

    希望我這段篇幅不長的文字能引起大家的共鳴。

    2.3 聊聊MySQL的邏輯架構(gòu)

    #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核心服務(wù)查詢緩存連接/線程處理解析器優(yōu)化器MySQL客戶端存儲(chǔ)引擎

    上面就是典型的MySQL三層架構(gòu):

  • MySQL客戶端
  • MySQL核心服務(wù)
  • 存儲(chǔ)引擎
  • MySQL客戶端我們并不用過多關(guān)注,因?yàn)樗c其他很多的產(chǎn)品都雷同,連接處理、認(rèn)證授權(quán)、安全等。

    而核心服務(wù)這一層除了優(yōu)化、緩存、函數(shù)外,存儲(chǔ)過程、觸發(fā)器、視圖也在這一層,因?yàn)檫@些都是跨存儲(chǔ)引擎的部分。

    最底層是我們重點(diǎn)關(guān)注的一層,即存儲(chǔ)引擎層,存儲(chǔ)引擎只負(fù)責(zé)數(shù)據(jù)的存儲(chǔ)和提取,MySQL核心服務(wù)則通過API與存儲(chǔ)引擎通信。聽起來很簡(jiǎn)單對(duì)吧?但是“數(shù)據(jù)的存儲(chǔ)和提取”則是MySQL中的重中之重(不然數(shù)據(jù)庫是干嘛的),這里面的學(xué)問很多,但核心是兩點(diǎn):

  • 減少IO次數(shù)(提升性能)
  • 高并發(fā)下的數(shù)據(jù)一致性
  • 圍繞著這核心的兩點(diǎn),就衍生出了很多復(fù)雜的數(shù)據(jù)結(jié)構(gòu)、架構(gòu)理論和加解鎖算法。

    減少IO次數(shù)中比較典型的就是B+ Tree;高并發(fā)下比較典型的就是MVCC。

    這個(gè)屬于技術(shù)細(xì)節(jié),會(huì)在后文中用大量篇幅講解,這里先不贅述。

    2.4 小結(jié)

    這里為了省事兒就直接引用《高性能MySQL》中的說辭了(不是原文,加了一些筆者個(gè)人理解):

    存儲(chǔ)引擎作為三層架構(gòu)中最核心的部分,掌握核心服務(wù)與存儲(chǔ)引擎之間如何通過API來回交互,就抓住了MySQL基礎(chǔ)架構(gòu)的精髓。

    三、 頂層設(shè)計(jì)

    想了半天沒有想好這章叫什么,姑且就叫做頂層設(shè)計(jì)吧,聽起來高大上一點(diǎn)。

    其實(shí)筆者本章主要是想講一些平時(shí)設(shè)計(jì)表、索引、列等需要注意的點(diǎn)而非去深挖技術(shù)細(xì)節(jié)(后面會(huì)另起一章講細(xì)節(jié))。

    3.1 設(shè)計(jì)表的坑

    3.1.1 太多的列

    究其原因還是因?yàn)镸ySQL的工作機(jī)制:

    MySQL核心服務(wù)和存儲(chǔ)引擎之間會(huì)通過緩沖格式來拷貝數(shù)據(jù),核心服務(wù)會(huì)對(duì)API返回的內(nèi)容進(jìn)行解碼(有點(diǎn)類似于RPC調(diào)用中的Serialization,大道至簡(jiǎn),技術(shù)之間都是互通的),而解碼的過程開銷是非常高的。當(dāng)然也不可太過于極端,為了減少列的數(shù)量而導(dǎo)致更多的表關(guān)聯(lián),得不償失。這里的列太多一般指數(shù)千個(gè)字段(《高性能MySQL》作者遇到的一個(gè)客戶案例,活久見),其實(shí)正常的表不會(huì)有什么大問題,對(duì)于過多極少用到的列,可以考慮另建一張表,做冷熱列的分離。

    3.1.2 太多的關(guān)聯(lián)

    這個(gè)可以理解為上面太多列的反向操作,把數(shù)據(jù)拆分的過于細(xì),導(dǎo)致想要獲取一條完整的業(yè)務(wù)數(shù)據(jù)需要關(guān)聯(lián)N張表,而MySQL單次最多只能關(guān)聯(lián)61張表(會(huì)有這種查詢嗎?筆者至今沒見過),建議一次關(guān)聯(lián)查詢控制在12張表以內(nèi)。對(duì)于關(guān)聯(lián)過多的筆者之前的做法都是查一些冗余數(shù)據(jù)到內(nèi)存里用代碼去過濾,僅供參考。

    書中提到了一種EAV的設(shè)計(jì)模式,并且說這種設(shè)計(jì)模式“糟糕透頂”,筆者帶著無比的好奇去查閱了下什么設(shè)計(jì)模式會(huì)讓書中作者如此嗤之以鼻,一查明白這種設(shè)計(jì)模式幾乎只存在于幻想之中:

    Entitty-Atribute-Value是EAV的全稱,在介紹中舉了一個(gè)典型的例子,即醫(yī)院中會(huì)檢測(cè)很多指標(biāo),還有一些指標(biāo)甚至在設(shè)計(jì)表的時(shí)候都不得而知,為了應(yīng)對(duì)將來可能陸續(xù)出現(xiàn)的檢測(cè)指標(biāo),通過設(shè)計(jì)以下的表來記錄指標(biāo)

    EntityAttributeValue
    病人血壓250

    可以看出EAV不光分離了完整的業(yè)務(wù)邏輯,還會(huì)導(dǎo)致大量的關(guān)聯(lián)查詢,違背了數(shù)據(jù)庫設(shè)計(jì)范式。

    筆者比較納悶,這樣的場(chǎng)景為什么不嘗試NoSQL或者列數(shù)據(jù)庫呢?

    3.1.3 過度使用枚舉

    筆者從來沒用過MySQL里的枚舉,故略過

    3.1.4 范式和反范式

    范式一詞筆者感覺不像是中文,聽起來雖然高大上,但是道理很簡(jiǎn)單

    以下是一張反范式的表

    員工部門領(lǐng)導(dǎo)
    小明技術(shù)部Tony
    小紅技術(shù)部Tony
    小花技術(shù)部Tony

    我們可以看到上表存了三次Tony,我們可以將上表拆成員工表+部門表,來滿足范式要求

    員工表:

    員工部門
    小明技術(shù)部
    小紅技術(shù)部
    小花技術(shù)部

    部門表:

    部門領(lǐng)導(dǎo)
    技術(shù)部Tony

    但是在實(shí)際運(yùn)用中,我們也要合理看待范式和反范式,常常都是混用的模式。

    這個(gè)筆者無法給出論斷,還得各位根據(jù)項(xiàng)目實(shí)際需求去抉擇。通常我們還是采用一些反范式的設(shè)計(jì)來增加一些可以接受的冗余來換取性能,同樣我們也可以把一些關(guān)聯(lián)查詢較少的進(jìn)行范式分解。

    3.1.5 緩存表、匯總表

    愛因斯坦生前最偉大的成就就是將時(shí)間與空間統(tǒng)一了起來。計(jì)算機(jī)里亦是如此,在常見的設(shè)計(jì)中就有“時(shí)間換空間,空間換時(shí)間”的說法。實(shí)際我們可以通過建立緩存或者匯總表來犧牲一定的空間,并且允許少量“臟數(shù)據(jù)”的存在來換取性能的提升(也就是換時(shí)間)。這些表也被叫做“累積表(Roll-UP Table)”。必要的時(shí)候我們還可以把MySQL數(shù)據(jù)同步到ElasticSearch來換取快速檢索和數(shù)據(jù)聚合分析的能力。

    3.1.6 Alter Table

    在設(shè)計(jì)好的表結(jié)構(gòu)上進(jìn)行更改是一項(xiàng)令人頭疼的工作,而這個(gè)常常是不可避免的,因?yàn)樾枨鬅o時(shí)無刻不在變化,雖然前期可以通過設(shè)計(jì)一些冗余字段來解決,但是架不住后期需求的爆發(fā)式增長。這里NoSQL或者列數(shù)據(jù)庫的優(yōu)勢(shì)就體現(xiàn)出來了,筆者認(rèn)為將來傳統(tǒng)的關(guān)系型數(shù)據(jù)庫終會(huì)被取代,當(dāng)然這個(gè)過程會(huì)比較漫長,畢竟現(xiàn)在還有不少大型系統(tǒng)用的是JDK1.6,對(duì)于他們來說,就如同升級(jí)JDK一般,升級(jí)數(shù)據(jù)庫會(huì)產(chǎn)生巨大的成本。

    對(duì)于線上的alter table,為了不導(dǎo)致業(yè)務(wù)中斷,我們常見的方式有兩種:

  • 先在一臺(tái)不提供服務(wù)的機(jī)器上alter table,然后和主庫進(jìn)行切換
  • 通過“影子拷貝”,通過要求的表結(jié)構(gòu)創(chuàng)建一張新表,然后將源表的數(shù)據(jù)遷移到新表,最后通過刪表+重命名的方式交換兩張表。這種方法已經(jīng)被很多團(tuán)隊(duì)開發(fā)成為一個(gè)個(gè)的工具,常見的有FaceBook 的 “online schema change”,Shlomi Noach(不認(rèn)識(shí))的openark tollkit,以及Percona Toolkit。
  • 對(duì)于修改某一列的操作,MODIFY COLUMN是會(huì)去重建整張表的,而ALTER COLUMN則不會(huì)。

    對(duì)于書中記載的一些未經(jīng)官方認(rèn)證的騷操作筆者這里也不記錄了,書中作者也坦言存在一定風(fēng)險(xiǎn),data is valuable,對(duì)于數(shù)據(jù)的操作還是謹(jǐn)慎一點(diǎn)的好。

    3.1.7 小結(jié)

    原則是放之四海而皆準(zhǔn)的東西,所以記住一些原則可能比一些細(xì)節(jié)更加重要,因?yàn)榧?xì)節(jié)是volatile的

  • 避免過度設(shè)計(jì),性能優(yōu)先
  • 盡可能使用小而簡(jiǎn)單的數(shù)據(jù)類型,除非有確切的需要,應(yīng)避免使用null值
  • 盡可能使用相同數(shù)據(jù)類型存儲(chǔ)相關(guān)的值
  • 注意可變長字符串(varchar),因?yàn)榭赡軙?huì)在臨時(shí)表或者排序的時(shí)候按最大長度分配內(nèi)存
  • 盡量用整型定義標(biāo)識(shí)列(筆者理解標(biāo)識(shí)列就是類似于ID列一類能標(biāo)識(shí)唯一性的列)
  • ENUM和SET慎用(我估計(jì)大多數(shù)人也不用)
  • 3.2 索引設(shè)計(jì)

    索引是保證MySQL高效查詢的利器,合理使用能大大加快查詢效率,但是如果設(shè)置不合理的索引可能只會(huì)白白增加update的開銷,數(shù)據(jù)量越大,索引效果越明顯。

    索引不是孤立的,而是和查詢成對(duì)出現(xiàn),最優(yōu)的索引甚至比好的索引快兩個(gè)數(shù)量級(jí),最優(yōu)索引常常伴隨著查詢的重寫。

    我們即使在使用一些ORM框架的時(shí)候,仍然需要注意索引的使用情況,除非是非常基本的查詢,ORM框架很難生成適合索引的查詢,這也是筆者詬病現(xiàn)在項(xiàng)目中的JPA的原因,因?yàn)槟闳绻淮蛴〕鰜韘ql然后去分析優(yōu)化,你永遠(yuǎn)不知道JPA給你生成的query有多么糟糕。

    言已至此,筆者更愿意在此處分享給大家一個(gè)實(shí)際的例子:

    許久以前,筆者接手龐大系統(tǒng)中的一個(gè)服務(wù),在此基礎(chǔ)上進(jìn)行開發(fā),這個(gè)系統(tǒng)中已經(jīng)實(shí)現(xiàn)了很多良好的設(shè)計(jì),使用QueryDSL框架并且在此基礎(chǔ)上做了很多層的封裝。而筆者要做的就是在此基礎(chǔ)上封裝一個(gè)接口,關(guān)聯(lián)多張表查詢出若干條完整的業(yè)務(wù)數(shù)據(jù)。聽起來這是一個(gè)簡(jiǎn)單的工作,完成調(diào)研后也很快就實(shí)現(xiàn)了這個(gè)功能。但是QA同事卻在測(cè)試過程中發(fā)現(xiàn)這個(gè)接口耗時(shí)特別長。經(jīng)過了近一天的調(diào)研,才發(fā)現(xiàn)問題出在QueryDSL框架生成的語句上,在運(yùn)行執(zhí)行后發(fā)現(xiàn)雖然走進(jìn)了索引,但是類型卻是DEPENDENT SUBQUERY,這種類型的索引效率會(huì)隨著父子查詢數(shù)量的增長而不斷降低。這是一個(gè)bad news,我和我的同事花費(fèi)了大量的時(shí)間來修改DSL表達(dá)式,但是最終效果都不盡人意。如果想達(dá)到預(yù)期效果,我們就要放棄層層封裝,轉(zhuǎn)而自己封裝一套native的查詢。本是提升效率的框架最終卻讓我們做了許多無用功。

    上面這個(gè)故事同時(shí)也印證了書中作者說的那句話:“很多時(shí)候,即使是查詢優(yōu)化技術(shù)專家也很難兼顧到各種情況,更別說ORM了”

    當(dāng)然JPA并不是一無是處,筆者認(rèn)為在數(shù)據(jù)量不大,數(shù)據(jù)關(guān)聯(lián)關(guān)系和數(shù)據(jù)操作不復(fù)雜的情況下,可以使用。更多時(shí)候還是建議使用像MyBatis一類能讓Dev直面sql的框架,能避免很多不必要的麻煩。

    3.2.1 還是那棵樹

    如果有人問MySQL底層索引數(shù)據(jù)結(jié)構(gòu)是什么,不要著急回答B(yǎng)+樹

    因?yàn)锽+樹是InnoDB底層的數(shù)據(jù)結(jié)構(gòu),MySQL支持的索引數(shù)據(jù)類型有很多:

  • B樹
  • B+樹
  • hash 索引
  • R樹(空間數(shù)據(jù)索引)
  • 全文索引
  • 分形樹索引
  • 還有很多,因?yàn)樗饕墙⒃诖鎯?chǔ)引擎上的,所以不同的存儲(chǔ)引擎可以根據(jù)自身特性實(shí)現(xiàn)不同的底層數(shù)據(jù)結(jié)構(gòu)。

    問的和用的最多的就是B+樹了,所以別的數(shù)據(jù)結(jié)構(gòu)我們先不做討論,著重看下B樹和B+樹,對(duì)別的數(shù)據(jù)結(jié)構(gòu)感興趣的小伙伴我會(huì)在此文發(fā)布后獨(dú)立發(fā)布一篇《MySQL索引機(jī)制及調(diào)優(yōu)》,可以去看看。

    雖然網(wǎng)絡(luò)上有很多B+樹的結(jié)構(gòu)說明圖,但是我還是愿意用mermaid去重繪一套,希望能用盡量簡(jiǎn)潔的圖形讓看官理解B+樹核心思想。

    (不好意思上面flag立的我也實(shí)現(xiàn)不了,不是我懶,是筆者遍歷了下mermaid支持的各種圖,確實(shí)畫不出那種效果)

    筆者找到了程序員小灰專欄上的一篇B+樹漫畫講解,自愧不如,這篇文章講得已經(jīng)足夠細(xì)足夠好,筆者沒有必要再畫蛇添足了,建議同學(xué)們移步去看一下,相信仔細(xì)看都能看懂。

    《高性能MySQL》一書中并沒有討論B+樹這種數(shù)據(jù)結(jié)構(gòu),因?yàn)樗J(rèn)為B+樹只是B樹的變種而已,其核心思想相同(事實(shí)也是如此)。

    BTREE這個(gè)關(guān)鍵字在MySQL的CREATE TABLE和其他語句中常常使用,但是不同的存儲(chǔ)引擎實(shí)現(xiàn)是不同的,比如InnoDB是B+Tree,而NDB Cluster使用了T-Tree。BTREE對(duì)索引列是順序組織存儲(chǔ)的,所以很適合查詢范圍數(shù)據(jù)。另外使用BTREE索引需要注意“最左匹配原則”,這個(gè)在《MySQL索引機(jī)制及調(diào)優(yōu)》中有詳細(xì)介紹,這里就不贅述了。

    3.2.2 三星系統(tǒng)

    講到這里不得不提評(píng)估一個(gè)索引是否適合某個(gè)查詢的“三星系統(tǒng)(three-star system)”(人們總是熱衷于打分,記得我所在Team里的某位大佬也是通過《DevOps成熟度模型》給爸爸們的DevOps平臺(tái)評(píng)分以及出改進(jìn)方案,贏得了一致好評(píng)),三星系統(tǒng)的評(píng)估基準(zhǔn)如下(星星越多越好):

  • 索引將相關(guān)記錄放到一起則獲得一星
  • 如果索引中的數(shù)據(jù)順序和查找中的排列順序一致則獲得二星
  • 如果索引中的列包含了查詢中需要的全部列則獲得三星
  • 書中對(duì)于三星系統(tǒng)的描述有點(diǎn)過于抽象,每個(gè)詞匯都能看懂,但是連起來讀就都不懂了。

    為此筆者通過結(jié)合書中的context和大量查閱資料,將自己的理解記錄下來:(不一定完全準(zhǔn)確)

  • 一星指的是以下的情況:

    根據(jù)書中原文:

    有的“專家”會(huì)建議“把where條件里面的列都建上索引”,這個(gè)建議是非常錯(cuò)誤的,這樣最好的情況下也只能是一星索引。

    我們可以推斷,給必要的列建立適當(dāng)?shù)乃饕歉鶕?jù)where條件的列都建上索引,即可滿足一星的標(biāo)準(zhǔn)。

  • 二星的標(biāo)準(zhǔn)和書中“5.3.4 選擇合適的索引列順序”這一節(jié)有著一定的關(guān)聯(lián)性,即如果創(chuàng)建一個(gè)組合索引,那么索引首先會(huì)按照最左列進(jìn)行排序,其次是第二列,如果select時(shí)候帶上了多列的order by,那么我們組合索引列的順序最好和order by保持一致。

  • 三星索引的定義讓人摸不著頭腦,但是根據(jù)書中所載可以推斷一二:

    有時(shí)如果無法設(shè)計(jì)一個(gè)三星索引,那么不如忽略掉WHERE子句,集中精力優(yōu)化索引列的順序,或者創(chuàng)建一個(gè)全覆蓋索引。在多個(gè)列上建立獨(dú)立的單列索引大部分情況下并不能提高M(jìn)ySQL的查詢性能。

    由原文我們可以得出以下幾點(diǎn),設(shè)計(jì)一個(gè)三星索引的注意事項(xiàng):

  • 多個(gè)單列索引在大多數(shù)情況下都達(dá)不到三星標(biāo)準(zhǔn)
  • 可以先從WHERE子句下手設(shè)計(jì)索引
  • 優(yōu)化索引列的順序
  • 再不濟(jì)就設(shè)計(jì)全覆蓋索引(這個(gè)在后面會(huì)介紹)
  • 三星索引也就是最優(yōu)索引,同時(shí)書中也建議將選擇性高的列放到組合索引的前面。所謂選擇性高即重復(fù)數(shù)據(jù)少,區(qū)分程度高的列,這樣索引才不會(huì)篩選出大量的重復(fù)數(shù)據(jù)。

    小結(jié):筆者認(rèn)為三星系統(tǒng)是逐步遞增完善的,每個(gè)星級(jí)的遞增都會(huì)包含上一星級(jí)的優(yōu)點(diǎn),逐步完善直至設(shè)計(jì)出最優(yōu)索引。而最優(yōu)索引的設(shè)計(jì)沒有一個(gè)放之四海而皆準(zhǔn)的法則,還需要筆者和大家不斷學(xué)習(xí),視情況而定。

    3.2.3 聚簇索引

    這個(gè)網(wǎng)絡(luò)上提到的太多了,筆者不想浪費(fèi)太多筆墨在此,只講重點(diǎn):

    聚簇索引并不是一種索引類型,而是數(shù)據(jù)存儲(chǔ)的方式。大家熟知的InnoDB就是聚簇,而MyISAM就是非聚簇,兩者區(qū)別就在于InnoDB在同一結(jié)構(gòu)中保存了BTREE的索引+數(shù)據(jù)row(存放于葉子頁中)。對(duì)于沒有主鍵和唯一索引的表,InnoDB會(huì)隱式定義一個(gè)row_id來作為聚簇索引(你不給我我就自己造)。書中提到聚簇索引的缺點(diǎn)時(shí)引申出二級(jí)索引在引入聚簇后需要兩次索引查找,并且用了大量的篇幅講解二級(jí)索引,筆者這里就不做介紹了,感興趣的可以自行百度“MySQL 二級(jí)索引”或者看下書中“5.3.5 聚簇索引”的后半部分。

    3.2.4 覆蓋索引

    這是一個(gè)讓筆者著迷的話題,我們知道在InnoDB BTREE的葉子節(jié)點(diǎn)中包含了索引+數(shù)據(jù)row。如果一個(gè)查詢匹配的索引中已經(jīng)包含了要查詢的數(shù)據(jù),也就意味著不需要再回表查詢了。如果一個(gè)索引覆蓋(包含)了所有需要查詢的字段的值,我們就稱之為“覆蓋索引”。

    覆蓋索引的好處有很多,筆者簡(jiǎn)單說說:

  • 索引往往比原表小很多,再加上不用回表,覆蓋索引可以說解決了IO優(yōu)化的兩大難題:IO次數(shù)和IO數(shù)據(jù)量。
  • 索引是順序IO,比原表的隨機(jī)IO性能高得不是一星半點(diǎn)。
  • 對(duì)于InnoDB得聚簇索引來講,InnoDB的二級(jí)索引保存了行的主鍵值,若二級(jí)主鍵能覆蓋查詢,可以避免主鍵索引的二次查詢。
  • MySQL中只能用BTREE做覆蓋索引,因?yàn)楦采w索引的大前提是索引中必須存儲(chǔ)索引列的值,而hash、空間、全文索引等都不存儲(chǔ)索引列的值。如果使用的是覆蓋索引,我們?cè)趀xplain sql語句的時(shí)候會(huì)看到“Using index”的信息。

    書中給了一個(gè)比較典型的例子:

    首先給store_id和film_id兩列創(chuàng)建了一個(gè)組合索引“idx_store_id_film_id”

    然后執(zhí)行以下sql

    explain select store_id, film_id from inventory

    我們會(huì)發(fā)現(xiàn)結(jié)果就是“Using index”,即走進(jìn)了覆蓋索引

    有一種情況即使走進(jìn)了覆蓋索引依舊會(huì)回表:即索引覆蓋了where條件中的字段,但是沒有覆蓋到查詢的字段,即使where中的條件不滿足,也會(huì)回表將不滿足的行取出來然后過濾掉。好在MySQL 5.5(不含)之后的版本優(yōu)化了這個(gè)bug(MySQL 5.6采用了一種“索引條件推送”機(jī)制,感興趣可以了解下)。

    3.2.5 索引和排序

    我們?cè)趯?shí)際開發(fā)過程中,如果有排序的需求時(shí),可以將自己的排序sql explain一下。

    如果type=index,則說明MySQL使用了索引掃描來做排序(區(qū)別于Extra的“Using index”,別搞混了)。但是排序過程中不是覆蓋索引的話(上面有講),每一條索引都需要回表查詢一次,甚至效率比順序全表掃描都慢(尤其是IO密集型系統(tǒng))。

    設(shè)計(jì)索引的時(shí)候如果既能滿足排序,又能用于查找,那是再好不過的了。設(shè)計(jì)索引的時(shí)候也要盡量往這上面靠。當(dāng)且僅當(dāng)索引和order by子句里列順序一致,且列的排序方向(正序或倒序)一致時(shí),MySQL才能用索引對(duì)結(jié)果集進(jìn)行排序。書中作者建議對(duì)于不同方向的排序,可以通過建立冗余列存儲(chǔ)排序列的反轉(zhuǎn)串或者相反數(shù)來實(shí)現(xiàn)。

    對(duì)于關(guān)聯(lián)表查詢的排序,只有order by子句中的列全部在第一張表里的時(shí)候,才能用索引做排序。

    3.2.6 冗余索引和重復(fù)索引

    首先做下科普:

    一張表有AB兩列,我在A上創(chuàng)建兩個(gè)不同名索引就叫重復(fù)索引,這個(gè)是設(shè)計(jì)里堅(jiān)決不允許的,因?yàn)闆]有任何意義,還會(huì)降低性能。如果我已經(jīng)有了AB列的聯(lián)合索引,我再建個(gè)A列的索引,這就叫冗余索引(如果先建立的是BA而非AB,則A不叫冗余索引,自己去看最左匹配原則),這個(gè)在設(shè)計(jì)上是允許存在的。

    大多數(shù)情況下我們都不需要冗余索引,書中作者建議盡量去擴(kuò)展已有的索引而非創(chuàng)建新索引(因?yàn)镸ySQL會(huì)獨(dú)立維護(hù)每個(gè)索引,索引多了會(huì)降低性能)。但是在個(gè)別場(chǎng)景下,比如不希望擴(kuò)展已有索引導(dǎo)致索引變得太大而影響原有索引性能時(shí),可以添加冗余索引。

    這個(gè)擴(kuò)展原有索引導(dǎo)致索引太大的概念比較寬泛,比較簡(jiǎn)單的例子就是我已經(jīng)有了AB列的聯(lián)合索引,但是A列是個(gè)很長的varchar,那么我就可以將A列單獨(dú)做個(gè)索引,這樣就不會(huì)導(dǎo)致性能的急劇下降。

    還有一種未使用索引,是書中作者不推薦保留的,建議刪除。

    3.2.7 索引和鎖

    鎖的種類很多,什么樂觀鎖、悲觀鎖、排他鎖、共享鎖、行鎖、表鎖、記錄鎖、間隙鎖、臨鍵鎖、死鎖等等,筆者在本文中就不一一說明了,一來是篇幅太長,二來也和主題不符。既然是高性能MySQL,筆者希望將更多的筆墨放在MVCC(多版本并發(fā)控制)上。之前有個(gè)MySQL大拿同事為我們做了一次內(nèi)部技術(shù)分享,筆者也很樂意將其中的干貨整理并分享給大家,同時(shí)也可以根據(jù)弗曼學(xué)習(xí)法來測(cè)試下筆者到底掌握了沒有。

    這里只講一點(diǎn)書中作者提到的現(xiàn)有人知的知識(shí)點(diǎn):InnoDB在二級(jí)索引上使用共享讀鎖,但訪問主鍵索引需要排他寫鎖。這消除了使用覆蓋索引的可能性,并且使得select for update比lock in share mode或非鎖定查詢慢很多。(可能是筆者在實(shí)際場(chǎng)景中遇到的比較少,不是完全理解,總是少用select for update就對(duì)了)

    關(guān)于MVCC各位看官不要著急,我會(huì)另起一章專門講解。

    3.2.8 實(shí)戰(zhàn)索引設(shè)計(jì)

    書中作者在此處舉了一個(gè)在線約會(huì)網(wǎng)站組合搜索用戶索引設(shè)計(jì)的案例,引起了筆者的共鳴。因?yàn)楣P者在工作中遇到了極其相似的場(chǎng)景,并且在性能優(yōu)化的需求上被反復(fù)按在地上摩擦。在筆者以下寫的案例中可能與原書中場(chǎng)景有較大差別,但是其核心思想相同,如果對(duì)于看官有些許幫助,我不勝榮幸:

    有一張服務(wù)記錄record表,現(xiàn)在需要進(jìn)行多條件篩選的同時(shí)排序。

    首先考慮的事情是使用索引來排序還是檢索出數(shù)據(jù)后再排序:

    如果采用前者,那么會(huì)嚴(yán)格限制索引和查詢的設(shè)計(jì),因?yàn)槲覀儾黄谕谢乇淼牟僮?#xff0c;正如3.2.5中所述:“設(shè)計(jì)索引的時(shí)候如果既能滿足排序,又能用于查找,那是再好不過的了”。當(dāng)然這個(gè)實(shí)現(xiàn)起來很難。

    如果采用后者,那么實(shí)現(xiàn)起來很容易,但是性能會(huì)不盡人意。

    那么我們?cè)囋囉脮型瑯拥姆椒▽?duì)我們的索引和查詢進(jìn)行優(yōu)化(書中選擇了前者)。

    1. 支持多種過濾條件

    過濾條件有時(shí)間范圍,分類,操作人(們),Owner(s),狀態(tài)等。

    首先需要對(duì)以上過濾條件在where條件里出現(xiàn)頻率做個(gè)排序,然后評(píng)估它們的選擇性,然后綜合評(píng)估后來選擇誰做索引的最左前綴(出現(xiàn)頻率越高,選擇性越高的越往左排)。

    但是到這里書中講到了sex(性別)列的案例,作者將sex作為最左前綴,這個(gè)是反常識(shí)的,因?yàn)閟ex的選擇性是很低的(通常只有兩種:男或女),而索引設(shè)計(jì)的時(shí)候是不建議在選擇性低的列上創(chuàng)建索引的。作者說出了他的思考:sex出現(xiàn)頻率極高,且即使不帶這個(gè)篩選條件我們也可以通過sex in(男,女)來巧妙的規(guī)避,這樣和不加沒有什么區(qū)別,但是在加上sex篩選條件的時(shí)候就能篩選出去不少數(shù)據(jù)。(筆者感慨,即使掌握了書中知識(shí)也很難在實(shí)際應(yīng)用中想到這點(diǎn),當(dāng)然這個(gè)前提是我們需要用代碼加工查詢條件來規(guī)避不走sex索引的情況)

    當(dāng)然筆者工作中的場(chǎng)景沒有類似sex這樣的神仙字段,只好作罷。并且這種只適用于極少的枚舉值的情況,因?yàn)槟悴豢赡馨裞ountry(國家)列也搞個(gè)同樣的操作:不帶country篩選條件就去SELECT IN 200多個(gè)國家,瘋了。。。(每增加一個(gè)IN優(yōu)化器需要做的組合都會(huì)呈指數(shù)增加,不可濫用)

    同時(shí)書中也解釋了為什么總是把a(bǔ)ge(年齡)列放到組合索引的最后,因?yàn)槟挲g查詢大多數(shù)時(shí)候是范圍查詢(很少有程序猿用IN(10,11,…,99)去篩選10~99歲的用戶),我們要盡可能在此之前讓MySQL使用更多的索引列,因?yàn)镸ySQL優(yōu)化器的機(jī)制(書中沒講具體是什么機(jī)制,留作課題后續(xù)研究),所以我們要將類似于=或者IN一類的等值查詢放在前面,范圍查詢放在后面。生僻的篩選條件可加可不加,這里就不再浪費(fèi)筆墨。

    而以上的字段按照筆者上述的排序規(guī)則則是(從左至右):操作人(們)→ Owners → 分類 → 狀態(tài) → 時(shí)間范圍

    2. 避免多個(gè)范圍條件

    比較下下面兩個(gè)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結(jié)果的type是相同的(range),但是第二個(gè)是多個(gè)等值查詢,所以會(huì)比第一條快上很多,即使explain無法很好區(qū)分它們。

    MySQL無法同時(shí)使用兩個(gè)范圍查詢條件對(duì)應(yīng)的兩個(gè)索引,那如果存在“查詢age在10~99歲并且7天之內(nèi)登錄過的用戶”這個(gè)需求時(shí),該怎么辦呢?

    作者采用了曲線救國的方式,age仍然作為范圍查詢條件,然后在表中加入一個(gè)新列active,來把0每次登錄用戶都更新為1,并且將連續(xù)7天沒有登錄的用戶設(shè)置為0(筆者認(rèn)為是否需要再加一個(gè)計(jì)數(shù)列?)。最后巧妙的把范圍查詢變成了等值查詢。

    – 截止目前筆者已經(jīng)寫了9200+字,就這樣才勉強(qiáng)總結(jié)了此書一半,后續(xù)還會(huì)有很多精彩的內(nèi)容,筆者也會(huì)不忘初心,盡可能多得將自己所見所聞書寫進(jìn)來,先發(fā)稿一版,之后不斷迭代,碼字不易,若耐心的看官能看到這行文字,麻煩隨手點(diǎn)個(gè)贊,不勝感激! –

    總結(jié)

    以上是生活随笔為你收集整理的高性能MySQL(呕心沥血整理万字长文)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。