关系型和非关系型数据库

关系型数据库

指采用了关系模型来组织数据的数据库。关系模型指的是二维表格模型,而一个关系型数据库就是由二维表及其之间的联系所组成的一个数据组织。数据库事务必须具备ACID特性。

  • 关系:一张二维表,每个关系都具有一个关系名,也就是表名
  • 元组:二维表中的一行,在数据库中被称为记录
  • 属性:二维表中的一列,在数据库中称为字段
  • 域:属性的取值范围,也就是数据库中某一列的取值限制
  • 关键字:一组可以唯一标识元组的属性,数据库中常称为主键,由一个或者多个列组成
  • 关系模型:指对关系的描述。格式为:关系名(属性1,属性2,...属性N),在数据库中称为表结构

主要有:MySQL,Oracle,SQLite,MariaDB,SQL server等

关系型数据库优点

  • 容易理解:二维表结构是非常贴近逻辑世界的一个概念,关系模型相对网状、层次等其他模型来说更容易理解
  • 使用方便:通用的SQL语言使得操作关系型数据库非常方便
  • 易于维护:丰富的完整性(实体完整性,参照完整性和用户定义的完整性)大大降低了数据冗余和数据不一致的概率

关系型数据库缺点

  • 网站的用户并发非常高,往往达到每秒上万次读写请求,对于传统关系型数据库来说,硬盘IO是一个很大的瓶颈
  • 网站每天产生的数据量是巨大的,对于关系型数据库来说,在一张包含海量数据的表中查询,效率很低
  • 表结构固定,灵活性欠佳
  • 在基于web的结构中,数据库是最难进行横向扩展的,当一个应用系统的用户量和访问量与日俱增的时候,数据库没法像web serverapp server那样简单的通过添加更多的硬件和服务节点来扩展性能和负载能力。当需要对数据库系统进行升级或者扩展的时候,通常需要停机维护和数据迁移。
  • 性能欠佳:在关系型数据库中,导致性能欠佳的主要原因是多表的关联查询,以及复杂的数据分析类型的复杂的sql报表查询。为了保证数据库的ACID原则,必须尽量按照其要求的范式进行设计,关系型数据库中的表都是存储一个格式化的数据结构。

非关系型数据库

NoSQL(Not Only SQL)指非关系型的,分布式的,且一般不保证遵循ACID原则的数据存储系统。

非关系型数据库以键值对存储,且结构不固定,每个元组可以有不一样的字段,每个元组可以根据需要增加一些自己的键值对,不局限于固定的结构,可以减少一些时间和空间的开销。

非关系型数据库严格上不是一种数据库,应该是一种数据机构话存储方法的集合,可以是文档或者键值对等。

  • 方便扩展(数据之间没有关系,很好扩展)
  • 大数据量高性能(Redis一秒可以写8万次,读11万次,NoSQL的缓存记录级是一种细粒度的缓存,性能比较高)
  • 数据类型多样(不需要事先设计数据表,随取随用,如果是数据量十分大的表则无法设计)

四大分类

  • key-value键值对型

    • Redis,Oracle BDB
    • 典型应用场景:内容缓存,主要用于处理大量数据的高访问负载,也用于一些日志系统等
    • 数据模型:Key指向Value的键值对,通常用Hash Table来实现
    • 优点:查找速度快
    • 缺点:数据无结构化,通常只被当作字符串或者二进制数据
  • 文档型数据库(bson格式)
    • MongoDB

      • 是一个基于分布式文件存储的数据库,主要涌过来处理大量的文档
      • 是一个介于关系型数据库和非关系型数据库中间的产品;是非关系型数据库中功能最丰富、最像关系型数据库的。
    • 典型应用场景:web应用(与key-value类似,value是结构化的,不同的是数据库能够了解value的内容)
    • 数据模型:key-value对应的键值对,value为结构化数据
    • 优点:数据结构要求不严格,表结构可以变化,不需要像关系型数据库一样需要预先定义表结构
    • 缺点:查询性能不高,而且缺乏统一的查询语法
  • 列存储数据库
    • HBase
    • 典型应用场景:分布式文件系统
    • 数据模型:以列簇式存储,将同一列数据存在一起
    • 优点:查找速度快,可扩展性强,更容易进行分布式扩展
    • 缺点:功能相对有限
  • 图关系数据库
    • Neo4J,InfoGrid,不是存储图形的,存放的是关系,比如朋友圈社交网络,广告推荐
    • 典型应用场景:社交网络,推荐系统等。专注于构建关系图谱
    • 数据结构:图结构
    • 优点:利用图结构相关算法。比如最短路径寻址,N度关系查找等
    • 缺点:很多时候需要对整个图做计算才能得到需要的信息,而却这种结构不太好做分布式的集群方案。

非关系型数据库优点

  • 用户可以根据需要添加自己需要的字段,为了获取用户的不同信息,不像关系型数据库中要对多表进行关联查询。仅需要根据ID取出相应的value就可以完成查询。
  • 适用于SNS(Social Network Services)中,例如社交网站。系统的升级,功能的增加往往意味着数据结构巨大变动,这一点关系型数据库难以应付,需要新的结构化数据存储。由于不可能用一种数据结构化存储应付所有的新的需求,因此,非关系型数据库严格上不是一种数据库,应该是一种数据机构话存储方法的集合。

非关系型数据库缺点

  • 只适合存储一些较为简单的数据,对于需要进行较复杂查询的数据,关系型数据库更合适。不适合持久存储海量数据。
  • 没有强大的事务关系,没有保证数据的完整性和安全性

数据库索引

索引概念

  • 是什么:索引(Index)是帮助MySQL高效获取数据的数据结构。

  • 作用:DB在执行一条Sql语句的时候,默认的方式是根据搜索条件进行全表扫描,遇到匹配条件的就加入搜索结果集合。如果我们对某一字段增加索引,查询时就会先去索引列表中一次定位到特定值的行数,大大减少遍历匹配的行数,所以能明显增加查询的速度。

  • 优点:

    通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。

    可以大大加快数据的检索速度,这也是创建索引的最主要的原因。

    可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。

    在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。

    通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。

  • 缺点:

    创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。

    索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。

    当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。

  • 分类:

    • 聚集索引(主键索引):在数据库里面,所有行数都会按照主键索引进行排序。
    • 非聚集索引:就是给普通字段加上索引。
    • 联合索引:就是好几个字段组成的索引,称为联合索引。遵循左前缀原则。
      • 通过字段顺序依次排序
      • 最左前缀法则:

索引不适合的场景

情景①

  • ****数据唯一性差(一个字段的取值只有几种时)的字段****不要使用索引
  • 比如性别,只有两种可能数据。意味着索引的二叉树级别少,多是平级。这样的二叉树查找无异于全表扫描

情景②

  • 频繁更新的字段不要使用索引
  • 比如logincount登录次数,频繁变化导致索引也频繁变化,增大数据库工作量,降低效率

情景③

  • ****字段不在where语句出现时不要添加索引,****只有在where语句出现,mysql才会去使用索引
  • 如果where****后含IS NULL /IS NOT NULL/ like ‘%输入符%’等条件****,不建议使用索引

情景④

  • where子句里对索引列使用不等于(<>),使用索引效果一般

索引失效的场景

条件中带有or

  • or语句两侧只有一个使用了索引,那么索引就会失效
  • 只有当or语句前后都有索引,那么才能根据索引进行查询

复合(组合)索引没有完全使用

  • 对于复合索引(组合索引),****如果没有使用左边的列,那么组合索引也失效****

在索引字段上使用not、<>、!=

  • **不等于操作符是永远不会用到索引的,**因此对它的处理只会产生全表扫描
  • 优化方法: key<>0 改为 key>0 or key<0

对索引字段进行计算操作

  • 如果where语句中对字段****进行了计算操作,那么也不会使用索引****

B-Tree

  • 满足以下条件

    • 出度d大于一的整数,一个节点有几个子节点
    • h为正整数为树的高度
    • 每个非叶子节点由n-1个key和n个指针组成,其中d<=n<=2d(指针和key互相间隔,节点两端是指针)
    • 每个叶子节点最少包含一个key和两个指针,最多包含2d-1个key和2d个指针,叶子节点的指针均为null
    • 所有叶子节点具有相同的深度,等于树高h
    • 一个节点中的key从左到右非递增排列
    • 每个指针要么指向另外一个节点,要么指向null

  • 查找:由于B-Tree的特性,在B-Tree中按key检索数据的算法非常直观:首先从根节点进行二分查找,如果找到则返回对应节点的data,否则对相应区间的指针指向的节点递归进行查找,直到找到节点或找到null指针,前者查找成功,后者查找失败。B-Tree上查找算法的伪代码如下:
BTree_Search(node, key) {if(node == null) return null;foreach(node.key){if(node.key[i] == key) return node.data[i];if(node.key[i] > key) return BTree_Search(point[i]->node);}return BTree_Search(point[i+1]->node);
}
data = BTree_Search(root, my_key);
  • 复杂度:关于B-Tree有一系列有趣的性质,例如一个度为d的B-Tree,设其索引N个key,则其树高h的上限为logd((N+1)/2)logd((N+1)/2),检索一个key,其查找节点个数的渐进复杂度为O(logdN)O(logdN)。从这点可以看出,B-Tree是一个非常有效率的索引数据结构。另外,由于插入删除新的数据记录会破坏B-Tree的性质,因此在插入删除时,需要对树进行一个分裂、合并、转移等操作以保持B-Tree性质

B+Tree

  • 相比于B-Tree的区别

    • 每个节点的指针上限为2d而不是2d+1(少了最左侧的指针)
    • 内节点不再存储data,只存储key;叶子节点也不存储指针,只存放data
    • 基于上一条不同点,B+Tree中叶节点和内节点所分配的空间一般不同;而B-Tree由于每个节点存放的指针和key值上限是一致的,所以在实现B-Tree往往对每个节点申请同等大小的空间。
    • B+Tree叶子节点带有顺序访问指针,提高区间访问的能力;如果需要查询跨越连续叶子节点范围内的数据,不必再次从根节点出发,而是直接通过顺序访问指针遍历

索引为什么使用B-Tree(B+Tree)

  • 一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。这样的话,索引查找过程中就要产生磁盘I/O消耗,相对于内存存取,I/O存取的消耗要高几个数量级,所以评价一个数据结构作为索引的优劣最重要的指标就是在查找过程中磁盘I/O操作次数的渐进复杂度。换句话说,索引的结构组织要尽量减少查找过程中磁盘I/O的存取次数。

    • 主存存储:主存存储时间仅仅与存储次数呈现线性相关性
    • 外存存储:外存磁盘io时间消耗巨大,通常外存会进行预读(页的整数倍)
  • B+/-树索引性能分析
    • 红黑树:h很深,逻辑上很近的父子兄弟节点在物理上可能很远,无法利用局部性,所以红黑树的渐进复杂度也为O(h),但是h要大很多。
    • hash:查询单个数据很快,但是不常用主要是因为不支持范围查找
    • B-Tree:
      1. 检索一次最多需要访问h(高度)个节点,将一个节点大小设置为一个页可以让每个节点只需要一次io就可以完全读入,所以在新建节点的时候直接申请一个页的空间。
      2. B-Tree中一次检索最多需要h-1次io(根节点常驻内存),渐进复杂度为O(h)=O(logdN)。实际应用中,出度d是一个非常大的数字,通常超过100,所以h一般很小,不超过3。一亿个,四层
    • B+Tree:
      1. 比B-Tree更适合外存索引,原因和出度d有关,d越大那么h越小,那么渐进复杂度就越小。而出度取决于节点内key和data的大小:dmax=floor(pagesize/(keysize+datasize+pointsize)) 最大出度=页大小/(键值大小+数据大小+指针大小)
      2. 因为B+Tree中内节点只有key和指针两项,所以空间大小下就可以比B-数拥有更多的出度,拥有更好的性能;
      3. B+Tree更支持范围查找,叶子节点键具有相连的双向指针。
  • 问题
    • 既然磁盘io的时间远大于在内存中查找的时间,为什么不直接把所有数据一次读入内存。浪费空间
    • 根节点16kb 16384 ,一个key值用big int(8字节)一个指针(6字节),根节点出度16kb/14b=1170,那么叶子节点可以放两千万个1k数据
    • 索引结构存储位置,根目录下的data文件
      • MYISAM:1.frame字段框架,2.data数据,3.index索引三个文件
      • InnoDB:1.frame,2ibd数据和索引
    • 存储引擎是形容数据库表的

MySql索引实现

MylSAM索引实现(非聚集索引)
  • MyISAM引擎使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址。下图是MyISAM索引的原理图:

  • 在MyISAM中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复。如果我们在Col2上建立一个辅助索引,则此索引的结构如下图所示:

InnoDB索引实现(聚集索引,叶子节点包括所有的数据)
  • 和MylSAM不同,叶子节点本身不再是数据的地址而是数据本身,节点中的key就是数据表的主键。

  • 这种索引叫做聚集索引。因为InnoDB的数据文件本身要按主键聚集,所以InnoDB要求表必须有主键(MyISAM可以没有),如果没有显式指定,则MySQL系统会自动选择一个可以唯一标识数据记录的列作为主键,如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整形。
  • 第二点和MylSAM不同的是辅助索引叶子节点记录的不再是data的地址而是记录的主键key。也就是说InnoDB通过主键搜索十分高效,但是通过辅助索引却需要检索两次索引:找到主键,再通过主键找到相应的数据块。这就是为什么InnoDB为什么不建议使用过长的字段作为主键的原因,过长的主键会导致辅助索引变得很大。

InnoDB的主键选择与插入优化

  • 在使用InnoDB存储引擎时,如果没有特别的需要,请永远使用一个与业务无关的自增字段作为主键

  • 如果不设置主键,mysql会自己设置主键或者添加一列隐藏主键列

  • 从数据库索引优化角度看,使用InnoDB引擎而不使用自增主键绝对是一个糟糕的主意(比如UUID 的目的是让分布式系统中的所有元素,都能有唯一的辨识资讯)

    • 每一个叶子节点都有固定的大小,如果存储的data将要溢出,那么Mysql就会开辟一个新的节点来存储。

      1. 如果用自增主键,那么每次插入新的记录,记录就会顺序添加到后续的位置,形成一个紧凑的索引结构,每次插入不用移动现有数据不会增加很多维护索引的开销。

      2. 如果使用非自增主键,那么每次插入新的记录都是近乎于随机,那么就会破坏已有的索引结构需要Mysql频繁移动记录位置,造成很多碎片。后续不得不通过OPTIMIZE TABLE来重建表并优化填充页面

  • 自增主键和uuid优缺点对比

    • 自增主键:

      • 优点:

        1. 数据库自动编号,速度快,而且是增量增长,按顺序存放,对于检索非常有利;
        2. 数字型,占用空间小,易排序,在程序中传递也方便;
        3. 如果通过非系统增加记录时,可以不用指定该字段,不用担心主键重复问题。
      • 缺点
        1. 当需要手动导入数据或者与其他或者新旧系统双向同步的时候会导致大范围冲突。
    • uuid
      • 优点:

        1. 出现数据拆分、合并存储的时候,能达到全局的唯一性
      • 缺点:
        1. 影响插入速度, 并且造成硬盘使用率低
        2. 作为索引键值,uuid(字符串)之间比较大小相对数字慢不少, 影响查询速度。
        3. uuid占空间大, 如果你建的索引越多, 影响越严重

Mysql原理

事务

概念

事务指的是满足 ACID 特性的一组操作,可以通过 Commit 提交一个事务,也可以使用 Rollback 进行回滚。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vl3JbPts-1602224318125)(C:\Users\free\AppData\Roaming\Typora\typora-user-images\image-20200816164909513.png)]

ACID

原子性(Atomicity)

事务被视为不可分割的最小单元,事务的所有操作要么全部提交成功,要么全部失败回滚。

回滚可以用回滚日志(Undo Log)来实现,回滚日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。

一致性(Consistency)

数据库在事务执行前后都保持一致性(不是一样)状态。在一致性状态下,所有事务对同一个数据的读取结果都是相同的。(指一个系统从一个正确的状态,转移到另一个正确的状态,可以说C是目的,AID是手段)

隔离性(Isolation)

一个事务所做的修改在最终提交以前,对其它事务是不可见的。

持久性(Durability)

一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。

系统发生奔溃可以用重做日志(Redo Log)进行恢复,从而实现持久性。与回滚日志记录数据的逻辑修改不同,重做日志记录的是数据页的物理修改。

ACID的关系

  • 只有满足一致性,事务的执行结果才是正确的。
  • 在无并发的情况下,事务串行执行,隔离性一定能够满足。此时只要能满足原子性,就一定能满足一致性。
  • 在并发的情况下,多个事务并行执行,事务不仅要满足原子性,还需要满足隔离性,才能满足一致性。
  • 事务满足持久化是为了能应对系统崩溃的情况。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j9MNIKAd-1602224318127)(C:\Users\free\AppData\Roaming\Typora\typora-user-images\image-20200816164951714.png)]

AUTOCOMMIT

MySQL 默认采用自动提交模式。也就是说,如果不显式使用START TRANSACTION语句来开始一个事务,那么每个查询操作都会被当做一个事务并自动提交。

分布式事务

分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。

简单的说就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。

CAP原则

CAP原则又称为CAP定理,指的是在一个分布式系统中,web服务无法同时满足以下3个特性:

  • 一致性(Consistency):在分布式系统中数据一更新,所有数据变动都是同步的
  • 可用性(Availability):好的响应性能,每个操作都必须有预期的响应结束
  • 分区容错性(Partition Tolerance):在网络分区的情况下,即使出现单个节点无法使用,系统依然正常对外提供服务。

单机版的Redis中,牺牲的是A可用性,保证了C一致性和P分区容错性。因为宕掉的一台Master节点无法服务了。

Cluster功能的Redis牺牲的是C,保证了A可用性和C分区容错性。因为某个节点宕机,那么会有其他节点顶替上来,保证了A,但是无法保证C了。

BASE理论

BASE理论是对CAP中的一致性和可用性进行一个权衡的结果,理论的核心思想是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。

  • 基本可用(Basically Available)

    • 指分布式系统在出现不可预知的故障的时候,允许损失部分可用性(不等价于系统不可用)
  • 软状态(Soft State)
    • 和硬状态对应,是指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统的不同节点的数据副本之间进行数据同步的过程存在延时
  • 最终一致性(Eventually Consistent)
    • 指的是系统所有的数据副本,经过一段时间的同步之后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要试试保证系统数据的强一致性。

两阶段提交(2PC)

两阶段提交需要有一个协调者,来协调两个操作之间的操作流程。当参与方很多时,逻辑会变得比较复杂。

参与方需要实现两阶段提交协议。

阶段1

  • 协调者向所有参与者发送事务内容,询问是否可以提交事务并等待答复
  • 各参与者执行事务操作,将undo和redo信息记入日志文件中
  • 如果参与者执行成功,给协调者返回yes,否则返回no

阶段2

  • 如果协调者收到参与者的失败消息或者超时,直接给每个参与者发送回滚rollback消息,否则发送提交commit消息。

可能遇到的情况:

  • 当所有参与者都反馈yes就提交事务
  • 当有一个参与者反馈no,回滚事务

问题

  • 性能问题:所有参与者在事务提交阶段处于同步阻塞状态,占用系统资源,容易导致性能瓶颈
  • 可靠性问题:如果协调者存在单点故障问题,或者出现故障,提供者将一直处于锁定状态
  • 数据一致性问题:在第二个阶段中,如果出现协调者和参与者都挂了的情况,有可能导致数据不一致。

优点

  • 尽量保证了数据的强一致,适合对数据强一致要求很高的关键领域。

缺点

  • 实现复杂,牺牲了可用性,对性能影响大,不适合高并发场景

三阶段提交(3PC)

二阶段提交的改进版本,要解决的是协调者和参与者同时挂的问题,3PC把2PC的准备阶段再次一分为二,变成三阶段提交。

阶段1

  • 协调者向所有参与者发出包含事务内容的canCommit请求,询问是否可以提交事务,并等待所有参与者答复。
  • 参与者收到canCommit请求,如果认为可以执行事务操作,则反馈yes并进入预备状态,否则反馈no

阶段2

  • 如果所有参与者返回yes,协调者预执行事务

    • 协调者向所有参与者发出preCommit请求,进入准备阶段
    • 参与者收到preCommit请求后,执行事务操作,将undo和redo信息记入事务日志中(但不提交事务)
    • 各参与者向协调者反馈ack响应或者no响应,并等待最终指令
  • 如果有参与者反馈no,或者等待超时后协调者无法收到所有参与者的反馈,即中断事务
    • 协调者向所有参与者发出abort请求
    • 无论收到协调者的abort请求,或者在等待协调者请求过程中出现超时,参与者都会中断事务

阶段3

  • 如果所有参与者均回馈ack响应,执行真正的事务提交

    • 如果协调者处于工作状态,则向所有参与者发出doCommit请求
    • 参与者收到doCommit请求后,会正式执行事务提交,并释放整个事务期间占用的资源
    • 各参与者向协调者反馈ack完成的消息
    • 协调者收到所有参与者反馈的消息后,即完成事务提交
  • 如果有参与者反馈no,或者等待超时后协调者无法收到所有参与者的反馈,即回滚事务
    • 如果协调者处于工作状态,则向所有参与者发送rollback请求
    • 参与者使用阶段1中的undo信息执行回滚操作,并释放整个事务期间占用的资源
    • 各参与者向协调者反馈ack完成的消息
    • 协调者收到所有参与者反馈的消息后,即完成回滚操作
  • 注意:进入阶段3后,无论协调者是否出问题,或者协调者与参与者网络出现问题,都会导致参与者无法接收到协调者发出的doCommit请求或者abort请求,此时参与者都在等待超时之后,自动执行事务提交。

优点:

  • 相比二阶段提交,三阶段提交降低了阻塞范围,在等待超时后协调者或参与者会中断事务。避免了协调者单点问题。阶段3中协调者出现问题时,参与者会继续提交事务。

缺点:

  • 数据不一致的问题依然存在,当在参与者收到preCommit请求后等待doCommit指令时,此时如果协调者请求中断事务,而协调者无法与参与者正常通信,会导致参与者继续提交事务,造成数据不一致。

补偿事务(TCC)

TCC是服务化的二阶段编程模型,采用的补偿机制。核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿操作。分为三个阶段:

  • Try阶段主要是对业务系统做检测和资源预留

    • 完成所有业务检查(一致性)
    • 预留必须业务资源(准隔离性)
    • Try尝试执行业务
  • Confirm阶段主要对业务系统做确认提交。
    • Try阶段执行成功并开始执行Confirm阶段时,默认Confirm阶段是不会出错的。即只要Try成功,Confirm一定成功。
  • Cancel阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。

优点:

  • 性能提升:具体业务来实现控制资源锁的粒度变小,不会锁定整个资源
  • 数据最终一致性:基于Confirm和Cancel的幂等性,保证事务最终完成确认或者取消,保证数据的一致性
  • 可靠性:解决了XA协议的协调者单点故障问题,由主业务发起并控制整个业务活动,业务活动管理器也变成多点,引入集群。

缺点:

  • TCC的三个阶段操作功能要按具体业务来实现,业务耦合度较高,提高了开发成本。

并发一致性问题

在并发环境下,事务的隔离性很难保证,因此会出现很多并发一致性问题。

产生并发不一致性问题的主要原因是破坏了事务的隔离性,解决方法是通过并发控制来保证隔离性。并发控制可以通过封锁来实现,但是封锁操作需要用户自己控制,相当复杂。数据库管理系统提供了事务的隔离级别,让用户以一种更轻松的方式处理并发一致性问题。

丢失修改

丢失修改指一个事务的更新操作被另外一个事务的更新操作替换。一般在现实生活中常会遇到。

例如:T1 和 T2 两个事务都对一个数据进行修改,T1 先修改并提交生效,T2 随后修改,T2 的修改覆盖了 T1 的修改。

读脏数据

读脏数据指在不同的事务下,当前事务可以读到另外事务未提交的数据

例如:T1 修改一个数据但未提交,T2 随后读取这个数据。如果 T1 撤销了这次修改,那么 T2 读取的数据是脏数据。

不可重复读

不可重复读指在一个事务内多次读取同一数据集合。在这一事务还未结束前,另一事务也访问了该同一数据集合并做了修改,由于第二个事务的修改,第一次事务的两次读取的数据可能不一致

例如:T2 读取一个数据,T1 对该数据做了修改。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同。

幻影读

幻读本质上也属于不可重复读的情况。

例如:T1 读取某个范围的数据,T2 在这个范围内插入新的数据,T1 再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同。

封锁

封锁粒度

MySQL 中提供了两种封锁粒度:行级锁以及表级锁

应该尽量只锁定需要修改的那部分数据,而不是所有的资源。锁定的数据量越少,发生锁争用的可能就越小,系统的并发程度就越高。

但是加锁需要消耗资源,锁的各种操作(包括获取锁、释放锁、以及检查锁状态)都会增加系统开销。因此封锁粒度越小,系统开销就越大。

在选择封锁粒度时,需要在锁开销和并发程度之间做一个权衡。

封锁类型

读写锁

  • 互斥锁(Exclusive):简写为X锁,又称为写锁。
  • 共享锁(Shared):简写S锁,又称为读锁。

有以下两个规定:

  • 一个事务对数据对象 A 加了 X 锁,就可以对 A 进行读取和更新加锁期间其它事务不能对 A 加任何锁。
  • 一个事务对数据对象 A 加了 S 锁,可以对 A 进行读取操作,但是不能进行更新操作。加锁期间其它事务能对 A 加 S 锁,但是不能加 X 锁

意向锁

使用意向锁(Intention Locks)可以更容易地支持多粒度封锁。

在存在行级锁和表级锁的情况下,事务 T 想要对表 A 加 X 锁,就需要先检测是否有其它事务对表 A 或者表 A 中的任意一行加了锁,那么就需要对表 A 的每一行都检测一次,这是非常耗时的。

意向锁在原来的 X/S 锁之上引入了 IX/ISIX/IS 都是表锁,用来表示一个事务想要在表中的某个数据行上加 X 锁或 S 锁。有以下两个规定:

  • 一个事务在获得某个数据行对象的 S 锁之前,必须先获得表的 IS 锁或者更强的锁;
  • 一个事务在获得某个数据行对象的 X 锁之前,必须先获得表的 IX 锁。

通过引入意向锁,事务 T 想要对表 A 加 X 锁,只需要先检测是否有其它事务对表 A 加了 X/IX/S/IS 锁,如果加了就表示有其它事务正在使用这个表或者表中某一行的锁,因此事务 T 加 X 锁失败。

锁的兼容关系:

  • 任意 IS/IX 锁之间都是兼容的,因为它们只表示想要对表加锁,而不是真正加锁;

  • 这里兼容关系针对的是表级锁,而表级的 IX 锁和行级的 X 锁兼容,两个事务可以对两个数据行加 X 锁。(事务 T1 想要对数据行 R1 加 X 锁,事务 T2 想要对同一个表的数据行 R2 加 X 锁,两个事务都需要对该表加 IX 锁,但是 IX 锁是兼容的,并且 IX 锁与行级的 X 锁也是兼容的,因此两个事务都能加锁成功,对同一个表中的两个数据行做修改。)

封锁协议

三级封锁协议

  • 一级封锁协议(对应Read Uncommited)

    • 事务 T 要修改数据 A 时必须加 X 锁,直到 T 结束才释放锁。
    • 可以解决丢失修改的问题,因为不能同时有两个事务对同一个数据进行修改,那么事务的修改就不会被覆盖。
  • 二级封锁协议(对应Read Commited)

    • 在一级的基础上,要求读取数据 A 时必须加 S 锁,读取完马上释放 S 锁。
    • 可以解决读脏数据问题,因为如果一个事务在对数据 A 进行修改,根据 1一级封锁协议,会加 X 锁,那么就不能再加 S 锁了,也就是不会读入数据。
  • 三级封锁协议(对应Reapetable Read)

    • 在一级的基础上,要求读取数据 A 时必须加 S 锁,直到事务结束了才能释放 S 锁。
    • 可以解决不可重复读的问题,因为读 A 时,其它事务不能对 A 加 X 锁,从而避免了在读的期间数据发生改变。
  • 四级封锁协议(对应Serialization)

    • 四级封锁协议是对三级封锁协议的增强,直接对事务中所读取或者更改的数据所在的表加表锁,也就是说其他事务不能读写该表中的任何数据。

两段锁协议

加锁和解锁分为两个阶段进行。

可串行化调度是指,通过并发控制,使得并发执行的事务结果与某个串行执行的事务结果相同。串行执行的事务互不干扰,不会出现并发一致性问题。

事务遵循两段锁协议是保证可串行化调度的充分条件。例如以下操作满足两段锁协议,它是可串行化调度。

lock-x(A)...lock-s(B)...lock-s(C)...unlock(A)...unlock(C)...unlock(B)

但不是必要条件,例如以下操作不满足两段锁协议,但它还是可串行化调度。

lock-x(A)...unlock(A)...lock-s(B)...unlock(B)...lock-s(C)...unlock(C)

MySQL隐式和显式锁定

MySQL 的 InnoDB 存储引擎采用两段锁协议,会根据隔离级别在需要的时候自动加锁,并且所有的锁都是在同一时刻被释放,这被称为隐式锁定。

InnoDB 也可以使用特定的语句进行显示锁定:

SELECT ... LOCK In SHARE MODE;
SELECT ... FOR UPDATE;

隔离级别

未提交读(READ UNCOMMITED)

事务中的修改,即使没有提交,对其他事务也是可见的。

不可以解决任何并发一致性问题。

提交读(READ COMMITED)

一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。

可以解决脏读问题。不可以解决不可重复读,幻影读问题。

可重复读(REPEATABLE READ)

保证在同一个事务中多次读取同一数据的结果是一样的。

可以解决脏读和不可重复读问题。不可以解决幻影读问题。

可串行化(SERIALIZABLE)

强制事务串行执行,这样多个事务互不干扰,不会出现并发一致性问题。

该隔离级别需要加锁实现,因为要使用加锁机制保证同一时间只有一个事务执行,也就是保证事务串行执行。

多版本并发控制(MVCC)

多版本并发控制(Multi-Version Concurrency Control, MVCC)是 MySQL 的InnoDB 存储引擎实现隔离级别的一种具体方式,用于实现提交读和可重复读这两种隔离级别。而未提交读隔离级别总是读取最新的数据行,要求很低,无需使用 MVCC。可串行化隔离级别需要对所有读取的行都加锁,单纯使用 MVCC 无法实现。

基本思想

在封锁一节中提到,加锁能解决多个事务同时执行时出现的并发一致性问题。在实际场景中读操作往往多于写操作,因此又引入了读写锁来避免不必要的加锁操作,例如读和读没有互斥关系。读写锁中读和写操作仍然是互斥的,而 MVCC 利用了多版本的思想,写操作更新最新的版本快照,而读操作去读旧版本快照,没有互斥关系,这一点和CopyOnWrite 类似。

在 MVCC 中事务的修改操作(DELETE、INSERT、UPDATE)会为数据行新增一个版本快照。

脏读和不可重复读最根本的原因是事务读取到其它事务未提交的修改。在事务进行读取操作时,为了解决脏读和不可重复读问题,MVCC 规定只能读取已经提交的快照。当然一个事务可以读取自身未提交的快照,这不算是脏读。

版本号

  • 系统版本号 SYS_ID:是一个递增的数字,每开始一个新的事务,系统版本号就会自动递增。
  • 事务版本号 TRX_ID :事务开始时的系统版本号。

undo log

MVCC 的多版本指的是多个版本的快照,快照存储在 Undo Log中,该日志通过回滚指针 ROLL_PTR 把一个数据行的所有快照连接起来。

如果用户执行的事务或者语句由于某种原因失败,或者用户使用一条ROLLBACK语句请求回滚,就可以利用Undo Log将数据回滚到修改之前的样子。

存放在数据库内部的一个特殊段undo段中,undo段位域共享表空间中。

undo是逻辑日志,因此只是将数据库逻辑的恢复到原来的样子,所有的修改都被逻辑性的取消了,但是数据结构和页本身在回滚之后可能大不相同。

逻辑性的取消是指对每个INSERT都完成一个DELETE,对每个DELETE都完成一个INSERT,对每个UPDATE都完成一个反向UPDATE

undo log的产生会伴随着redo log的产生,这是因为undo log也需要持久性的保护。

redo log

重做日志(redo log)用来实现事务的持久性,即事务ACID中的D。redo log是持久的。

记录的是数据页的物理修改,而不是某一行或者某几行的修改记录,它用来恢复提交后的物理数据页(恢复数据也,且只能恢复到最后一次提交的位置)

在事务的进行过程中,不断有重做日志条目被写入到重做日志文件中。

在数据库运行时不需要对redo log日志进行读取操作。

为了确保每次日志都写入重做日志文件,在每次将重做日志缓冲写入重做日志文件后,InnoDB存储引擎都需要调用一次fsync操作。

binary log(二进制日志)

记录了对Mysql数据库执行更改的所有操作(逻辑日志),但是不包括SELECT和SHOW这类操作,因为这类操作并没有对数据本身修改。然而如果操作本身并没有导致数据库发生变化,那么该操作也会写入二进制文件。

主要的作用:

  • 恢复:某些数据的恢复需要二进制日志,比如在一个数据库全备文件恢复后,用户可以通过二进制日志进行point-in-time恢复
  • 复制:原理与恢复类似,通过复制和执行二进制日志使远程一台mysql数据库(slave)与一台mysql数据库(master)进行实时同步。
  • 审计:用户可以通过二进制日志中的信息来进行审计,判断是否有对数据库进行注入的攻击。

redo log 和 binary log区别

  • redo log是在Innodb存储引擎层产生的,而binary log是在mysql数据库的上层产生的,并且binary log不仅仅针对于innodb存储引擎。

  • 记录的内容形式不同,redo log是物理格式日志,记录的是对于每个页的修改;binary log是一种逻辑日志,记录的是对应的sql语句。

  • 记录的时间点不同,redo log在事务进行中不断的被写入,该日志并不是随着事务提交的顺序进行写入的;binary log只在事务提交完成后进行一次写入

ReadView

MVCC维护了一个ReadView结构,主要包含了当前系统未提交的事务列表TRX_IDs{TRX_ID_1,TRX_ID_2,...},还有该列表的最小值TRX_ID_MIN和最大值TRX_ID_MAX

在进行SELECT操作时,根据数据行快照的 TRX_IDTRX_ID_MINTRX_ID_MAX 之间的关系,从而判断数据行快照是否可以使用:

  • TRX_ID < TRX_ID_MIN,表示该数据行快照时在当前所有未提交事务之前进行更改的,因此可以使用。
  • TRX_ID > TRX_ID_MAX,表示该数据行快照是在事务启动之后被更改的,因此不可使用。
  • TRX_ID_MIN <= TRX_ID <= TRX_ID_MAX,需要根据隔离级别再进行判断:
    • 提交读:如果 TRX_IDTRX_IDs 列表中,表示该数据行快照对应的事务还未提交,则该快照不可使用。否则表示已经提交,可以使用。
    • 可重复读:都不可以使用。因为如果可以使用的话,那么其它事务也可以读到这个数据行快照并进行修改,那么当前事务再去读这个数据行得到的值就会发生改变,也就是出现了不可重复读问题。

在数据行快照不可使用的情况下,需要沿着 Undo Log 的回滚指针 ROLL_PTR 找到下一个快照,再进行上面的判断。

快照读和当前读

快照读

MVCC 的 SELECT 操作是快照中的数据,不需要进行加锁操作。

当前读

MVCC 其它会对数据库进行修改的操作(INSERT、UPDATE、DELETE)需要进行加锁操作,从而读取最新的数据。可以看到 MVCC 并不是完全不用加锁,而只是避免了 SELECT 的加锁操作。

在进行SELECT操作时,可以强制指定进行加锁操作。

SELECT * FROM table WHERE ? lock in share mode;
SELECT * FROM table WHERE ? for update;

Next-Key Locks

Next-Key Locks 是 MySQL 的 InnoDB 存储引擎的一种锁实现。

MVCC 不能解决幻影读问题,Next-Key Locks 就是为了解决这个问题而存在的。在可重复读(REPEATABLE READ)隔离级别下,使用 MVCC + Next-Key Locks 可以解决幻读问题。

Record Locks

锁定一个记录上的索引,而不是记录本身。

如果表没有设置索引,InnoDB会自动在主键上创建隐藏的聚簇索引,因此Record Locks依然可以使用。

Gap Locks

锁定索引之间的间隙,但是不包括索引本身。

例如当一个事务执行以下语句,其他事务就不能在t.c中插入15。

SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATA;

Next-Key Locks

是Record Locks 和 Gap Locks的结合,不仅锁定一个记录上的索引,也锁定索引之间的间隙,他锁定一个前开后闭的区间。

关系型数据库设计理论

函数依赖

A->B 表示 A 函数决定 B,也可以说 B 函数依赖于 A。

如果 {A1,A2,... ,An} 是关系的一个或多个属性的集合,该集合函数决定了关系的其它所有属性并且是最小的,那么该集合就称为键码。

对于 A->B,如果能找到 A 的真子集 A',使得 A'-> B,那么A->B 就是部分函数依赖,否则就是完全函数依赖。

对于 A->BB->C,则 A->C 是一个传递函数依赖。

异常

以下的学生课程关系的函数依赖为 {Sno, Cname} -> {Sname, Sdept, Mname, Grade},键码为 {Sno, Cname}。也就是说,确定学生和课程之后,就能确定其它信息。

Sno Sname Sdept Mname Cname Grade
1 学生-1 学院-1 院长-1 课程-1 90
2 学生-2 学院-2 院长-2 课程-2 80
2 学生-2 学院-2 院长-2 课程-1 100
3 学生-3 学院-2 院长-2 课程-2 95

不符合范式的关系,会产生很多异常,主要有以下四种异常:

  • 冗余数据:例如 学生-2 出现了两次。
  • 修改异常:修改了一个记录中的信息,但是另一个记录中相同的信息却没有被修改。
  • 删除异常:删除一个信息,那么也会丢失其它信息。例如删除了 课程-1 需要删除第一行和第三行,那么 学生-1 的信息就会丢失。
  • 插入异常:例如想要插入一个学生的信息,如果这个学生还没选课,那么就无法插入。

ER图

基本元素

  • 实体:客观存在并可以相互区别的事务。使用矩形表示
  • 属性:实体所具有的某一特性,一个实体可以由若干个属性来刻画。使用椭圆形表示,并用无向边将其与相应的实体连接起来。
  • 联系:即在信息世界中反映实体内部或者实体之间的联系。实体内部联系通常指组成实体的各属性之间的联系;实体之间的联系通常指不同实体集之间的联系。使用菱形表示,并用无向边分别于有关实体连接起来,同时在无向边上标记联系的类型,如一对一,一对多,多对多标记为1:1 1:n m:n

范式

  • 第一范式就是属性不可分割,每个字段都应该是不可再拆分的。比如一个字段是姓名(NAME),在国内的话通常理解都是姓名是一个不可再拆分的单位,这时候就符合第一范式;但是在国外的话还要分为FIRST NAME和LAST NAME,这时候姓名这个字段就是还可以拆分为更小的单位的字段,就不符合第一范式了。
  • 第二范式就是要求表中要有主键,表中其他其他字段都依赖于主键,因此第二范式只要记住主键约束就好了。比如说有一个表是学生表,学生表中有一个值唯一的字段学号,那么学生表中的其他所有字段都可以根据这个学号字段去获取,依赖主键的意思也就是相关的意思,因为学号的值是唯一的,因此就不会造成存储的信息对不上的问题,即学生001的姓名不会存到学生002那里去。
  • 第三范式即非主属性不传递,只依赖于键码。比如说有一个表是学生表,学生表中有学号,姓名等字段,那如果要把他的系编号,系主任,系主任也存到这个学生表中,那就会造成数据大量的冗余,一是这些信息在系信息表中已存在,二是系中有1000个学生的话这些信息就要存1000遍。因此第三范式的做法是在学生表中增加一个系编号的字段(外键),与系信息表做关联。

MySQL逻辑架构

  • 第一层是服务器层,主要提供连接处理、授权认证、安全等功能。
  • 第二层实现了Mysql核心服务功能,包括查询解析、分析、优化、缓存以及日期和时间等所有内置函数、所有跨存储引擎的功能都在这一层实现,例如存储过程、触发器、视图等
  • 第三层是存储引擎层,存储引擎负责Mysql中数据的存储和提取。服务器通过api和存储引擎通信,这些接口屏蔽了不同存储引擎的差异,是的差异对于上层查询过程透明。除了会解析外键定义的innodb外,存储引擎不会解析sql,不同存储引擎之间也不会相互通信,只是简单响应上层服务器请求。

查询流程

  • 客户端发送一条查询给服务器
  • 服务器先检查查询缓存,如果命中了缓存则立刻返回存储在缓存中的结果,否则进入下一个阶段
  • 服务器端进行SQL解析、预处理,再由优化器生成对应的执行计划
  • MySQL根据优化器生成的计划,调用存储引擎的API来执行查询
  • 将结果返回给客户端

Mysql存储引擎

主要引擎

功能 MyISAM Memory InnoDB Archive
存储限制 256TB RAM 64TB NO
支持事务 NO NO YES NO
支持全文索引 YES NO NO NO
支持数索引 YES YES YES NO
支持哈希索引 NO YES NO NO
支持数据缓存 NO N/A YES NO
支持外键 NO NO YES NO
  • InnoDB:如果想要提供提交、回滚、崩溃恢复能力的事务安全(ACID兼容)能力,并实现并发控制
  • MyISAM:如果数据表主要用来插入和查询记录,则此引擎效率快
  • Memory:
    • 数据存放在内存中,数据量小,速度快
    • 如果只是临时存放数据,数据量不大,并且不需要较高的数据安全性,可以选择将数据保存在内存中的Memory引擎,MySQL中使用该引擎作为临时表,存放查询的中间结果
  • Archive:如果只有insert和select操作,可以选择此引擎。Archive支持高并发的插入操作,但本身并不是事务安全的。Archive非常适合存储归档数据(有很好的压缩机制),如记录日志信息

InnoDB与MyISAM的区别

存储结构

  • MyISAM引擎创建数据库,将产生3个文件,文件的名字以表的名字开始,扩展名指出文件类型:

    • frm文件存储表定义
    • 数据文件的扩展名为**.MYD**(MYData)
    • 索引文件的扩展名为**.MYI**(MYIndex)

底层结构

  • myisam的b+树叶子节点存储的时数据的地址,而innodb存储的时具体的数据

存储空间

  • **InnoDB:**需要更多的内存和存储,它会在主内存中建立其专用的缓冲池用 于高速缓冲数据和索引。
  • **MyISAM:**MyISAM索引和数据是分开的,并且索引是有压缩的,内存使用率就对应提高了不少。能加载更多索引,而 Innodb 是索引和数据是紧密捆绑的,没有使用压缩从而会造成 Innodb 比 MyISAM 体积庞大不小

事务处理

  • MyISAM:MyISAM类型的表强调的是性能,其执行数度比 InnoDB 类型更快,但是*不支持外键、不提供事务支持***
  • *InnoDB:InnoDB 提供事务*支持事务,外部键(foreign key)****等高级数据库功能

表的具体行数

  • MyISAM:保存有表的总行数,如果 select count() from table;会*直接取出该值**
  • **InnoDB:没有保存表的总行数,如果使用 select count() from table; ****就会遍历整个表*,消耗相当大,但是在加了 where 后,myisam 和 innodb 处 理的方式都一样

表锁差异

  • MyISAM:*只支持表级锁*,用户在操作 myisam 表时, select,update,delete,insert 语句都会****给表自动加锁****
  • InnoDB:*支持事务和行级锁,*是 innodb 的最大特色。行锁大幅度*提高了多用户并发操作的新能*。但是 InnoDB 的行锁也不是绝对的,如果在执行一个 SQL 语句时 MySQL 不能确定要扫描的范围,InnoDB 表同样会锁全表, 例如 update table set num=1 where name like “%aaa%”

选取适合的存储引擎

  • 对于如何选择存储引擎,可以简单地归纳为一句话:****“除非需要用到某些InnoDB不具备的特性,并且没有其他办法可以替代,否则都应该优先选择InnoDB引擎”****
  • 如果应用需要不同的存储引擎,请先考虑以下几个因素:
    • 事务:如果应用需要事务支持,那么InnoDB(或者XtraDB)是目前最稳定并且经过验证的选择。如果不需要事务,并且主要是SELECT 和INSERT操作,那么MyISAM是不错的选择。一般日志型的应用比较符合这一特性
    • **备份:**备份的需求也会影响存储引擎的选择。如果可以定期地关闭服务器来执行备份,那么备份的因素可以忽略。反之,如果需要在线热备份,那么选择InnoDB就是基本的要求
    • **崩溃恢复:**数据量比较大的时候,系统崩溃后如何快速地恢复是一个需要考虑的问题。相对而言,MyISAM崩溃后发生损坏的概率比InnoDB要高很多,而且恢复速度也要慢。因此,**即使不需要事务支持,很多人也选择InnoDB引擎,**这是一个非常重要的因素
    • **特有的特性:**最后,有些应用可能依赖一些存储引擎所独有的特性或者优化,比如很多应用依赖聚簇索引的优化。另外,MySQL中也只有 MyISAM支持地理空间搜索。如果一个存储引擎拥有一些关键的特 性,同时却又缺乏一些必要的特性,那么有时候不得不做折中的考 虑,或者在架构设计上做一些取舍。某些存储引擎无法直接支持的 特性,有时候通过变通也可以满足需求

MySQL查询优化与主从复制

优化Mysql(简要版)

硬件和配置的优化

  • cpu饱和:CPU在饱和的时候一般发生在数据装入内存或从磁盘上读取数据时候
  • io瓶颈:磁盘I/O瓶颈发生在装入数据远大于内存容量的时候,如果应用分布在网络上,那么查询量相当大的时候那么平瓶颈就会出现在网络上

Mysql内部优化

  • sql语句优化

    • 只返回必要的列:最好不要使用select *语句

      • 会查询不需要的列或者大文本的字段,增加数据库解析的工作量,网络开销和磁盘io操作
      • 失去了覆盖索引优化的可能性,比如某一个查询只需要通过辅助索引就可以得到结果,但是用了select*就必须要再去通过聚簇索引去查询所有列,多了一次b+树查询。
    • 只返回必要的行:使用limit语句来限制返回的语句
    • 缓存重复查询的数据:使用缓存可以避免在数据库中进行查询,特别在要查询的数据经常被重复查询时,缓存带来的查询性能提升是非常明显的。
  • 索引优化:

    • 使用自增型的主键
    • 索引的长度尽量短,节省索引空间
    • 基数较小的字段建立索引的效果差,需要考虑
  • 慢查询优化:用慢查询日志记录查询时间很长的查询语句,然后优化相对应的查询语句

    • 查询时,能不要就不用,尽量写全字段名
    • 大部分情况连接效率远大于子查询
    • 多使用explain和profile分析查询语句
    • 查看慢查询日志,找出执行时间长的sql语句优化
    • 多表连接时,尽量小表驱动大表,即小表 join 大表
    • 在千万级分页时使用limit
    • 对于经常使用的查询,可以开启缓存
  • 数据表结构优化

    • 表的字段尽可能用NOT NULL
    • 字段长度固定的表查询会更快
    • 把数据库的大表按时间或一些标志分成小表
    • 将表拆分
      • 数据表拆分:主要就是垂直拆分和水平拆分
      • 水平切分:将记录散列到不同的表中,各表的结构完全相同,每次从分表中查询, 提高效率
      • 垂直切分:将表中大字段单独拆分到另外一张表, 形成一对一的关系

使用EXPLAIN可以分析select查询语句的性能,可以通过分析EXPLAIN结果来优化查询语句。

其中EXPLAIN结果中比较重要的字段有:

  • select_type:查询类型,有简单查询、联合查询、自查询等
  • key:使用的索引
  • rows:扫描的行数

优化数据访问

减少请求的数据量

  • 只返回必要的列:最好不要使用select *语句

    • 会查询不需要的列或者大文本的字段,增加数据库解析的工作量,网络开销和磁盘io操作
    • 失去了覆盖索引优化的可能性,比如某一个查询只需要通过辅助索引就可以得到结果,但是用了select*就必须要再去通过聚簇索引去查询所有列,多了一次b+树查询。
  • 只返回必要的行:使用limit语句来限制返回的语句
  • 缓存重复查询的数据:使用缓存可以避免在数据库中进行查询,特别在要查询的数据经常被重复查询时,缓存带来的查询性能提升是非常明显的。

减少服务端扫描的行数

  • 最有效的方式就是使用索引来覆盖查询

重构查询方式

切分大查询

一个大查询如果一次性执行的话,可能一次锁住很多数据、占满整个事务日志、耗尽系统资源、阻塞很多小的但重要的查询。

分解大连接查询

将一个大连接查询分解成对每一个表进行一次单表查询,然后在应用程序中进行关联,这样做的好处有:

  • 让缓存更高效。对于连接查询,如果其中一个表发生变化,那么整个查询缓存就无法使用。而分解后的多个查询,即使其中一个表发生变化,对其它表的查询缓存依然可以使用。
  • 分解成多个单表查询,这些单表查询的缓存结果更可能被其它查询使用到,从而减少冗余记录的查询。
  • 减少锁竞争;
  • 在应用层进行连接,可以更容易对数据库进行拆分,从而更容易做到高性能和可伸缩。
  • 查询本身效率也可能会有所提升。例如下面的例子中,使用 IN() 代替连接查询,可以让 MySQL 按照 ID 顺序进行查询,这可能比随机的连接要更高效。

MySQL分库分表

主要目的是为了解决单库单表数据过多,查询缓慢的问题,解决数据扩展性问题。

数据库瓶颈

IO瓶颈

  • 磁盘读IO瓶颈,热点数据太多,数据库缓存放不下,每次查询都会产生大量的IO,降低查询速度。适合分库和垂直分表
  • 网络IO瓶颈,请求的数据太多,网络宽带不够。适合分库

CPU瓶颈

  • SQL问题,比如SQL中包含join,group by,order by,非索引字段条件查询等,增加CPU运算的操作。适合使用SQL优化,建立合适的索引,在业务service层进行业务计算解决。
  • 单表数据量太大,查询扫描行太多,SQL效率低,CPU率先出现瓶颈。适合水平分表

分库

水平分库

以字段为依据,按照一定策略(hash,range等),将一个库中的数据拆分到多个库中。

**场景:**系统绝对并发量上来了,分表难以根本上解决问题,并且还没有明显的业务归属来垂直分库。

垂直分库

以表为依据,按照业务归属不同,将不同的表拆分到不同的库中。

**场景:**系统绝对并发量上来了,并且可以抽象出单独的业务模块。随着业务的发展一些公用的配置表,字典表等越来越多,这是可以将这些表拆到单独的库中,甚至可以服务化。再有,随着业务的发展孵化出了一套业务模式,这时可以将相关的表拆到单独的库中,甚至可以服务化。

分表

水平分表

以字段为依据,按照一定策略(hash,range等),将一个表中的数据拆分到多个表中。

**场景:**系统绝对并发量没有上来,只是单表的数据量太多,影响了SQL效率,加重了CPU负担,以至于成为瓶颈。

垂直分表

以字段为依据,按照字段的活跃性,将表中字段拆到不同的表(主表和扩展表)中

**场景:**系统绝对并发量没有上来,表的记录并不多,但是字段多,并且热点数据和非热点数据在一起,单行数据所需的存储空间较大。以至于数据库缓存的数据库减少,查询时回去读取磁盘数据产生大量的随机读IO,产生IO瓶颈。

MySQL主从复制

作用

复制解决的基本问题是让一台服务器的数据与其他服务器保持同步,一台主库的数据可以同步到多台备库上,备库本身也可以被配置成另外一台服务器的主库。主库和备库之间可以有多种不同的组合方式。

MySQL 支持两种复制方式:基于行的复制和基于语句的复制,基于语句的复制也称为逻辑复制,从 MySQL 3.23 版本就已存在,基于行的复制方式在 5.1 版本才被加进来。这两种方式都是通过在主库上记录二进制日志、在备库重放日志的方式来实现异步的数据复制。因此同一时刻备库的数据可能与主库存在不一致,并且无法包装主备之间的延迟。

MySQL 复制大部分是向后兼容的,新版本的服务器可以作为老版本服务器的备库,但是老版本不能作为新版本服务器的备库,因为它可能无法解析新版本所用的新特性或语法,另外所使用的二进制文件格式也可能不同。

复制解决的问题:数据分布、负载均衡、备份、高可用性和故障切换、MySQL 升级测试

步骤

  • 在主库上把数据更改记录到二进制文件binary log中;数据库每次在准备提交事务完成数据更新前,主库将数据更新的事件记录到二进制日志中。Mysql会按照事务提交的顺序而非每条语句的执行顺序来记录二进制文件,在记录二进制日志后,主库会告知存储引擎可以提交事务了。
  • 备库将主库的日志复制到自己的中继日志relay log中;备库首先启动一个工作的IO线程,跟主库建立一个普通的客户端连接,然后再主库上启动一个特殊的二进制转储线程,这个线程会读取主库上二进制日志中的事件。他不会对事件进行轮询。如果该线程最赶上了主库的进度就进入睡眠状态,直到主库发送信号量通知其有新的事件产生时才会被唤醒,备库IO线程会将接收到的事件记录到中继日志中。
  • 备库读取中继日志中的事件,将其重放到备库数据库中;备库的sql线程执行这一步,该线程从中继日志中读取事件并在备库执行,从而实现备库数据的更新。当Sql线程追赶上IO线程时,中继日志通常已经在系统缓存中,所以中继日志的开销很低。SQL 线程执行的时间也可以通过配置选项来决定是否写入其自己的二进制日志中。

MySQL读写分离

读写分离一般有至少两个数据库,一个主数据库,一个从数据库,主数据库用来写操作,从数据库用来读操作。

适合场景

适用于读远大于写的场景,如果只有一台服务器,当select很多时,update和delete会被这些select访问中的数据堵塞,等待select结束,并发性能不高。对于协和读比例相近的应用,应该部署双主相互复制。

优点

  • 主从只负责各自的写和读,极大程度的缓解X锁和S锁的争用(如果读操作被设置为锁的话,InnoDB一般不会对select加锁,不管是不是在事务中)
  • 分摊读取。可以设置一主多从,对读取操作进行分摊。

后端面试知识点总结 数据库 mysql相关推荐

  1. 《Java 后端面试经》数据库篇

    <Java 后端面试经>专栏文章索引: <Java 后端面试经>Java 基础篇 <Java 后端面试经>Java EE 篇 <Java 后端面试经>数 ...

  2. 后端面试知识点总结 其他

    Docker Docker是在容器的基础上,进行了进一步的封装,从文件系统.网络互联到进程隔离等,极大的简化了容器的创建和维护.使得docker技术比虚拟机更为轻便.快捷. 与传统虚拟机比较 传统虚拟 ...

  3. 后端面试知识点大串烧(蚂蚁美团头条腾讯面试经历)

    笔者在面过 猿辅导,去哪儿,旷视, 陌陌,头条, 阿里, 快手, 美团, 腾讯之后,除了收获一大堆面试问题,还思考到如何成为面试官眼中的"爱技术,爱思考,靠谱,有潜力候选人的"一些 ...

  4. mysql 同一张表 某个字段更新到另一条数据上_面试基础:数据库MySQL基础入门(下)...

    本文是面试基础的第二篇.本篇偏理论,包括三节: 事务和并发 数据库设计 索引 所选的三个内容均是面试的高频考察点,需要细致地理解 No.1     事务和并发 事务:数据库操作的基本单元.对于数据库的 ...

  5. 后端面试知识点总结 设计模式

    设计模式 设计模式(Design Pattern)代表了最佳实践.设计模式是软件开发人员在软件开发过程中面临的一般问题的解决问题. 设计模式是一套被反复使用的.多人知晓的.经过分类编目的.代码设计经验 ...

  6. 《Java 后端面试经》Java 基础篇

    <Java 后端面试经>专栏文章索引: <Java 后端面试经>Java 基础篇 <Java 后端面试经>Java EE 篇 <Java 后端面试经>数 ...

  7. 《Java 后端面试经》微服务篇

    <Java 后端面试经>专栏文章索引: <Java 后端面试经>Java 基础篇 <Java 后端面试经>Java EE 篇 <Java 后端面试经>数 ...

  8. mysql存储base64位用什么类型_【漫画】面试现场:为什么MySQL数据库要用B+树存储索引?...

    推荐阅读:MySQL最全整理(面试题+笔记+导图),面试大厂不再被MySql难倒! 小史是一个应届生,虽然学的是电子专业,但是自己业余时间看了很多互联网与编程方面的书,一心想进BAT互联网公司. 话说 ...

  9. Java 面试知识点解析(六)——数据库篇

    前言: 在遨游了一番 Java Web 的世界之后,发现了自己的一些缺失,所以就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Java 知识点进行复习和学习一番,大 ...

最新文章

  1. 在Proteus中添加标号
  2. Router Modules模块化
  3. python定义一个矩形类_创建矩形类
  4. linux系统路由功能记录
  5. Android ContentProvider
  6. python冒泡算法_python_冒泡算法
  7. PHP响应式H5图片网盘外链系统源码 自适应PC手机端
  8. Vue.js实现可配置的登录表单
  9. Ubuntu视频教程
  10. Stemming and lemmatization
  11. 【MTSP】遗传和粒子群算法求解多旅行商问题【Matlab 1156期】
  12. Go函数和方法的区别
  13. java js hexmd5_JAVA与JS在MD5上问题
  14. PL-VIO学习+注释
  15. 【IoT】智能硬件设计:遥控窗帘设计
  16. 熬夜加班问题总结反思
  17. python编写字典库_Python中的字典及举例-阿里云开发者社区
  18. python完成文件夹批量word转pdf文件及pdf文件合并+word文件合并
  19. 交换机为什么需要划分AP、AG和SW三个层?
  20. ST-DBSCAN算法简述及其python实现

热门文章

  1. CentOS 7上编译安装PHP 8.1及Nginx 配置支持PHP
  2. javascript运算符_双重否定运算符是什么! 用JavaScript做?
  3. (JAVA实现)平衡二叉树的判断
  4. 无人驾驶车辆运动规划方法综述
  5. js中iif的真假条件的判断方式
  6. 纪念一下| 上传资源的创作者等级升级到Lv3
  7. 【转载】eMule电驴使用从入门到精通(8)-------代理和高ID、低ID
  8. 解决Google AdSense导致网站加载慢的优化方法
  9. UrlRewriter url 地址重写
  10. 性的短暂而致真情永失