起航学习网

- 让每个人都能学到最前沿新知识、新技能!
起航学习网
当前位置: 起航学习网 > 短期培训 > 编程语言 > 如何了解MySQL数据库原理

如何了解MySQL数据库原理

时间:2018-03-08 20:46:03来源:编程网 作者:IT培训网 已有: 名学员访问该课程

前言:然而当每个子团队将子问题解决了,整个系统的问题就解决了么?你可以想象你将一辆整车拆成零件,然后再组装起来的过程,你就可以想象拆虽然不容易,合则更难,需要各种标准,各种流水线,才能将零件组装称为车。

以MySQL为例,带你从原理上理解那些所谓的数据库军规。

本文是微服务实战系列文章的第三篇,前两篇链接如下:

  • 《微服务与持续集成:拆之前要先解决合的问题》

如何了解MySQL数据库原理_www.epx365.cn

一、持续集成对于微服务的意义:拆之前要先解决合的问题

在很多微服务化的文章中,很少会把持续集成放在第一篇,因为大多数的文章都会讲如何拆的问题,例如拆的粒度,拆的时机,拆的方式。
 
为什么需要拆呢?因为这是人类处理问题的本质方式:将一个大的复杂问题,变成很多个小问题解决。
 
所以当一个系统复杂到一定程度,当维护一个系统的人数多到一定程度,解决问题的难度和沟通成本大大提高,因而需要拆成很多个工程,拆成很多个团队,分而治之。
 
然而当每个子团队将子问题解决了,整个系统的问题就解决了么?你可以想象你将一辆整车拆成零件,然后再组装起来的过程,你就可以想象拆虽然不容易,合则更难,需要各种标准,各种流水线,才能将零件组装称为车。
 
我们先来回顾一下拆的过程。
 
最初的应用大多数是一个单体应用。
 

如何了解MySQL数据库原理_www.epx365.cn

 

一个Java后端,后面跟一个数据库,基本上就搞定了。
 
随着系统复杂度的增加,首先Java程序需要做的是纵向的拆分。
 

如何了解MySQL数据库原理_www.epx365.cn

首先最外面是一个负载均衡,接着是接入的Nginx,做不同服务的路由。

不同的服务拆成独立的进程,独立部署,每个服务使用自己的数据库和缓存,解决数据库和缓存的单点瓶颈。
 
数据库使用一主多从的模式,进行读写分离,主要针对读多写少的场景。
 
为了承载更多的请求,设置缓存层,将数据缓存到Memcached或者Redis中,增加命中率。
 
当然还有些跨服务的查询,或者非结构化数据的查询,引入搜索引擎,比关系型数据库的查询速度快很多。
如何了解MySQL数据库原理_www.epx365.cn

 

在高并发情况下,仅仅纵向拆分还不够,因而需要做真正的服务化。
 
一个服务化的架构如图所示。
 
首先是接入层,这一层主要实现API网关和动态资源和静态资源的分离及缓存,并且可以在这一层做整个系统的限流。
 
接下来是Web层,也就是controller,提供最外层的API,是对外提供服务的一层。
 
下面是组合服务层,有时候被称为编排层,Compose层,是实现复杂逻辑的一层。
 
下面是基础服务层,是提供原子性的基本的逻辑的一层,他下面是缓存,数据库。
 
服务之间需要治理,需要相互发现,所以一般会有Dubbo或者Spring Cloud一样的框架。
 
对所有的服务,都应该有监控告警,及时发现异常,并自动修复或者告警运维手动修复。
 
对于所有的服务的日志,应该有相同的格式,收集到一起,称为日志中心,方便发现错误的时候,在统一的一个地方可以debug。
 
对于所有的服务的配置,有统一的管理的地方,称为配置中心,可以通过修改配置中心,下发配置,对于整个集群进行配置的修改,例如打开熔断或者降级开关等。
 
通过简单的描述,大家可以发现,从一个简单的单体应用,变成如此复杂的微服务架构,除了关心怎么拆的问题,还必须关注:
  • 如何控制拆的风险

  • 如何保证代码质量

  • 如何保证功能不变,不引入新的Bug

 
答案当然就是集成,从一开始就集成,并且不断的集成,反复的将拆分的模块重新组合,看看是否能够顺利组合起来,并且保证功能的不变。
 
要是不没事儿就组合一下,天知道几个月以后还能不能合的起来。
 
别忘了程序是人写的,你和你媳妇长时间不沟通都对不上默契,别说两个程序员了。
二、持续集成就是不断的尝试在一起
集成就是在一起。
 

如何了解MySQL数据库原理_www.epx365.cn

为什么需要一个统一的代码仓库Git来做代码管理呢?是为了代码集成在一起。

为什么需要进行构建build呢?就是代码逻辑需要集成在一起,编译不出错。
 
为什么要单元测试呢?一个模块的功能集成在一起能够正确工作。
 
为什么需要联调测试Staging环境呢?需要将不同模块之间集成在一起,在一个类生产的环境中进行测试。
 
最终才是部署到生产环境中,将所有人分开做的工作才算真正的合在了一起。
 

 

如何了解MySQL数据库原理_www.epx365.cn

 

 
持续集成就是制定一系列流程,或者一个系列规则,将需要在一起的各个层次规范起来,方便大家在一起,强迫大家在一起。
三、持续集成、持续交付、持续部署、敏捷开发、DevOps都啥关系?
这些概念都容易混淆,他们之间是什么关系呢?

如何了解MySQL数据库原理_www.epx365.cn

 

敏捷开发Agile是一种开发流程,是一种快速迭代的开发流程,每个开发流程非常短,长到一个月,短到两个星期,就会是一个周期,在这个周期中,每天都要开会同步,每天都要集成。正是因为周期短,才需要持续的做这件事情,如果一个开发周期长达几个月,则不需要持续的集成,最后留几个星期的集成时间一起做也是可以的,但是这样就不能达到互联网公司的快速迭代,也是我们常常看到传统公司的做法。
 
持续集成往往指对代码的提交,构建,测试的过程,也就是上述的在一起的过程。
 
持续交付是指将集成好的交付物,例如war、jar或者容器镜像,部署在联调环境,或者预发环境的过程。
 
持续部署是指将交付物持续部署在生产环境的过程。
 
我们常说CI/CD,CD有时候指的是Delivery交付,有的是指Deployment部署,对于非生产环境,自动部署是没有问题的,对于生产环境,往往还是需要有专人来进行更为严肃的部署过程,不会完全的自动化。
 
接下来就是DevOps,DevOps不只是CI/CD,除了技术和流程,还包含文化。例如容器化带来的一个巨大的转变是,原来只有运维关心环境的部署,无论是测试环境,还是生产环境,都是运维搞定的,而容器化之后,需要开发自己写Dockerfile,自己关心环境的部署。因为微服务之后,模块太多了,让少数的运维能够很好的管理所有的服务,压力大,易出错,然而开发往往分成很多的团队,每个模块自己关心自己的部署,则不易出错,这就需要运维一部分的工作让研发来做,需要研发和运维的打通,如果公司没有这个文化,研发的老大说我们不写Dockerfile,则DevOps是搞不定的。
四、从一个持续集成的日常,看上述的几个概念如何实践

 

如何了解MySQL数据库原理_www.epx365.cn

 

 
这是一个持续集成的流程,但是运行起来更加的复杂。
 
首先,项目开发的流程使用的是Agile,用常见的scrum为例子。
 

 

如何了解MySQL数据库原理_www.epx365.cn

 

 
每天早上第一件事情,就是开站会standup meeting,为什么要站着呢?因为时间不能太长,微服务的一个模块,大概需要5-9人的团队规模,如果团队规模太大了,说明服务应该进行拆分了,这个团队规模,是能够保证比较短的时间之内过完昨天的状态的。
 
一定要大家一起开,而不要线下去更新Jira,虽然看起来一样,但是执行起来完全不一样。只有大家一起开,一起看燃尽图,一起说我昨天做了什么,今天打算做什么,有什么阻碍,才能够让大家都了解情况,不要期望大家会去看别人的Jira,经验告诉你,不会的。
 
而且这个站会对于开发是比较大的压力,例如你的一个功能block了依赖方的开发,在会议上会暴露出来,大家都知道这件事情了,一天block,两天block,第三天你都不好意思去说了,这会强迫你将大任务,比如原来写1周干一件什么事情,写成小时级别,这样每天你都有的说,昨天完成了一个task,而不是周只在那里说干同样一件事情,而且一旦有了block,team lead会知道这件事情,会帮你赶紧解决这个事情,推进整个项目的进展。让一个技术人员在团队面前承认这件事情我尝试了几天,的确搞不定了,也是一种压力。
 
站会中的内容其实在前一天晚上就要开始准备了。
 
持续集成要求每天都提交代码,这样才能降低代码集成的风险,不能埋头写一周一起提交,这样往往集成不成功。怎么样才能鼓励每天都提交代码呢?一个就是第二天的站会,你这个功能代码提交了,单元测试通过了,第二天才能说做完了,否则不算,这就逼得你,将大任务拆成小任务,每天都多次提交。
 
而且Git的提交方式,是后提交者有责任去merge,保证代码的编译通过和测试通过,你会发现,如果你不及时提交,等你改了一大片代码,别人都提交完了,这一大片的冲突都是你来merge,测试用例不通过的你来fix,所以逼的你有一个小的功能的改动,就尽早提交,pull一下发现没有人提交,赶紧提交。
 
提交不是马上进入主库,而是需要代码审核,这是把控代码质量的重要的环节。
 
代码质量的控制往往每个公司都有文档,甚至你可以从网上下载一篇很长很长的Java代码规范。但是我们常常看到的例子是,规范是有,但是虱子多了不咬人,规范太多的,谁也记不住,等于没有规范。
 
所以建议将复杂的规范通过项目组内部的讨论,简化为简单的10几条军规,深入人心,大家都容易记住,并且容器执行。
 
代码审核往往需要注意下面的几方面:
 
  • 代码结构:整个项目组应该规定统一的代码组织结构,使得每个开发拿到另一个人的代码,都能看的熟悉的面孔。这也是Scrum中提倡的每个开发之间是可替代的,当一个模块有了阻碍,其他人是可以帮上忙的。至于核心的逻辑,估计审核人员也来不及细看,这不要紧,核心逻辑是否通过,不能靠眼睛,要靠测试。

  • 有没有注释,尤其是对外的接口,应该有完善的注释,方便自动生成接口文档。

  • 异常的处理,是否抛出太过宽泛的异常,是否吞掉异常,是否吞掉异常的日志等。

  • 对于pom是否有修改,引入了新的jar。

  • 对于配置文件是否有修改,对外访问是否设置超时。

  • 对于数据库是否有修改,是否经过DBA审核。

  • 接口实现是否幂等,因为Dubbo和Spring Cloud都会重试接口。接口是否会升级,是否带版本号。

  • 是否有单元测试。

 
当然还有一些不容易一眼看出来的,可以通过一段时间通过统一的代码review,来修改这些问题。
 
  • 某个类代码长度过长

  • 设计是否合理,高内聚低耦合

  • 数据库设计是否合理

  • 数据库事务是否使用合理

  • 代码是否有明显的阻塞

 
代码审核完毕之后提交上去之后,一个是要通过静态代码审查,可以发现一些可能带来代码风险的问题,例如异常过于宽泛等。
 
在就是要通过单元测试。我们应该要求每个类都要有单元测试,并且单元测试覆盖率要达到一定的指标。单元测试要有带Mock的模块内的集成测试。
 
在编译过程中会触发单元测试,单元测试不通过,已经代码覆盖率,都会统计后发邮件,抄送所有的人,这对于研发来讲又是一个压力。
 
当有一天你的提交break掉了测试,或者代码覆盖率很低,则就像通报批评一样,你需要赶紧去修改。
 
单元测试完毕之后,就会上传成果物,或者是war或者是jar,一般会用nexus,因为有版本号,有md5,可以保证安装在环境中的就是某个版本的某个包,我们还遇到过有使用FTP的,这样一个是很难保证版本号的维护,升级和回滚比较难弄,另一个是没有md5,很可能包不完整都有可能的,而且一旦发生,很难发现。
 
如果使用了容器,则还需要编译Dockerfile,使用Docker镜像作为交付,能够实现更好的环境一致性,保证原子的升级和回滚。
 
每天下班前,当天的代码需要提交到库中去,晚上会做一次统一的环境部署和集成测试。
 
每天晚上凌晨,会有自动化的脚本将Docker镜像通过编排部署一个完整的环境,然后跑集成测试用例,集成测试用例应该是基于API的,很多的公司是基于UI的,这样由于UI变化太快,还有UI不能覆盖所有的场景,所以还是建议UI和API分离,通过API进行集成测试,有了每天的测试,才能保证每天晚上的版本都是可以交付的版本,也保证我们微服务拆分的时候,尽管改了很多,不会因为新的修改,破坏掉原来能够通过的测试用例,保证不会有了新的,坏了旧的。
 
这个集成测试或者叫回归测试每天晚上都做,都是在一个全新的环境中,这就是持续部署和持续交付。
 
如果某一天测试不通过,则会发出邮件来,是因为当天谁的哪个提交,导致测试不通过,抄送所有人,这是另一个压力。
 
所以第二天的站会上,昨天你完成了哪些功能,是否提交了,是否完成了单元测试,是否通过了集成测试,就都知道了,你需要给大家一个解释,然后进入到新一天的开发。
 
到了两周,一个周期完毕,可以上线到生产环境了,可以通知有权限的运维进行操作,但是也是通过自动化的脚本进行部署的。
 
这就是整个过程,层层保证质量,从中可以看到,敏捷开发,持续集成,持续交付,持续部署,DevOps是互相联系的,少了哪个,流程都玩不转。
五、有关代码结构

 

如何了解MySQL数据库原理_www.epx365.cn

 

代码结构往往包括:
 
  • API接口包

  • 访问外部服务包

  • 数据库DTO

  • 访问数据库包

  • 服务与商务逻辑

  • 外部服务

 
如果使用Dubbo RPC,则API接口往往在一个单独的jar里面,被服务端和客户端共同依赖,但是使用了Spring Cloud的restful方式就不用了,只要在各自的代码里面定义就可以了,会变成json的方式传递,这样的好处是当jar有多个版本依赖,需要升级的时候,关系非常复杂,难以维护,而json的方式比较好的解决了这个问题。
 
这个模块提供了哪些接口,只要到API接口这个package下面找就可以了。因为无论是Dubbo还是Spring Cloud,接口的调用都会重试,因而接口需要实现幂等。
 
访问外部服务的包,这将所有对外的访问独立出来,好处一是可以抽象出来,在服务拆分的时候,可能会用到,例如原来支付的逻辑在下单的模块中,要讲支付独立出来,则会有一个抽象层,涉及到老的支付方式,还是调用本模块中的逻辑,涉及到新接入的支付方式使用远程调用,有了这一层方便的多。好处二是可以实现熔断,当被调用的服务不正常的时候,在这里可以返回托底数据。好处三是可以实现Mock,这样对于单元测试来讲非常好,不用依赖于其他服务,就可以自己进行测试。
 
DTO和访问数据库的包,看到了这些数据结构,会帮助程序员快速掌握代码逻辑,不知道大家有没有这个体验,你去看一个开源软件的代码,首先要看的是他的数据结构,数据结构和关系看懂了,代码逻辑就比较容易懂了,如果数据结构没看懂,则光看逻辑,就容易云里雾里的。
 
还有就是核心的代码逻辑和对接口的实现。在这里面是软件代码设计的内功所在,但是却不是流程能够控制的。
六、有关接口设计规范
上面也说过了,Dubbo和Spring Cloud会对接口进行重试,因而接口需要保持幂等。也即多次调用,应该产生一致的结果,例如转账1元,因为调用失败或者超时重试的时候,最终结果还应该是转账1元,而非调用两次变成转账2元。
 
幂等判断尽量提前,可以使用ID作为判断条件。
 
接口的实现应该尽量避免阻塞,可以使用异步方式提升性能。
 
接口应该包括能够区分不同情况的异常,而非抛出宽泛的Exception,不能吞掉异常。
 
接口的实现要有足够的容错性,以及对不同版本的兼容性。当要引入新接口的时候,使用先添加,后删除的方式。
 
接口应该有良好的注释。
七、有关代码设计
对于代码的设计,这里常说的就是SOLID原则。
 
S是单一责任原则,如果你的代码中有一个类行数太长,可能你需要重新审视一下,是不是这个类承担了过多的责任。
 
O是开放关闭原则,比较拗口,对扩展开放,对修改关闭。思想是对于代码的直接修改是非常危险的事情,因为你不知道这段代码原来被谁用了,而且当时候用的时候,面临的情况都是怎样的。因而不要贸然修改一段代码,而是选择用接口进行调用,用实现进行扩展的方式进行。当你要实现一段新的功能的时候,不要改原来的代码,也不要if-else,而是应该扩展一种实现,让原来的调用的代码逻辑还是原来的,在新的情况下使用新实现的代码逻辑。
 
L是里氏替换原则,如果基于接口进行编程,则子类一定要能够扩展父类的功能,如果不能,说明不应该继承与这个接口。例如你的实现的时候,发现接口中有一个方法在你这里实在对应不到实现,不是接口设计的问题,就是你不应该继承这个接口,绝不能出现not implemented类似之类的实现方法。
 
I是接口隔离原则,接口不应该设计的大而全,一个接口暴露出所有的功能,从而使得客户端依赖了自己不需要的接口或者接口的方法。而是应该讲接口进行细分和提取,而不应该将太过灵活的参数和变量混杂在一个接口中。
 
D是依赖倒置原则,A模块依赖于B模块,B模块有了修改,反而要改A,就是依赖的过于紧密的问题。这就是常说的,你变了,我没变,为啥我要改。如果基于抽象的接口编程,将修改隐藏在后面,则能够实现依赖的解耦。
 
以上是模块内部常见的设计原则,对于模块之间,则是对于云原生应用常说的十二原则。
 

 

如何了解MySQL数据库原理_www.epx365.cn

 

 
详情可看《云原生时代下的12-factor应用与实践》。
八、有关配置文件
在代码仓库中,还需要管理的是配置文件,往往在src/main/resource下面。
 
配置的管理原来多使用profile进行管理,对于dev、test、production使用不同的配置文件。
 
然而当配置非常多的时候,比较的痛苦,而且配置不断的修改,每次上线各种配置需要仔细的核对,眼睛都花了,才敢上线。
 
我们可以将配置分为下面的三类:
 
  • 内部配置项(启动后不变,改变需要重启)

  • 集中配置项(配置中心,可动态下发)

  • 外部配置项(外部依赖,和环境相关)

 
在梳理配置的时候,可以按着三类归类,分门别类管理。
 
在使用了容器之后,很多的内部配置项可固化在配置文件中,放在容器镜像中,需要启动的时候修改的,则通过环境变量,在启动容器的时候,在编排文件中进行修改。
 
依赖的内部服务的地址,在容器平台Kubernetes里面,可以通过配置服务名进行服务发现,仅仅在配置文件中配置名称就可以了,不用配置真实的地址,Kubernetes可以根据不同的环境,不同的namespace自动关联好,大大简化了配置。当然也可以用服务中心Dubbo和Spring Cloud做内部服务的相互发现。
 
依赖的外部服务的地址,例如MySQL、Redis等,往往不同的环境不同,也可以通过配置Kubernetes外部服务名的方式进行,而不用一一核对,担心测试环境连上了生产环境的IP地址。
 
还有一些集中配置项,需要动态修改的,例如限流,降级的开关等,需要通过统一的配置中心进行管理。
九、有关数据库版本
代码可以很好的版本化,应用也可以用镜像进行原子化的升级和回滚。
 
唯一比较难做到的就是数据库如何版本化管理。
 
有一个工具Flyway可以比较好的做这件事情。
 
在代码中,Flyway需要有以下的结构:
 
  • 在src/db/migration中有SQL文件,命名规则,如:V1__2017_4_13.sql ,V开头+版本号+双下划线+描述,后缀为sql。

  • 增加flyway的java类,实现migration方法。

在数据库中,Flyway会自动增加SCHEME_VERSION表。
 
当服务启动的时候,Java类的migration方法会被调用,它会按照指定路径中sql语句的版本号进行排序并且按照这个排序去执行,当每一个SQL文件被执行后,元数据的表就会按照格式进行更新。
 

 

如何了解MySQL数据库原理_www.epx365.cn

 

 
当服务重启的时候,Flyway再次扫描SQL的时候,它就会检查元数据表中迁移版本,如果要执行的迁移脚本的版本小于或者等于当前版本,Flyway将会忽略,不再重复执行。
 
但是Flyway从来不解决数据库升级和回滚的代码兼容性问题。
 
太多的人问这个问题了,代码可以灰度发布,数据库咋灰度?代码升级了,发现不对可以回滚,数据库咋回滚。
 
如果可以停服的话,自然是使用数据库快照备份的方式进行回滚了。
 
如果不可以停服,没办法,只有在代码层面做兼容性。每次涉及数据库升级的都是大事情,代码当然应该有个开关,保证随时可以切回原来的逻辑。

 

 

  • 如何了解MySQL数据库原理_www.epx365.cn

数据库永远是应用最关键的一环,同时越到高并发阶段,数据库往往成为瓶颈,如果数据库表和索引不在一开始就进行良好的设计,则后期数据库横向扩展,分库分表都会遇到困难。

对于互联网公司来讲,一般都会使用MySQL数据库。

一、数据库的总体架构

我们首先来看MySQL数据的总体架构如下:

如何了解MySQL数据库原理_www.epx365.cn

这是一张非常经典的MySQL的系统架构图,通过这个图可以看出MySQL各个部分的功能。

当客户端连接数据库的时候,首先面对的是连接池,用于管理用户的连接,并会做一定的认证和鉴权。

连接了数据库之后,客户端会发送SQL语句,而SQL接口这个模块就是来接受用户的SQL语句的。

SQL语句往往需要符合严格的语法规则,因而要有语法解析器对语句进行语法解析,解析语法的原理如同编译原理中的学到的那样,从语句变成语法树。

对于用户属于的查询可以进行优化,从而可以选择最快的查询路径,这就是优化器的作用。

为了加快查询速度,会有查询缓存模块,如果查询缓存有命中的查询结果,查询语句就可以直接去查询缓存中取数据。

上面的所有的组件都是数据库服务层,接下来是数据库引擎层,当前主流的数据库引擎就是InnoDB。

对于数据库有任何的修改,数据库服务层会有binary log记录下来,这是主备复制的基础。

对于数据库引擎层,一个著名的图如下:

如何了解MySQL数据库原理_www.epx365.cn

在存储引擎层,也有缓存,也有日志,最终数据是落到盘上的。

存储引擎层的缓存也是用于提高性能的,但是同数据库服务层的缓存不同,数据库服务层的缓存是查询缓存,而数据库引擎层的缓存读写都缓存。数据库服务层的缓存是基于查询逻辑的,而数据库引擎引擎的缓存是基于数据页的,可以说是物理的。

哪怕是数据的写入仅仅写入到了数据库引擎层中的缓存,对于数据库服务层来讲,就算是已经持久化了,当然这个时候会造成缓存页和硬盘上的页的数据的不一致,这种不一致由数据库引擎层的日志来保证完整性。

所以数据库引擎层的日志和数据库服务层的也不同,服务层的日志记录的是一个个的修改逻辑,而引擎层的日志记录的是缓存页和数据页的物理差异。

二、数据库的工作流程

在收到一个查询的时候,MySQL的架构中的各个组件是如此工作的:

如何了解MySQL数据库原理_www.epx365.cn

客户端同数据库服务层建立TCP连接,连接管理模块会建立连接,并请求一个连接线程。如果连接池中有空闲的连接线程,则分配给这个连接,如果没有,在没有超过最大连接数的情况下,创建新的连接线程负责这个客户端。

在真正的操作之前,还需要调用用户模块进行授权检查,来验证用户是否有权限。通过后,方才提供服务,连接线程开始接收并处理来自客户端的SQL语句。

连接线程接收到SQL语句之后,将语句交给SQL语句解析模块进行语法分析和语义分析。

如果是一个查询语句,则可以先看查询缓存中是否有结果,如果有结果可以直接返回给客户端。

如果查询缓存中没有结果,就需要真的查询数据库引擎层了,于是发给SQL优化器,进行查询的优化。如果是表变更,则分别交给insert、update、delete、create、alter处理模块进行处理。

接下来就是请求数据库引擎层,打开表,如果需要的话获取相应的锁。

接下来的处理过程就到了数据库引擎层,例如InnoDB。

在数据库引擎层,要先查询缓存页中有没有相应的数据,如果有则可以直接返回,如果没有就要从磁盘上去读取。

当在磁盘中找到相应的数据之后,则会加载到缓存中来,从而使得后面的查询更加高效,由于内存有限,多采用变通的LRU表来管理缓存页,保证缓存的都是经常访问的数据。

获取数据后返回给客户端,关闭连接,释放连接线程,过程结束。

三、数据库索引的原理

在整个过程中,最容易称为瓶颈点的是数据的读写,往往意味着要顺序或者随机读写磁盘,而读写磁盘的速度往往是比较慢的。

如果加快这个过程呢?相信大家都猜到了就是建立索引。

为什么索引能够加快这个过程呢?

相信大家都逛过美食城,里面众多家餐馆琳琅满目,如果你不着急呢,肚子不饿,对搜索的性能没有要求,就可以在商场里面慢慢逛,逛一家看一家,知道找到自己想吃的餐馆。但是当你饿了,或者你们约好了餐馆,你一定想直奔那个餐馆,这个时候,你往往会去看楼层的索引图,快速的查找你目标餐馆的位置,找到后,直奔主题,就会大大节约时间,这就是索引的作用。

所以索引就是通过值,快速的找到它的位置,从而可以快速的访问。

索引的另外一个作用就是不用真正的查看数据,就能够做一些判断,例如商场里面有没有某个餐馆,你看一下索引就知道了,不必真的到商场里面逛一圈,再如找出所有的川菜馆,也是只要看索引就可以了,不用一家一家川菜馆跑。

那么在MySQL中,索引是如何工作的呢?

MySQL的索引结构,往往是一棵B+树。

一棵M阶B+树具有如下的性质:

  1. 节点分索引节点和数据节点。索引节点相当于B树的内部节点,所有的索引节点组成一棵B树,具有B树的所有的特性。在索引节点中,存放着Key和指针,并不存放具体的元素。数据节点相当与B树的外部节点,B树的外部节点为空,在B+树中被利用了起来,用于存放真正的数据元素,里面包含了Key和元素的其他信息,但是没有指针。

  2. 整棵索引节点组成的B树仅仅用来查找具有某个Key的数据元素位于哪个外部节点。在索引节点中找到了Key,事情没有结束,要继续找到数据节点,然后将数据节点中的元素读出来,或者二分查找,或者顺序扫描来寻找真正的数据元素。

  3. M这个阶数仅仅用来控制索引节点部分的度,至于每个数据节点包含多少元素,与M无关。

  4. 另外有一个链表,将所有的数据节点串起来,可以顺序访问。

这个定义的比较抽象,我们来看一个具体的例子。

如何了解MySQL数据库原理_www.epx365.cn

从图中我们可以看出,这是一个3阶B+树,而一个外部数据节点最多包含5项。如果插入的数据在数据节点,如果不引起分裂和合并,则索引节点组成的B树就不会变。

如果在71到75的外部节点插入一项76,则引起分裂,71、72、73成为一个数据节点,74、75、76成为一个数据节点,而对于索引节点来讲相当于插入一个Key为74的过程。

如果在41到43的外部节点中删除43,则引起合并,41、42、61、62、63合并成一个节点,对于索引节点来讲,相当于删除Key为60的过程。

查找的时候,由于B+树层高很小,所以能够比较快速的定位,例如我们要查找值62,在根节点发现大于40则访问右面,小于70则访问左面,大于60则访问右面,在叶子节点的第二个,就找到了62,成功定位。

在MySQL的InnoDB中,有两种类型的B+树索引,一种称为聚簇索引,一种称为二级索引。

聚簇索引的叶子节点就是数据节点,往往是主键作为聚簇索引,二级索引的叶子节点存放的是KEY字段加主键值。因而通过二级索引访问数据,要访问两次索引。

如何了解MySQL数据库原理_www.epx365.cn

还有一种索引的形式称为组合索引,或者复合索引,可以在多个列上建立索引。

如何了解MySQL数据库原理_www.epx365.cn

这种索引的排序规则为,先比较第一列,在第一列相等的情况下,比较第二列,以此类推。

四、数据库索引的优缺点

数据库索引的优势最明显的就是减少I/O,下面分析几种场景。

对于=条件的字段,可以直接通过查找B+树的方式,通过很少的硬盘读取次数(相当于B+树层高),就能够到达叶子节点,然后直接定位到数据的位置。

对于范围的字段,由于B+树里面都是排好序的,范围可以很快的通过树进行定位。

同理对于orderby、group by、distinct/max、min,由于B+树是排好序的,也是能够很快的得到结果的。

还有一个常见的场景称为索引覆盖数据。例如A, B两个字段作为条件字段,常出现A=a AND B=b,同时select C、D时候,往往会建联合索引(A、B),是一个二级索引,所以搜索的时候,通过二级索引的B+树能够很快的找到相应的叶子节点和记录,但是记录中有的是聚簇索引的ID,所以还需要查找一次聚簇索引的B+树,找到真正的表中的记录,然后在记录中,将C、D读取出来。如果建立联合索引的时候为(A、B、C、D),则在二级索引的B+树中就有了所有的数据,可以直接返回了,减少了一次搜索树的过程。

当然索引肯定是有代价的,天下没有免费的午餐。

索引带来的好处多是读的效率的提高,而索引带来的代价就是写的效率的降低。

插入和修改数据,都有可能意味着索引的改变。

插入的时候,往往会在主键上建设聚簇索引,因而主键最好使用自增长,这样插入的数据就总是在最后,而且是顺序的,效率比较高。主键不要使用UUID,这样顺序比较随机,会带来随机的写入,效率比较差。主键不要使用和业务有关,因为与业务相关意味着会被更新,将面临着一次删除和重新插入,效率会比较差。

通过上面对于B+树的原理的介绍,我们可以看出B+树的分裂代价还是比较大的,而分裂往往就产生于插入的过程中。

而对于数据的修改,则基本相当于删除再插入,代价也比较大。

对于一些字符串的列的二级索引,往往会造成随机的写入和读取,对I/O的压力也比较大。

五、解读数据库军规背后的原理

了解了这两种索引的原理,我们就能够解释为什么很多所谓的数据库的军规长这个样子了。下面我们来一一解释。

什么情况下应该使用组合索引而非单独索引呢?

假设有条件语句A=a AND B=b,如果A和B是两个单独的索引,在AND条件下只有一个索引起作用,对于B则要逐个判断,而如果使用组合索引(A、B),只要遍历一棵树就可以了,大大增加了效率。但是对于A=a OR B=b,由于是或的关系,因而组合索引是不起作用的,因而可以使用单独索引,这个时候,两个索引可以同时起作用。

为什么索引要有区分度,组合索引中应该讲有区分度的放在前面?

如果没有区分度,例如用性别,相当于把整个大表分成两部分,查找数据还是需要遍历半个表才能找到,使得索引失去了意义。

如果有组合索引,还需要单列索引吗?

如果组合索引是(A、B),则对于条件A=a,是可以用上这个组合索引的,因为组合索引是先按照第一列进行排序的,所以没必要对于A单独建立一个索引,但是对于B=b就用不上了,因为只有在第一列相同的情况下,才比较第二列,因而第二列相同的,可以分布在不同的节点上,没办法快速定位。

索引是越多越好吗?

当然不是,只有在必要的地方添加索引,索引不但会使得插入和修改的效率降低,而且在查询的时候,有一个查询优化器,太多的索引会让优化器困惑,可能没有办法找到正确的查询路径,从而选择了慢的索引。

为什么要使用自增主键?

因为字符串主键和随机主键会使得数据随机插入,效率比较差,主键应该少更新,避免B+树和频繁合并和分裂。

为什么尽量不使用NULL?

NULL在B+树里面比较难以处理,往往需要特殊的逻辑进行处理,反而降低了效率。

为什么不要在更新频繁的字段上建立索引?

更新一个字段意味着相应的索引也要更新,更新往往意味着删除然后再插入,索引本来是一种事先在写的阶段形成一定的数据结构,从而使得在读的阶段效率较高的方式,但是如果一个字段是写多读少,则不建议使用索引。

为什么在查询条件里面不要使用函数?

例如ID+1=10这种条件,索引是事先写入的时候生成好的,ID+1这种操作在查询阶段,索引无能为例,没办法把所有的索引都先做一个计算,然后再比较吧,代价太大了,因而应该使用ID=10-1。

为什么不要使用NOT等负向查询条件?

你可以想象一下,对于一棵B+树,跟节点是40,如果你的条件是等于20,就去左面查,你的条件等于50,就去右面查,但是你的条件是不等于66,索引应该咋办?还不是遍历一遍才知道。

为什么模糊查询不要以通配符开头?

对于一棵B+树来讲,如果根是字符def,如果通配符在后面,例如abc%,则应该搜索左面,例如efg%,则应该搜索右面,如果通配符在前面%abc,则不知道应该走哪一面,还是都扫描一遍吧。

为什么OR要改成IN,或者使用Union?

OR查询条件的优化往往比较难找到最佳的路径,尤其是OR的条件比较多的时候,尤其如此,对于同一个字段,使用IN就好一些,数据库会对IN里面的条件进行排序,并统一通过二分搜索的方法处理。对于不同的字段,使用Union,则可以让每一个子查询都使用索引。

为什么数据类型应该尽量小,常用整型来代替字符型,长字符类型可以考虑使用前缀索引?

因为数据库是按照页存放的,每一页的大小是一样的,如果数据类型比较大,则页数会比较多,每一页放的数据会比较少,树的高度会比较高,因而搜索数据要读取的I/O数目会比较多,插入的时候节点也容易分裂,效率会降低。使用整型来代替字符型多是这个考虑,整型对于索引有更高的效率,例如IP地址等。如果有长字符类型需要使用索引进行查询,为了不要使得索引太大,可以考虑将字段的前缀进行索引,而非整个字段。

六、查询优化的方法论

要找到需要优化的SQL语句,首先要收集有问题的SQL语句。

MySQL 数据库提供了慢SQL日志功能,通过参数slow_query_log,获取执行时间超过一定阈值的SQL语录列表。

没有使用索引的SQL语句,可以通过long_queries_not_using_indexes参数开启。

min_examined_row_limit,扫描记录数大于该值的SQL语句才会被记入慢SQL日志。

找到有问题的语句,接下来就是通过explainSQL,获取SQL的执行计划,是否通过索引扫描记录,可以通过创建索引来优化执行效率。是否扫描记录数过多。是否持锁时间过长,是否存在锁冲突。返回的记录数是否较多。

接下来可以定制化的优化。没有被索引覆盖的过滤条件涉及的字段,在区分度较大的字段上创建索引,如果涉及多个字段,尽量创建联合索引。

扫描记录数非常多,返回记录数不多,区分度较差,重新评估SQL语句涉及的字段,选择区分度高的多个字段创建索引。

扫描记录数非常多,返回记录数也非常多,过滤条件不强,增加SQL过滤条件

schema_redundant_indexes查看有哪些冗余索引。

如果多个索引涉及字段顺序一致,则可以组成一个联合索引schema_unused_indexes查看哪些索引从没有被使用。

七、读写分离的原理

数据库往往写少读多,所以性能优化的第一步就是读写分离。

如何了解MySQL数据库原理_www.epx365.cn

主从复制基于主节点上的服务层的日志实现的,而从节点上有一个IO线程读取这个日志,然后写入本地。另有一个线程从本地日志读取后在从节点重新执行。

如何了解MySQL数据库原理_www.epx365.cn

如图是主从异步复制的流程图。在主实例写入引擎后就返回成功,然后将事件发给从实例,在从实例上执行。这种同步方式速度较快,但是在主挂了的时候,如果还没有复制,则可能存在数据丢失问题。

如何了解MySQL数据库原理_www.epx365.cn

数据库同步复制也不同,是当从节点落盘后再返回客户端,当然这样会使得性能有所降低,网易数据库团队是通过组提交,并行复制等技术将性能提上来。

有了主从复制,在数据库DAO层可以设置读写分离策略,也有通过数据库中间件做这个事情的。

其实数据库日志还有很多其他用处,如使用Canal(阿里巴巴开源项目:基于MySQL数据库Binlog的增量订阅&消费)订阅数据库的Binlog,可以用于更新缓存等。

文章出自:http://qh.itpxw.cn/peixun/software/201838817.html

文章标题:如何了解MySQL数据库原理



免责声明:本站文章均由入驻起航学习网的会员所发或者网络转载,所述观点仅代表作者本人,不代表起航学习网立场。如有侵权或者其他问题,请联系举报,必删。侵权投诉

(责任编辑:IT培训网)
顶一下
(0)
0%
踩一下
(0)
0%
------分隔线----------------------------
培训学校
IT培训网 访问该机构站点 报名留言 加为好友 用户等级:注册会员 用户级别:10 机构名称:IT培训网 联 系 人:罗老师 联系电话:13783581536 联系手机:13783581536 在线客服:起航学习网客服 在 线 QQ:起航学习网客服 电子邮件: 网站域名:http://www.itpxw.cn 注册时间:2016-07-18 11:07 最后登录:2024-02-20 13:02
推荐内容