高性能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可能不是当下或未来的最优解,但是它是架构中的万金油,庞杂非单一的项目中总会有它的用武之地。

在此之前,请允许我给大家普及两个术语:

  1. OLTP (联机事务处理 On-Line Transaction Processing)
  2. OLAP (联机分析处理 On-Line Analytical Processing)

这是我们对自身系统的一个分类,你的系统是事务型的还是分析型的。我们常见的传统关系型数据库的应用几乎都是OLTP,这也是MySQL的擅长项。OLAP更多是一些报表系统,比如笔者所在项目用的就是PowerBI,上面运行着大量的复杂的查询,视图以及存储过程,当然现在PowerBI也存在着性能瓶颈,正计划用大数据组件如Spark、Flink来破局。

所以是否选用MySQL可以从以下维度考量:

  1. 是否是OLTP类型的应用
  2. 是否有更优解(技术选型是没有最优解的)
  3. 成员对于更优解和MySQL的熟悉程度(实施成本)

第三点确实是一个重要的考量依据,几乎所有的程序员或多或少都接触过MySQL,这个也会大幅降低团队的学习成本,因为你不能要求一个从未接触过Flink的团队一个月就交付出高质量的流处理平台,读过《人月神话》或者接触过十个女人一个月生产的甲方爸爸的小伙伴,应该深有体会。

对于更优解,我再啰嗦两句,这个更优解可能是一个新技术,你从来没有尝试在项目中应用的技术(毕竟谁都有第一次)。有一次内部培训中我问了我的Boss关于新技术选型的问题,我的Boss对于新技术的选型给出了以下建议:

  1. 这个技术是否有大厂背书
  2. 这个技术是否广泛应用
  3. 这个技术是否有活跃的社区
  4. 你是否做了足够的前置工作

如果同时满足以上四个条件,那么就可以大胆尝试了。

2.2 一次并不特别成功的技术选型

这一段和MySQL自身并无太大关系,只是笔者单纯想吐个槽而已,不感兴趣的同学可以自行跳过。

笔者项目使用的是JPA,而在实际应用过程中发现很多问题:

  1. 项目中的开发对于JPA or Hibernate的了解良莠不齐,导致大量多余查询的出现
  2. JPA N+1 问题频现
  3. 开发上生产前没有对语句进行explain,导致大量慢查询
  4. 有的服务引用了QueryDSL,而这是一个漏洞百出的框架
  5. JPQL对于union查询不支持,导致很多DEPENDENT SUBQUERY
  6. 繁杂的Entity关系

以上还是一小部分问题,并不是说JPA不好,JPA一直是一个优秀的ORM框架,但是同时也对使用者的要求较高。而产生问题的根本原因就是当初没有对团队成员实力有一个合理的评估,亦或是没有针对性的培训。

反观数据库的技术选型时,你自身或者你的团队对于被选择的产品(Oracle、MySQL、PowerBI、PostgreSQL等),不光是看产品本身,还要综合上你和你的团队对于这个数据库产品的了解,才能做出明智的选型。

我的Boss常常在面试部门架构师的时候会问:“有那么多可以实现的技术,你为什么选择了这个技术?”

有超过90%的人会回答:“因为我只会这个技术”,或者“这个技术是现在最流行的”。

很显然这个回答并不能让我的Boss满意,因为同样的话是说服不了客户的。

想象你在和众多优秀的公司一起BD(竞标)一个大项目,我们假设我们只做技术咨询而非交付,那么下面那一句话更容易打动客户?

  1. 我们选用了微服务框架,因为这是时下最热门的技术,且我只会这门技术
  2. 我们选用了微服务框架,因为结合上现有项目体量和未来需要的延展性,以及实施成本综合考量,这是目前众多技术中最适合您的方案

最后第二种说辞打动了客户,也帮助我的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三层架构:

  1. MySQL客户端
  2. MySQL核心服务
  3. 存储引擎

MySQL客户端我们并不用过多关注,因为它与其他很多的产品都雷同,连接处理、认证授权、安全等。

而核心服务这一层除了优化、缓存、函数外,存储过程、触发器、视图也在这一层,因为这些都是跨存储引擎的部分。

最底层是我们重点关注的一层,即存储引擎层,存储引擎只负责数据的存储和提取,MySQL核心服务则通过API与存储引擎通信。听起来很简单对吧?但是“数据的存储和提取”则是MySQL中的重中之重(不然数据库是干嘛的),这里面的学问很多,但核心是两点:

  1. 减少IO次数(提升性能)
  2. 高并发下的数据一致性

围绕着这核心的两点,就衍生出了很多复杂的数据结构、架构理论和加解锁算法。

减少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的全称,在介绍中举了一个典型的例子,即医院中会检测很多指标,还有一些指标甚至在设计表的时候都不得而知,为了应对将来可能陆续出现的检测指标,通过设计以下的表来记录指标

Entity Attribute Value
病人 血压 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,为了不导致业务中断,我们常见的方式有两种:

  1. 先在一台不提供服务的机器上alter table,然后和主库进行切换
  2. 通过“影子拷贝”,通过要求的表结构创建一张新表,然后将源表的数据迁移到新表,最后通过删表+重命名的方式交换两张表。这种方法已经被很多团队开发成为一个个的工具,常见的有FaceBook 的 “online schema change”,Shlomi Noach(不认识)的openark tollkit,以及Percona Toolkit。

对于修改某一列的操作,MODIFY COLUMN是会去重建整张表的,而ALTER COLUMN则不会。

对于书中记载的一些未经官方认证的骚操作笔者这里也不记录了,书中作者也坦言存在一定风险,data is valuable,对于数据的操作还是谨慎一点的好。

3.1.7 小结

原则是放之四海而皆准的东西,所以记住一些原则可能比一些细节更加重要,因为细节是volatile的

  1. 避免过度设计,性能优先
  2. 尽可能使用小而简单的数据类型,除非有确切的需要,应避免使用null值
  3. 尽可能使用相同数据类型存储相关的值
  4. 注意可变长字符串(varchar),因为可能会在临时表或者排序的时候按最大长度分配内存
  5. 尽量用整型定义标识列(笔者理解标识列就是类似于ID列一类能标识唯一性的列)
  6. ENUM和SET慎用(我估计大多数人也不用)

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支持的索引数据类型有很多:

  1. B树
  2. B+树
  3. hash 索引
  4. R树(空间数据索引)
  5. 全文索引
  6. 分形树索引

还有很多,因为索引是建立在存储引擎上的,所以不同的存储引擎可以根据自身特性实现不同的底层数据结构。

问的和用的最多的就是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平台评分以及出改进方案,赢得了一致好评),三星系统的评估基准如下(星星越多越好):

  1. 索引将相关记录放到一起则获得一星
  2. 如果索引中的数据顺序和查找中的排列顺序一致则获得二星
  3. 如果索引中的列包含了查询中需要的全部列则获得三星

书中对于三星系统的描述有点过于抽象,每个词汇都能看懂,但是连起来读就都不懂了。

为此笔者通过结合书中的context和大量查阅资料,将自己的理解记录下来:(不一定完全准确)

  1. 一星指的是以下的情况:

    根据书中原文:

    有的“专家”会建议“把where条件里面的列都建上索引”,这个建议是非常错误的,这样最好的情况下也只能是一星索引。

    我们可以推断,给必要的列建立适当的索引而非根据where条件的列都建上索引,即可满足一星的标准。

  2. 二星的标准和书中“5.3.4 选择合适的索引列顺序”这一节有着一定的关联性,即如果创建一个组合索引,那么索引首先会按照最左列进行排序,其次是第二列,如果select时候带上了多列的order by,那么我们组合索引列的顺序最好和order by保持一致。

  3. 三星索引的定义让人摸不着头脑,但是根据书中所载可以推断一二:

    有时如果无法设计一个三星索引,那么不如忽略掉WHERE子句,集中精力优化索引列的顺序,或者创建一个全覆盖索引。在多个列上建立独立的单列索引大部分情况下并不能提高MySQL的查询性能。

    由原文我们可以得出以下几点,设计一个三星索引的注意事项:

    1. 多个单列索引在大多数情况下都达不到三星标准
    2. 可以先从WHERE子句下手设计索引
    3. 优化索引列的顺序
    4. 再不济就设计全覆盖索引(这个在后面会介绍)

    三星索引也就是最优索引,同时书中也建议将选择性高的列放到组合索引的前面。所谓选择性高即重复数据少,区分程度高的列,这样索引才不会筛选出大量的重复数据。

小结:笔者认为三星系统是逐步递增完善的,每个星级的递增都会包含上一星级的优点,逐步完善直至设计出最优索引。而最优索引的设计没有一个放之四海而皆准的法则,还需要笔者和大家不断学习,视情况而定。

3.2.3 聚簇索引

这个网络上提到的太多了,笔者不想浪费太多笔墨在此,只讲重点:

聚簇索引并不是一种索引类型,而是数据存储的方式。大家熟知的InnoDB就是聚簇,而MyISAM就是非聚簇,两者区别就在于InnoDB在同一结构中保存了BTREE的索引+数据row(存放于叶子页中)。对于没有主键和唯一索引的表,InnoDB会隐式定义一个row_id来作为聚簇索引(你不给我我就自己造)。书中提到聚簇索引的缺点时引申出二级索引在引入聚簇后需要两次索引查找,并且用了大量的篇幅讲解二级索引,笔者这里就不做介绍了,感兴趣的可以自行百度“MySQL 二级索引”或者看下书中“5.3.5 聚簇索引”的后半部分。

3.2.4 覆盖索引

这是一个让笔者着迷的话题,我们知道在InnoDB BTREE的叶子节点中包含了索引+数据row。如果一个查询匹配的索引中已经包含了要查询的数据,也就意味着不需要再回表查询了。如果一个索引覆盖(包含)了所有需要查询的字段的值,我们就称之为“覆盖索引”。

覆盖索引的好处有很多,笔者简单说说:

  1. 索引往往比原表小很多,再加上不用回表,覆盖索引可以说解决了IO优化的两大难题:IO次数和IO数据量。
  2. 索引是顺序IO,比原表的随机IO性能高得不是一星半点。
  3. 对于InnoDB得聚簇索引来讲,InnoDB的二级索引保存了行的主键值,若二级主键能覆盖查询,可以避免主键索引的二次查询。

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(呕心沥血整理万字长文)相关推荐

  1. 最全整理 | 万字长文综述目标检测领域,您要的,都在这里!

    点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 在人体姿态估计前面的工作,一般还需要进行目标检测以提高最后姿态估计的准确度.那么这一次呢,站长就来跟大 ...

  2. 学习最新大厂付费视频时整理的万字长文+配图带你搞懂 MySQL

    万字长文+配图带你搞懂 MySQL MySQL SQL的介绍 SQL分类 MySQL语法 创建数据库 修改.删除.使用数据库 DDL查询数据表 DDL创建数据表 修改数据表结构 删除数据表 DML添加 ...

  3. 性能追击:万字长文30+图揭秘8大主流服务器程序线程模型 | Node.js,Apache,Nginx,Netty,Redis,Tomcat,MySQL,Zuul

    本文为<高性能网络编程游记>的第六篇"性能追击:万字长文30+图揭秘8大主流服务器程序线程模型". 最近拍的照片比较少,不知道配什么图好,于是自己画了一个,凑合着用,让 ...

  4. 呕心沥血整理出来的mysql执行流程,一定要看!

    我曾踏足山巅,也曾进入低谷,二者都令我受益良多. -----宝石骑士-塔里克 你有多了解mysql? 说到mysql,相信很多人对他都不陌生,尤其是后端开发和DBA,更是熟悉地不能再熟悉了,什么mys ...

  5. uiautomation遍历windows所有窗口_万字长文!滑动窗口看这篇就够了!

    大家好,我是小浩.今天是小浩算法 "365刷题计划" 滑动窗口系列 - 整合篇.之前给大家讲解过一些滑动窗口的题目,但未作系统整理. 所以我就出了这个整合合集,整合工作中除了保留原 ...

  6. 两万字长文总结,梳理 Java 入门进阶那些事

    两万字长文总结,梳理 Java 入门进阶那些事 先给大家看下完整的思维导图,也是这篇文章的主要脉络. Java从入门到进阶学习路线 主导三个项目,让我独当一面 能力提升你要怎么学 全篇总结 Java ...

  7. 万字长文剖析架构设计全攻略(上)

    正文开始前,先花大量笔墨推荐几个我工作中常用的思考框架.实践框架,后续文章中会使用这几种思考框架作为工具来描述.拆解.分析问题.当然你也可以使用到其它工作内容中,掌握几种利器,比无头苍蝇样做事效率会高 ...

  8. 两万字长文总结,梳理 Java 入门进阶哪些事(推荐收藏)

    两万字长文总结,梳理 Java 入门进阶哪些事(推荐收藏) 程序员小跃 2021-01-12 13:19:09  23  收藏 分类专栏: Java学习之路 文章标签: java 数据库 redis ...

  9. 兄弟姐妹们,我终于上岸了,喜获蚂蚁offer,定级p7,万字长文带你走完面试全过程

    前言 在今天,我收到了蚂蚁金服A级的实习录用offer. 从开始面试到拿到口头offer(四面技术+一面HR)战线大约拉了半个月, 从拿到口头offer到收到正式录用邮件大概又是半个月. 思前想后,决 ...

  10. 喜获蚂蚁offer,定级p7,面经分享,万字长文带你走完面试全过程

    前言 在今天,我收到了蚂蚁金服A级的实习录用offer. 从开始面试到拿到口头offer(四面技术+一面HR)战线大约拉了半个月, 从拿到口头offer到收到正式录用邮件大概又是半个月. 思前想后,决 ...

最新文章

  1. Codeforces Gym101246C:Explode 'Em All(DP + bitset)
  2. 平行志愿计算机检索原理,通俗图解平行志愿,让你明白平行志愿检索规则
  3. 2020-08-29
  4. 《营销云价值解读与场景实践》白皮书重磅首发,加码企业数字化营销!
  5. MySQL-入门安装
  6. el-popover超过固定高度后出现滚动条_「测绘精选」RTK测量不出现固定解的原因...
  7. mysql数据库备份工具expdb_expdp 备份数据库
  8. [Vue.js] 深入 -- 案例 - 购物车
  9. jinja2模板注入_Flask(Jinja2) 服务端模板注入漏洞
  10. 学了一年matlab,我到现在还不会读论文~
  11. c语言如何引用一维数组,C语言一维数组的定义和引用
  12. vscode必备常用插件
  13. Wallpaper Engine开机黑屏、休眠黑屏、不显示壁纸解决方法
  14. Douyin-Bot 项目优化-改进(二),主播昵称识别+数据库储存
  15. 动态修改域名解析服务器(DDNS)
  16. 经典PID在智能自寻迹小车中的应用分析
  17. 如何生成一维条码图片
  18. javax.mail发送邮件(带附件)
  19. 如何进行Modbus 通讯测试
  20. Qt设计师-提升法(自定义部件)“提升为”

热门文章

  1. debug使用方法(概念篇)
  2. 推荐几个前端模板下载站
  3. scratch编程作品展示
  4. IBM DB2百度云下载
  5. cad图纸怎么看懂_教你看懂CAD图纸
  6. 分类与聚类的本质区别
  7. ApolloStudio高手之路(4):用Python以最轻便的方式进行金橙子激光打标板卡二次开发(以EzCad2为载体二次开发)
  8. 宗地图绘制要求和规范_地籍图、宗地图、房产图的制图规范
  9. 微信多开软件苹果版_微信多开教程—Mac版amp;Win版
  10. MYSQL 中取拼音首字母的函数