数据库面试总结

Posted 肖帆咪

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据库面试总结相关的知识,希望对你有一定的参考价值。

数据库

索引

数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询,更新数据库中表的数据.索引的实现通常使用B树和变种的B+树(mysql常用的索引就是B+树)。除了数据之外,数据库系统还维护为满足特定查找算法的数据结构,这些数据结构以某种方式引用数据.这种数据结构就是索引!

左连接右连接内连接

left join(左联接) 返回包括左表中的所有记录和右表中联结字段相等的记录 (通俗点讲就是,左边的表是老大,要显示全部内容,右边的表是小弟,只有跟老大相同的东西才显示出来,不相同的就是null

right join(右联接) 返回包括右表中的所有记录和左表中联结字段相等的记录(通俗点讲就是,右边的表是老大,要显示全部内容,左边的表是小弟,只有跟老大相同的东西才显示出来,不相同的就是null

inner join(等值连接|全连接也叫内连接) 只返回两个表中联结字段相等的行(左右两边势均力敌,不相上下。两边都不想妥协,所以只有共同的东西才显示出来)

什么是最左前缀原则?什么是最左匹配原则

顾名思义,就是最左优先,在创建多列索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边。

最左前缀匹配原则,非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a=1 and b=2 and c>3 and d=4如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。

=和in可以乱序,比如a=1 and b=2 and c=3建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式

数据库为什么使用B+树而不是B树

B+树

  1. 随机检索和顺序检索,B树只适合随机检索
  2. B+树空间利用率更高,可减少I/O次数,磁盘读写代价更低
  3. B+树的叶子节点使用指针顺序连接在一起,只要遍历叶子节点就可以实现整棵树的遍历。
  4. 增删文件(节点)时,效率更高。因为B+树的叶子节点包含所有关键字,并以有序的链表结构存储,这样可很好提高增删效率
  5. B+树的内部结点,只是作为索引使用,其内部结点比B树小,盘块能容纳的结点数量更多,一次性读入内存中可以查找的关键字也就越多,相对的,IO读写次数也就降低了。
  6. B+树中只有叶子节点会带有指向记录的指针(ROWID),而B树则所有节点都带有,在内部节点出现的索引项不会再出现在叶子节点中。
  7. B+树中所有叶子节点都是通过指针连接在一起,而B树不会。

B树的优点:
对于在内部节点的数据,可直接得到,不必根据叶子节点来定位。

红黑树

AVL树定义:

1>它的左子树和右子树都是AVL树
2>左子树和右子树的高度差不能超过1

红黑树并不是一个完美平衡二叉查找树

  1. 每个节点要么是黑色,要么是红色。
  2. 根节点是黑色。
  3. 每个叶子节点(NIL)是黑色。
  4. 每个红色结点的两个子结点一定都是黑色。
  5. 任意一结点到每个叶子结点的路径都包含数量相同的黑结点。

红黑树统计性能比AVL树更高

红黑树能够以O(log2 n) 的时间复杂度进行搜索、插入、删除操作。

红黑树的算法时间复杂度和AVL相同,但统计性能比AVL树更高,典型的用途是实现关联数组。

AVL树查找、插入和删除在平均和最坏情况下都是O(log n)。

AVL查找效率高

如果你的应用中,搜索的次数远远大于插入和删除,那么选择AVL树,如果搜索,插入删除次数几乎差不多,应选择红黑树。即,有时仅为了排序(建立-遍历-删除),不查找或查找次数很少,R-B树合算一些。

调整平衡的实现机制不同:

红黑树根据节点颜色(同一双亲节点出发到哨兵节点,所有路径上的黑色节点数目一样),一些约定和旋转实现;

AVL根据树的平衡因子(所有节点的左右子树高度差的绝对值不超过1)和旋转决定

红黑树的插入效率更高!!!

红黑树是用非严格的平衡来换取增删节点时候旋转次数的降低,任何不平衡都会在三次旋转之内解决,红黑树并不追求“完全平衡”,它只要求部分地达到平衡要求,降低了对旋转的要求,从而提高了性能

而AVL是严格平衡树(高度平衡的二叉搜索树),因此在增加或者删除节点的时候,根据不同情况,旋转的次数比红黑树要多。所以红黑树的插入效率更高

索引失效

  1. – 查询语句中使用LIKE关键字:(这种情况主要是针对于单列索引)

    – 在使用LIKE关键字查询时,如果匹配字符串的第一个字符为’%’,则索引不会被使用,而’%'不在最左边,而是在右边,则索引会被使用到!

-- eg:
SELECT * FROM t_user WHERE name LIKE 'xx%' -- 可以利用上索引,这种情况下可以拿xx到索引树上去匹配
SELECT * FROM t_user WHERE name LIKE '%xx%' -- 不可以利用上索引
SELECT * FROM t_user WHERE name LIKE '%xx' -- 不可以利用上索引
  1. – 查询语句中使用多列索引:(这种情况主要是针对于聚合索引)

    – 多索引是在表的多个字段创建索引,只有查询条件中使用了这些字段中的第一个字段,索引才会被使用。即:最左前缀原则,

  2. – 查询语句中使用OR关键字:

    – 查询条件中有OR关键字时,如果OR前后的两个条件列都具有索引,则查询中索引将被使用,而如果OR前后有一个或2个列不具有索引,那么查询中索引将不被使用到!

索引分类

主键索引:PRIMARY KEY

设定为主键后,数据库自动建立索引,innodb为聚簇索引,主键索引列值不能有空(Null)

**单值索引:**又叫单列索引、普通索引即,一个索引只包含单个列,一个表可以有多个单列索引

**唯一索引:**索引列的值必须唯一,但允许有空值(Null),但只允许有一个空值(Null)

**复合索引:**即,一个索引可以包含多个列,多个列共同构成一个复合索引!(可能会造成索引失效)
eg: SELECT id (name age) INDEX WHERE name AND age;

**全文索引:**Full Text (MySQL5.7之前,只有MYISAM存储引擎支持全文索引)
全文索引类型为FULLTEXT,在定义索引的列上支持值的全文查找,允许在这些索引列中插入重复值和空值。全文索引可以在Char 、Varchar 上创建。

主键索引和唯一索引的区别

  • 主键是一种约束,唯一索引是一种索引,两者在本质上是不同的。
  • 主键创建后一定包含一个唯一性索引,唯一性索引并不一定就是主键。
  • 唯一性索引列允许空值,而主键列不允许为空值。
  • 主键列在创建时,已经默认为空值 + 唯一索引了。
  • 主键可以被其他表引用为外键,而唯一索引不能。
  • 一个表最多只能创建一个主键,但可以创建多个唯一索引。
  • 主键更适合那些不容易更改的唯一标识,如自动递增列、身份证号等。
  • 在 RBO 模式下,主键的执行计划优先级要高于唯一索引。 两者可以提高查询的速度。

哪些情况需要索引

  1. 主键自动建立唯一索引
  2. 频繁作为查询条件的字段应该创建索引(where 后面的语句)
  3. 查询中与其它表关联的字段,外键关系建立索引
  4. 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度

哪些情况不要创建索引

  1. 表记录太少
  2. 经常增删改的表:提高了查询速度,同时却会降低更新表的速度
  3. Where 条件里用不到的字段不创建索引
  4. 数据重复且分布平均的表字段,因此应该只为最经常查询和最经常排序的数据列建立索引,某个数据列包含许多重复的内容,建立索引没有太大实际效果。

什么是聚簇索引?何时使用聚簇索引与非聚簇索引

聚簇索引:将数据存储与索引放到了一块,找到索引也就找到了数据

非聚簇索引:将数据存储于索引分开结构,索引结构的叶子节点指向了数据的对应行,myisam通过key_buffer把索引先缓存到内存中,当需要访问数据时(通过索引访问数据),在内存中直接搜索索
引,然后通过索引找到磁盘相应数据,这也就是为什么索引不在key buffer命中时,速度慢的原因

非聚簇索引一定会回表查询吗

不一定,这涉及到查询语句所要求的字段是否全部命中了索引

select age from employee where age < 20的查询时,在索引的叶子节点上,已经包含了age信息,不会再次进行回表查询

使用聚簇索引的优势

– 1.由于行数据和聚簇索引树的叶子节点存储在一起,同一页中会有多条行数据,首次访问数据页中某条行记录时,会把该数据页数据加载到Buffer(缓存器)中,当再次访问该数据页中其他记录时,不必访问磁盘而直接在内存中完成访问。
– 注:主键id和行数据一起被载入内存,找到对应的叶子节点就可以将行数据返回了,如果按照主键id来组织数据,获取数据效率更快!

– 2.辅助索引的叶子节点,存储主键的值,而不是行数据的存放地址。这样做的好处是,因为叶子节点存放的是主键值,其占据的存储空间小于存放行数据物理地址的储存空间

使用聚簇索引需要注意什么?

– 当使用主键为聚簇索引时,而不要使用UUID方式,因为UUID的值太过离散,不适合排序,导致索引树调整复杂度增加,消耗更多时间和资源。

– 建议主键最好使用INT/BIGINT类型,且为自增,这样便于排序且默认会在索引树的末尾增加主键值,对索引树的结构影响最小(下面主键自增的问题会解释原因)。而且主键占用的存储空间越大,辅助索引中保存的主键值也会跟着增大,占用空间且影响IO操作读取数据!

为什么主键通常建议使用自增id?

– 聚簇索引树存放数据的物理地址(xx1,xx2,xx3,xxx5)与索引顺序(1,2,3,5)是一致的,即:
– 1.只要索引是相邻的,那么在磁盘上索引对应的行数据存放地址也是相邻的。
– 2.如果主键是自增,那么当插入新数据时,只需要按照顺序在磁盘上开辟新物理地址存储新增行数据即可。
– 3.而如果不是主键自增,那么当新插入数据后,会对索引进行重新排序(重新调整B+树结构),磁盘上的物理存储地址也需要重新分配要存储的行数据!

当前读,快照读和MVCC的关系

MVCC 多版本并发控制是 「维持一个数据的多个版本,使得读写操作没有冲突」 的概念,只是一个抽象概念,并非实现
因为 MVCC 只是一个抽象概念,要实现这么一个概念,MySQL 就需要提供具体的功能去实现它,「快照读就是 MySQL 实现 MVCC 理想模型的其中一个非阻塞读功能」。而相对而言,当前读就是悲观锁的具体功能实现
要说的再细致一些,快照读本身也是一个抽象概念,再深入研究。MVCC 模型在 MySQL 中的具体实现则是由 3 个隐式字段,undo 日志 ,Read View 等去完成的,具体可以看下面的 MVCC 实现原理
MVCC 能解决什么问题,好处是?

数据库并发场景有三种,分别为:

读-读:不存在任何问题,也不需要并发控制
读-写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读
写-写:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失

数据库的三范式是什么?

  • 第一范式:强调的是列的原子性,即数据库表的每一列都是不可分割的原子数据项。
  • 第二范式:要求实体的属性完全依赖于主关键字。所谓完全 依赖是指不能存在仅依赖主关键字一部分的属性。
  • 第三范式:任何非主属性不依赖于其它非主属性。

存储引擎?

MySQL 支持多种存储引擎,比如 InnoDB,MyISAM,Memory,Archive 等等.在大多数的情况下,直接选择使用 InnoDB 引擎都是最合适的,InnoDB 也是 MySQL 的默认存储引擎。

MyISAM 和 InnoDB 的区别有哪些:

  • InnoDB 支持事务,MyISAM 不支持
  • InnoDB 支持外键,而 MyISAM 不支持
  • InnoDB 是聚集索引,数据文件是和索引绑在一起的,必须要有主键,通过主键索引效率很高;MyISAM 是非聚集索引,数据文件是分离的,索引保存的是数据文件的指针,主键索引和辅助索引是独立的。
  • Innodb 不支持全文索引,而 MyISAM 支持全文索引,查询效率上 MyISAM 要高;
  • InnoDB 不保存表的具体行数,MyISAM 用一个变量保存了整个表的行数。
  • MyISAM 采用表级锁(table-level locking);InnoDB 支持行级锁(row-level locking)和表级锁,默认为行级锁。

超键、候选键、主键、外键分别是什么?

  • 超键:在关系中能唯一标识元组的属性集称为关系模式的超键。一个属性可以为作为一个超键,多个属性组合在一起也可以作为一个超键。超键包含候选键和主键。
  • 候选键:是最小超键,即没有冗余元素的超键。
  • 主键:数据库表中对储存数据对象予以唯一和完整标识的数据列或属性的组合。一个数据列只能有一个主键,且主键的取值不能缺失,即不能为空值(Null)。
  • 外键:在一个表中存在的另一个表的主键称此表的外键。

SQL 约束有哪几种?

  • NOT NULL: 用于控制字段的内容一定不能为空(NULL)。
  • UNIQUE: 控件字段内容不能重复,一个表允许有多个 Unique 约束。
  • PRIMARY KEY: 也是用于控件字段内容不能重复,但它在一个表只允许出现一个。
  • FOREIGN KEY: 用于预防破坏表之间连接的动作,也能防止非法数据插入外键列,因为它必须是它指向的那个表中的值之一。
  • CHECK: 用于控制字段的值范围。

MySQL中 in 和 exists 区别

MySQL中的in语句是把外表和内表作hash 连接,而exists语句是对外表作loop循环,每次loop循环再对内表进行查询。一直大家都认为exists比in语句的效率要高,这种说法其实是不准确的。这个是要区分环境的。

如果查询的两个表大小相当,那么用in和exists差别不大。
如果两个表中一个较小,一个是大表,则子查询表大的用exists,子查询表小的用in。
not in 和not exists:如果查询语句使用了not in,那么内外表都进行全表扫描,没有用到索引;而not extsts的子查询依然能用到表上的索引。所以无论那个表大,用not exists都比not in要快。

视图

为了提高复杂SQL语句的复用性和表操作的安全性,MySQL数据库管理系统提供了视图特性。本质上是一种虚拟表,在物理上是不存在的,其内容与真实的表相似,包含一系列带有名称的列和行数据。但是,视图并不在数据库中以储存的数据值形式存在。行和列数据来自定义视图的查询所引用基本表,并且在具体引用视图时动态生成。

优点:提高复杂sql语句的复用性和表操作的安全性

缺点:. 修改限制,基本表发生改变,视图可能出问题

游标

游标是系统为用户开设的一个数据缓冲区,存放SQL语句的执行结果,每个游标区都有一个名字。用户可以通过游标逐一获取记录并赋给主变量,交由主语言进一步处理

存储过程

存储过程是一个预编译的SQL语句,优点是允许模块化的设计,就是说只需要创建一次,以后在该程序中就可以调用多次。如果某次操作需要执行多次SQL,使用存储过程比单纯SQL语句执行要快。

优点

  1. 存储过程是预编译过的,执行效率高。存储过程的代码直接存放于数据
  2. 库中,通过存储过程名直接调用,减少网络通讯。
  3. 安全性高,执行存储过程需要有一定权限的用户。
  4. 存储过程可以重复使用,减少数据库开发人员的工作量。

缺点

  1. 调试麻烦,但是用 PL/SQL Developer 调试很方便!弥补这个缺点。
  2. 移植问题,数据库端代码当然是与数据库相关的。但是如果是做工程型项目,基本不存在移植问题。
  3. 重新编译问题,因为后端代码是运行前编译的,如果带有引用关系的对象发生改变时,受影响的存储过程、包将需要重新编译(不过也可以设置成运行时刻自动编译)。
  4. 如果在一个程序系统中大量的使用存储过程,到程序交付使用的时候随着用户需求的增加会导致数据结构的变化,接着就是系统的相关问题了, 后如果用户想维护该系统可以说是很难很难、而且代价是空前的,维护起来更麻烦。

MySQL 执行查询的过程

  1. 客户端通过 TCP 连接发送连接请求到 MySQL 连接器,连接器会对该请求进行权限验证及连接资源分配
  2. 查缓存。当判断缓存是否命中时,MySQL 不会进行解析查询语句,而是直接使用 SQL 语句和客户端发送过来的其他原始信息。所以,任何字符上的不同,例如空格、注解等都会导致缓存的不命中。
  3. 语法分析(SQL 语法是否写错了)。 如何把语句给到预处理器,检查数据表和数据列是否存在,解析别名看是否存在歧义。
  4. 优化。是否使用索引,生成执行计划。
  5. 交给执行器,将数据保存到结果集中,同时会逐步将数据缓存到查询缓存中,最终将结果集返回给客户端

事务

什么是数据库事务?

事务是一个不可分割的数据库操作序列,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。事务是逻辑上的一组操作,要么都执行,要么都不执行。

介绍一下事务具有的四个特征

事务就是一组原子性的操作,这些操作要么全部发生,要么全部不发生。事务把数据库从一种一致性状态转换成另一种一致性状态。

  • 原子性: 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
  • 一致性: 执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;
  • 隔离性: 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
  • 持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

MySQL 的四种隔离级别

  • Read Uncommitted(读取未提交内容)

在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。

  • Read Committed(读取提交内容)

这是大多数数据库系统的默认隔离级别(但不是 MySQL 默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓 的 不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的 commit,所以同一 select 可能返回不同结果。

  • Repeatable Read(可重读)

这是 MySQL 的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。

  • Serializable(可串行化)

通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。

MySQL 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别

事务隔离机制的实现基于锁机制和并发调度。其中并发调度使用的是MVVC(多版本并发控制),通过保存修改的旧版本信息来支持并发一致性读和回滚等特性。

因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内容):,但是你要知道的是InnoDB 存储引擎默认使用 **REPEATABLE-READ(可重读)**并不会有任何性能损失。

InnoDB 存储引擎在 分布式事务 的情况下一般会用到**SERIALIZABLE(可串行化)**隔离级别。

脏读?幻读?不可重复读?

  1. 脏读(Drity Read):某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。
  2. 不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。
  3. 幻读(Phantom Read):在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的。

不可重复读侧重于修改,幻读侧重于新增或删除(多了或少量行),脏读是一个事务回滚影响另外一个事务。

事务的实现原理

事务是基于重做日志文件(redo log)和回滚日志(undo log)实现的。

每提交一个事务必须先将该事务的所有日志写入到重做日志文件进行持久化,数据库就可以通过重做日志来保证事务的原子性和持久性。

每当有修改事务时,还会产生 undo log,如果需要回滚,则根据 undo log 的反向语句进行逻辑操作,比如 insert 一条记录就 delete 一条记录。undo log 主要实现数据库的一致性。

SQL 标准定义了四个隔离级别:

READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。

READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。

REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。

SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。

事务日志介绍下

innodb 事务日志包括 redo log 和 undo log。

undo log 指事务开始之前,在操作任何数据之前,首先将需操作的数据备份到一个地方。redo log 指事务中操作的任何数据,将最新的数据备份到一个地方。

事务日志的目的:实例或者介质失败,事务日志文件就能派上用场。

redo lo

redo log 不是随着事务的提交才写入的,而是在事务的执行过程中,便开始写入 redo 中。

防止在发生故障的时间点,尚有脏页未写入磁盘,在重启 MySQL 服务的时候,根据 redo log 进行重做,从而达到事务的未入磁盘数据进行持久化这一特性。
RedoLog 是为了实现事务的持久性而出现的产物。

undo log

undo log 用来回滚行记录到某个版本。

事务未提交之前,Undo 保存了未提交之前的版本数据,Undo 中的数据可作为数据旧版本快照供其他并发事务进行快照读。

什么是MySQL的 binlog?

MySQL的 binlog 是记录所有数据库表结构变更(例如 CREATE、ALTER TABLE)以及表数据修改(INSERT、UPDATE、DELETE)的二进制日志。

binlog 不会记录 SELECT 和 SHOW 这类操作,因为这类操作对数据本身并没有修改,但你可以通过查询通用日志来查看 MySQL 执行过的所有语句。

MySQL binlog 以事件形式记录,还包含语句所执行的消耗的时间,MySQL 的二进制日志是事务安全型的。binlog 的主要目的是复制和恢复。

binlog 有三种格式,各有优缺点:

  • statement: 基于 SQL 语句的模式,某些语句和函数如 UUID, LOAD DATA INFILE 等在复制过程可能导致数据不一致甚至出错。
  • row: 基于行的模式,记录的是行的变化,很安全。但是 binlog 会比其他两种模式大很多,在一些大表中清除大量数据时在 binlog 中会生成很多条语句,可能导致从库延迟变大。
  • mixed: 混合模式,根据语句来选用是 statement 还是 row 模式。

MySQL中是如何实现事务隔离的?

读未提交和串行化基本上是不需要考虑的隔离级别,前者不加锁限制,后者相当于单线程执行,效率太差。

MySQL 在可重复读级别解决了幻读问题,是通过行锁和间隙锁的组合 Next-Key 锁实现的。

MVCC的好处

MVCC, 即多版本并发控制。MVCC 的实现,是通过保存数据在某个时间点的快照来实现的。根据事务开始的时间不同,每个事务对同一张表,同一时刻看到的数据可能是不一样的。

在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能

同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题

MVCC 的实现原理

对于 InnoDB ,聚簇索引记录中包含 3 个隐藏的列:

  • ROW ID:隐藏的自增 ID,如果表没有主键,InnoDB 会自动按 ROW ID 产生一个聚集索引树。
  • 事务 ID:记录最后一次修改该记录的事务 ID。
  • 回滚指针:指向这条记录的上一个版本。

InnoDB 每一行数据都有一个隐藏的回滚指针,用于指向该行修改前的最后一个历史版本,这个历史版本存放在 undo log 中。如果要执行更新操作,会将原记录放入 undo log 中,并通过隐藏的回滚指针指向 undo log 中的原记录。其它事务此时需要查询时,就是查询 undo log 中这行数据的最后一个历史版本。

MVCC 最大的好处是读不加锁,读写不冲突,极大地增加了 MySQL 的并发性。通过 MVCC,保证了事务 ACID 中的 I(隔离性)特性。

为什么要加锁

当多个用户并发地存取数据时,在数据库中就会产生多个事务同时存取同一数据的情况。若对并发操作不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性。

保证多用户环境下保证数据库完整性和一致性。

按照锁的粒度分数据库锁有哪些?

行级锁

行锁的种类

  • 行级锁(Record Locks):单个行记录上的锁。
  • 间隙锁(Gap Locks):间隙锁,锁定一个范围,但不包括记录本身。比如锁定a=5以及其前后2个范围内的数据,也就是将a=3,4,6,7这些行都锁了起来,不包括a=5本身。GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况。
  • 临键锁(Next-key Locks):锁定一个范围,并且锁定记录本身。比如锁定a=5以及其前后2个范围内的数据,也就是将a=3,4,5,6,7这些行都锁了起来。对于行的查询,都是采用该方法,主要目的是解决幻读的问题

行锁的特点

  • 行级锁是MySQL中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。行级锁分为共享锁 和 排他锁。
  • 开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。

间隙锁Gap Lock

间隙锁,锁定一个范围,但不包括记录本身(它的锁粒度比记录锁的锁整行更大一些,他是锁住了某个范围内的多个行,包括根本不存在的数据),隙锁一定是开区间,比如(3,5)。

GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况。该锁只会在隔离级别是 可重复读或者以上的级别内存在。间隙锁的目是为了让其他事务无法在间隙中新增数据

临键锁Next-Key Lock

是记录锁和间隙锁的结合,锁定一个范围,并且锁定记录本身。对于行的查询,都是采用该方法,主要目的是解决幻读的问题。next-key锁是InnoDB默认的锁,临键锁是是一个左开右闭的区间,比如(3,5]。

next-key lock的效果相当于一个记录锁加一个间隙锁。当next-key lock加在某索引上,则该记录和它前面的区间都被锁定。假设有记录1, 3, 5, 7,现在记录5上加next-key lock,则会锁定区间(3, 5],任何试图插入到这个区间的记录都会阻塞。

record lock、gap lock、next-key lock,都是加在索引上的。假设有记录1,3,5,7,则5上的记录锁会锁住5,5上的gap lock会锁住(3,5),5上的next-key lock会锁住(3,5]。

注意,next-Key锁规定是左开右闭区间!

表级锁

  • 表级锁是MySQL中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持。最常使用的MYISAM与INNODB都支持表级锁定。表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁)。
  • 开销小,加锁快;不会出现死锁;锁定粒度大,发出锁冲突的概率最高,并发度最低。

页级锁

  • 页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。BDB支持页级锁
  • 开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般

MyISAM和InnoDB存储引擎使用的锁:

  • MyISAM采用表级锁(table-level locking)。
  • InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁

数据库的乐观锁和悲观锁是什么?怎么实现的?

​ 数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段。

  • 悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。在查询完数据的时候就把事务锁起来,直到提交事务。
  • **实现方式:**使用数据库中的锁机制
  • 乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。在修改数据的时候把事务锁起来,通过version的方式来进行锁定。
  • 实现方式:乐一般会使用版本号机制或CAS算法实现。

两种锁的使用场景

从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。

但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。

分库分表

为什么要分库分表?

分表

比如你单表都几千万数据了,你确定你能扛住么?绝对不行,单表数据量太大,会极大影响你的 sql执行的性能,到了后面你的 sql 可能就跑的很慢了。一般来说,就以我的经验来看,单表到几百万的时候,性能就会相对差一些了,你就得分表了。

分表就是把一个表的数据放到多个表中,然后查询的时候你就查一个表。比如按照用户 id 来分表,将一个用户的数据就放在一个表中。然后操作的时候你对一个用户就操作那个表就好了。这样可以控制每个表的数据量在可控的范围内,比如每个表就固定在 200 万以内。

分库

分库就是你一个库一般我们经验而言,最多支撑到并发 2000,一定要扩容了,而且一个健康的单库并发值你最好保持在每秒 1000 左右,不要太大。那么你可以将一个库的数据拆分到多个库中,访问的时候就访问一个库好了。

数据库如何进行垂直拆分或水平拆分

水平拆分的意思,就是把一个表的数据给弄到多个库的多个表里去,但是每个库的表结构都一样,只不过每个库表放的数据是不同的,所有库表的数据加起来就是全部数据。水平拆分的意义,就是将数据均匀放更多的库里,然后用多个库来抗更高的并发,还有就是用多个库的存储容量来进行扩容。

垂直拆分的意思,就是把一个有很多字段的表给拆分成多个表或者是多个库上去。每个库表的结构都不一样,每个库表都包含部分字段。一般来说,会将较少的访问频率很高的字段放到一个表里去,然后将较多的访问频率很低的字段放到另外一个表里去。因为数据库是有缓存的,你访问频率高的行字段越少,就可以在缓存里缓存更多的行,性能就越好。这个一般在表层面做的较多一些。

读写分离、主从同步(复制)

如何做慢 SQL 优化?

首先要搞明白慢的原因是什么:是查询条件没有命中索引?还是 load 了不需要的数据列?还是数据量太大?所以优化也是针对这三个方向来的:

​ 首先用 explain 分析语句的执行计划,查看使用索引的情况,是不是查询没走索引,如果可以加索引解决,优先采用加索引解决。

​ 分析语句,看看是否存在一些导致索引失效的用法,是否 load 了额外的数据,是否加载了许多结果中并不需要的列,对语句进行分析以及重写。

​ 如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行垂直拆分或者水平拆分。

什么是MySQL主从同步?

主从同步使得数据可以从一个数据库服务器复制到其他服务器上,在复制数据时,一个服务器充当主服务器(master),其余的服务器充当从服务器(slave)。

因为复制是异步进行的,所以从服务器不需要一直连接着主服务器,从服务器甚至可以通过拨号断断续续地连接主服务器。通过配置文件,可以指定复制所有的数据库,某个数据库,甚至是某个数据库上的某个表。

为什么要做主从同步

  1. 通过增加从服务器来提高数据库的性能,在主服务器上执行写入和更新,在从服务器上向外提供读功能,可以动态地调整从服务器的数量,从而调整整个数据库的性能。
  2. 提高数据安全-因为数据已复制到从服务器,从服务器可以终止复制进程,所以,可以在从服务器上备份而不破坏主服务器相应数据
  3. 在主服务器上生成实时数据,而在从服务器上分析这些数据,从而提高主服务器的性能
  4. 数据备份。一般我们都会做数据备份,可能是写定时任务,一些特殊行业可能还需要手动备份,有些行业要求备份和原数据不能在同一个地方,所以主从就能很好的解决这个问题,不仅备份及时,而且还可以多地备份,保证数据的安全

如何实现MySQL的读写分离

其实很简单,就是基于主从复制架构,简单来说,就搞一个主库,挂多个从库,然后我们就单单只是写主库,然后主库会自动把数据给同步到从库上去。

MySQL优化

大表数据查询优化

  • 优化sql语句+索引;
  • 第二加缓存,memcached, redis
  • 主从复制,读写分离;
  • 垂直拆分,根据你模块的耦合度,将一个大的系统分为多个小的系统,也就是分布式系统;
  • 水平切分,针对数据量大的表,这一步最麻烦,最能考验技术水平,要选择一个合理的sharding key, 为了有好的查询效率,表结构也要改动,做一定的冗余,应用也要改,sql中尽量带sharding key,将数据定位到限定的表上去查,而不是扫描全部的表;
  • 如果小的循环在外层,对于数据库连接来说就只连接5次,进行5000次操作,如果1000在外,则需要进行1000次数据库连接,从而浪费资源,增加消耗。这就是为什么要小表驱动大表。

慢查询优化

  • 首先分析语句,看看是否load了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载了许多结果中并不需要的列,对语句进行分析以及重写。
  • 分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中索引。
  • 如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行横向或者纵向的分表。

大表优化

类似的问题:某个表有近千万数据,CRUD比较慢,如何优化?分库分表了是怎么做的?分表分库了有什么问题?有用到中间件么?他们的原理知道么?

当MySQL单表记录数过大时,数据库的CRUD性能会明显下降,一些常见的优化措施如下:

  • 限定数据的范围: 务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内;
  • 读/写分离: 经典的数据库拆分方案,主库负责写,从库负责读;
  • 缓存: 使用MySQL的缓存,另外对重量级、更新少的数据可以考虑;
  • 通过分库分表的方式进行优化,主要有垂直分表和水平分表。

以上是关于数据库面试总结的主要内容,如果未能解决你的问题,请参考以下文章

display:inline-block; 去除间隙的方法 总结:

核心面试题:MVCC间隙锁Undo Log链表级锁行级锁页级锁共享锁排它锁记录锁等等

面试管:手写一个二叉树的前序遍历吧!

python 二叉树

数据结构与算法面试题80道(11)

#yyds干货盘点# 面试必备常见存储引擎与锁的分类,请查收