秋招面经(后端开发)——数据库篇(MySQL)

其他后端所需技术栈个人总结链接汇总:

  • 秋招面经(后端开发)——语言篇(C++)
  • C++ STL容器底层学习(个人总结)
  • 秋招面经(后端开发)——数据库篇(MySQL)
  • 秋招面经(后端开发)——操作系统篇
  • 秋招面经(后端开发)——计算机网络篇

1、SQL

查询语句总结:

	select			5
		...
	from			1
		...
	where			2
		...
	group by		3 	# 在 SQL 语句中若有 group by 语句,那么在 select 语句后面只能跟分组函数+参与分组的字段。
		...
	having			4
		...
	order by		6
		...
	limit			7
		...;		

以上语句的执行顺序为:

  1. 首先执行 where 语句过滤原始数据
  2. 执行 group by 进行分组
  3. 执行 having 对分组数据进行操作
  4. 执行 select 选出数据
  5. 执行 order by 排序
  6. 执行 limit 进行分页

原则: 能在 where 中过滤的数据,尽量在 where 中过滤,效率较高。having 的过滤是专门对分组之后的数据进行过滤的。

1.1 介绍一下数据库分页

MySQL 的分页语法:
使用 LIMIT 语句,后边有两个参数,第一个为行号,第二个为查询几个

-- 在所有的查询结果中,返回前5行记录。
SELECT prod_name FROM products LIMIT 5;
-- 在所有的查询结果中,从第5行开始,返回5行记录。
SELECT prod_name FROM products LIMIT 5,5;

优化 LIMIT 分页:
方法:使用覆盖索引,通过索引快速找到所搜的行数,然后再查找所有。

SELECT film.film_id,film.description
FROM sakila.film
INNER JOIN (
    SELECT film_id FROM sakila.film ORDER BY title LIMIT 50,5 # 根据字段film_id创建的索引
) AS lim USING(film_id);

1.2 SQL 中的聚合函数

函数名 作用
count() 取得记录数
sum() 求和
avg() 取平均数
max() 取最大的数
min() 取最小的数

注意:分组函数自动忽略空值,不需要手动的加 where 条件排除空值。

分组函数

在 SQL 语句中若有 group by 语句,那么在 select 语句后面只能跟分组函数+参与分组的字段

1.3 表跟表是怎么关联的?

总共两种方式:内连接外连接

内连接:

  • 描述: 假设 A 和 B 表进行连接,使用内连接的话,凡是 A 表和 B 表能够匹配上的记录查询出来,这就是内连接。AB 两张表没有主副之分,两张表是平等的。
  • 结果: 它将返回两张表中满足连接条件的数据,不满足条件的数据不会查询出来。

外连接:

  • 描述: 假设 A 和 B 表进行连接,使用外连接的话,AB 两张表中有一张表是主表,一张表是副表,主要查询主表中的数据,捎带着查询副表,当副表中的数据没有和主表中的数据匹配上,副表自动模拟出 NULL 与之匹配。
  • 结果: 它会返回两张表中满足连接条件的数据,同时返回主表中不满足连接条件的数据。
  • 分类: 左连接和右连接
    • 左外连接(左连接):表示左边的这张表是主表。
    • 右外连接(右连接):表示右边的这张表是主表。

常见的关联关系: 一对多关联、多对多关联、自关联。

  • 一对多关联: 这种关联形式最为常见,一般是两张表具有主从关系,并且以主表的主键关联从表的外键来实现这种关联关系。
  • 多对多关联: 这种关联关系比较复杂,如果两张表具有多对多的关系,那么它们之间需要有一张中间表来作为连接,以实现这种关联关系。
    • 中间表: 这个中间表要设计两列,分别存储那两张表的主键。因此,这两张表中的任何一方,都与中间表形成了一对多关系,从而在这个中间表上建立起了多对多关系。
  • 自关联: 自关联就是一张表自己与自己相关联,为了避免表名的冲突,需要在关联时通过别名将它们当做两张表来看待。

1.4 SQL 中怎么将行转成列?

两种方式:

  1. 使用 CASE...WHEN...THEN 语句实现行转列
  2. 使用 IF() 函数实现行转列

1.5 谈谈对 SQL 注入的理解

原理: 客户端通过将 SQL 代码伪装到输入参数中,传递到服务器解析,服务端在执行 SQL 操作时,会拼接对应参数,同时也将一些 SQL 注入攻击的 “SQL” 拼接起来,导致会执行一些预期之外的操作。

解决办法:

  1. 严格的参数校验。
  2. SQL预编译: 通过 SQL 预编译,对特殊字符进行转移操作,防止 SQL 注入。

SQL 预编译

作用:可以节省 SQL 查询时间,以及 MySQL 服务器的资源。达到一次编译、多次执行的目的。

1.6 将一张表的数据部分更新到另一张表,该如何操作呢?

主要采用关联更新的方式,将一张表的部分数据,更新到另一张表内。

update b set b.col=a.col from a,b where a.id=b.id;				# 查询
update b set col=a.col from b inner join a on a.id=b.id;		# 内连接
update b set b.col=a.col from b left Join a on b.id = a.id;		# 外连接

1.7 WHERE 和 HAVING 有什么区别?

WHERE: 是一个约束声明

  • 是在结果返回之前起作用的。
  • WHERE 中不能使用聚合函数。

HAVING: 是一个过滤声明

  • 是在查询返回结果集以及对查询结果进行的过滤操作。
  • 在 HAVING 中可以使用聚合函数。
  • HAVING 子句中不能使用除了分组字段和聚合函数之外的其他字段。

总结: 从性能角度看,当使用分组字段作为过滤条件时,应尽量使用 WHERE 子句,而不使用 HAVING 子句。

2、索引

2.1 说说你对 MySQL 索引的理解

概念: 索引是一个单独的、存储在磁盘上的数据库结构,包含着对数据表里所有记录的引用指针。所有 MySQL 列类型都可以被索引,对相关列使用索引是提高查询操作速度的最佳途径。
索引与存储引擎: 索引是在存储引擎中实现的,因此,每种存储引擎的索引都不一定完全相同,并且每种存储引擎也不一定支持所有索引类型。MySQL 总共有四种存储引擎,支持两种索引类型(BTREE 和 HASH)。

  • MyISAM / InnoDB: 只支持 BTREE 索引
  • MEMORY / HEAP: 可以支持 HASH 和 BTREE 索引

索引的优点:

  • 通过创建唯一索引,可以保证数据库表中每一行数据的唯一性。
  • 可以大大加快数据的查询速度,这也是创建索引的主要原因。
  • 在实现数据的参考完整性方面,可以加速表和表之间的连接。
  • 在使用分组和查询子句进行数据查询时,也可以显著减少查询中分组和排序的时间。

索引的缺点:

  • 创建索引和维护索引要耗费时间,数据量越大耗费时间越长。
  • 索引需要占用磁盘空间。
  • 当对表中的数据进行增删改的时候,索引也要动态地维护,这样就降低了数据的维护速度。

2.2 索引有那几类?

  1. 普通索引和唯一索引
    • 普通索引: 是 MySQL 中的基本索引类型,允许索引列插入重复值和空值。
    • 唯一索引: 要求索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。
      主键索引 是一种特殊的唯一索引,不允许有空值。
  2. 单列索引和组合索引
    • 单列索引: 一个索引只包含单个列,一个表可以有多个单列索引。
    • 组合索引: 是指在表的多个字段组合上创建的索引。使用组合索引时遵循最左前缀集合(主要因为底层是 B+ 树)。
  3. 全文索引
    • 全文索引类型为 FULLTEXT,在定义索引的列上支持值的全文查找,允许在这些索引列中插入重复值和空值。全文索引可以在 CHAR、VARCHAR 或者 TEXT 类型的列上创建。MySQL 只有 MyISAM 存储引擎支持全文索引。
  4. 空间索引
    • 空间索引是对空间数据类型的字段建立的索引,MySQL 中的空间数据类型有 4 种,分别是GEOMETRY、POINT、LINESTRING 和 POLYGON。MySQL 使用 SPATIAL 关键字进行扩展,使得能够用创建正规索引类似的语法创建空间索引。创建空间索引的列,必须将其声明为 NOT NULL,空间索引只能在存储引擎为 MyISAM 的表中创建。

2.3 如何创建及保存 MySQL 的索引

在创建表的时候创建索引:

CREATE TABLE table_name [colname data_type]
[UNIQUE|FULLTEXT|SPATIAL] [INDEX|KEY] [index_name] (col_name [length]) [ASC|DESC]

其中,UNIQUE、FULLTEXT 和 SPATIAL 为可选参数,分别表示唯一索引、全文索引和空间索引;INDEX 与 KEY 为同义词,两者作用相同,用来指定创建索引。
例如:

CREATE TABLE t1 (
	id INT NOT NULL,
	name CHAR(30) NOT NULL,
	UNIQUE INDEX UniqIdx(id)	
);

在已存在的表上创建索引: 总共两种方法 ALTER TABLE 或者 CREATE INDEX 语句

ALTER TABLE:

ALTER TABLE t_name ADD
[UNIQUE|FULLTEXT|SPATIAL] [INDEX|KEY] [index_name] (col_name [length]...) [ASC|DESC]

例如:

ALTER TABLE book ADD UNIQUE INDEX UniqidIdx (bookId);

CREATE INDEXS:

CREATE [UNIQUE|FULLTEXT|SPATIAL] INDEX index_name
ON t_name (col_name [length]...) [ASC|DESC]

例如:

CREATE UNIQUE INDEX UniqidIdx ON book (bookId);

2.4 MySQL 怎么判断要不要加索引?

  1. 当唯一性是某种数据本身的特征时,指定唯一索引。
  2. 在频繁进行排序或分组(即进行 group by 或 order by 操作) 的列上建立索引,如果待排序的列有多个,可以在这些列上建立组合组合索引。

2.5 只要创建了索引,就一定会走索引吗?

不一定,会发挥索引失效问题,例如在使用组合索引的时候,如果没有遵从"最左前缀"的原则进行搜索,索引就会失效。

扩展:索引失效问题(原因和底层是 B+ 树的排序原则有关)

  • 不遵循最左前缀原则,例如索引字段是(a,b),你只根据字段 b 去查询。
  • 例如索引字段(a,b),只有在字段 a 相等时查 b,才会调用索引,如果是 <> a 时,去查 b,那就会失效。
  • 使用 like 非前缀(1%)的情况,右缀(%1)和中缀(%1%)都会失效。

总结: 所有三种情况都是在第一个字段不相等的情况下,去查第二个字段,这样就会导致索引失效。

2.6 如何判断数据库的索引有没有生效?

主要使用 EXPLAIN 语句来查看索引是否正在使用。
EXPLAIN 语句将为我们输出详细的 SQL 执行信息,其中:

  • possible_keys 行给出了 MySQL 在搜索数据记录时可选用的各个索引。
  • key 行是 MySQL 实际选用的索引。

2.7 如何评估一个索引创建的是否合理?

  • 避免对经常更新的表进行过多的索引,并且索引中的列要尽可能小。应该为经常用于查询的字段创建索引,但要避免添加不必要的字段。
  • 数据量小的表最好不要使用索引,由于数据较少,查询花费的时间可能比遍历索引的时间还要短,索引可能不会产生优化效果。
  • 在条件表达式中经常用到的不同值较多的列上建立索引,在不同值很小的列上不要建立索引。
  • 当唯一性是某种数据本身的特征时,指定唯一索引。使用唯一索引需能确保定义的列的数据完整性,以提高查询速度。
  • 在频繁进行排序或分组(即进行 group by 或 order by 操作)的列上建立索引,如果待排序的列有多个,可以在这些列上建立组合索引。

2.8 索引是越多越好吗?

索引并非越多越好,一个表中如果有大量的索引,不仅占用磁盘空间,还会影响 INSERT、DELETE、UPDATE 等语句的性能,因为在表中的数据更改时,索引也会进行调整和更新。

2.9 如何避免数据库索引失效?

  • 使用组合索引时,需要遵循"最左前缀"原则。
  • 不在索引列上做任何操作,例如计算、函数、类型转换。
  • 尽量使用覆盖索引(覆盖索引:索引包含了要查询的列)。
  • MySQL 在使用不等于(!= 或者 <>)MySQL 索引会失效变成全表扫描的操作。
  • 字符串不加单引号会导致索引失效(可能发生了索引列的隐式转换)。
  • 少用 or,用它来连接时会索引失效。

2.10 所有的字段都适合创建索引吗?

不是

下列几种情况,是不适合创建索引的:

  1. 频繁更新的字段
  2. where 条件中用不到的字段
  3. 数据比较少的表
  4. 数据重复且分布比较均匀的字段
  5. 参与列计算的列

2.11 索引的实现原理

在 MySQL 中,索引是在存储引擎层实现的,不同存储引擎对索引的实现方式是不同的。
MyISAM 索引实现:

  • 底层数据结构: B+ Tree 作为索引结构,叶节点data 域存放的是数据记录的地址
  • 主索引与辅助索引: 主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求 key 是唯一的,而辅助索引的 key 可以重复。
  • 辅助索引存储: 同样也是采用 B+ Tree 作为索引结构,叶节点data 域存放的也是数据记录的地址

InnoDB 索引实现:

  • 底层数据结构: B+ Tree 作为索引结构,与 MyISAM 的具体实现方式截然不同
    • InnoDB 的数据文件本身就是索引文件,MyISAM 索引文件和数据文件是分离的,索引文件采用 B+ Tree 结构,仅保存数据记录的地址。
    • 主索引的叶节点 包含了完整的数据记录。这种索引叫做聚集索引。注意:InnoDB 要求表必须有主键,如果没有,会默认生成一个隐含字段作为主键。
    • 辅助索引存储: 辅助索引 data 域存储相应记录主键的值而不是地址。所以辅助索引搜索需要检索两遍索引。(1)检查辅助索引获得主键。(2)用主键到主索引中检索获得记录。
    • 细节:根据原理可以知道,InnoDB 不建议使用过长的字段作为主键,不建议使用非单调的字段作为主键。

2.12 介绍一下数据库索引的重构过程

什么时候需要重建索引?

  • 表上频繁发生 update、delete 操作
  • 表上发生了 alter table,move 操作(move 操作导致了 rowid 变化)

怎么判断索引是否应该重建?

  • 一般看索引是否倾斜的严重,是否浪费了空间,对索引进行结构分析:
    analyze index index_name validate structure;
    
  • 在相同的 session 中查询 Index_stats 表(视图来收集 B 树索引的统计信息,可以从中查到该用户拥有索引的名字,高度等等详细信息):
    select height,DEL_LF_ROWS / LF_ROWS from index_stats;
    
    当查询的 height >= 4(索引的深度,即从根到叶节点的高度)或 DEL_LF_ROWS / LF_ROWS > 0.2 的情况下,就应该考虑重建该索引。

如何重建索引?

  • drop 原索引,然后再创建索引:(不建议)
    drop index index_name;
    create index index_name on table_name (index_column);
    
  • 直接重建索引:
    alter index index_name rebuild;
    alter index index_name rebuild online;
    
    rebuild 是快速创建索引的一种有效的办法,因为它是一种使用现有索引项来重建新索引的方法。如果重建索引时有其他用户在对这个表操作,尽量使用带 online 参数来最大限度的减少索引重建时将会出现的任何加锁问题。

rebuild 重建索引的过程:

  • rebuild 读取原索引中的数据来构建一个新的索引,重建过程中有排序操作。
  • rebuild 会阻塞 DML 操作,rebuild online 不会阻塞 DML 操作。
  • rebuild online 会创建一个系统临时日志表 SYS_JOURNAL_xxx。所有 rebuild online 时索引的变化都记录在这个表中,当新的索引创建完成后,把这个表的记录维护到新的索引中去,然后 drop 掉旧的索引,rebuild online 就完成了。

重建索引过程中的注意事项:

  • 执行 rebuild 操作时,需要检查表空间是否足够。
  • 虽然说 rebuild online 操作允许 DML 操作,但还是建议在业务不繁忙时间段进行。
  • rebuild 操作会产生大量 Redo Log。

2.13 MySQL 的索引为什么用 B+ 树?

  1. AVL-Trees:
    • 随着节点数目增多,二叉树的高度会变得很高,查找速度大大减少。
    • 在进行范围查询时,会发生回旋查找,速度变得很慢
  2. B-Trees:
    • 解决了 AVL-Trees 的第一个问题
    • 但是还会发生回旋查找,在搜索一个范围的时候,会变得很慢
  3. B+ Trees:
    • 解决了上述问题,所以采用 B+Trees。并且在数据库中 B+ 树的高度一般都在 2-4 层。

扩展:B+ 树 3 层索引能存多少条数据?

几个要明确的(假设):

  • InnoDB 最小的存储单元,页大小 16 KB
  • 一行记录的大小:1 KB
  • 关键字值大小和指针大小:8 字节和 6 字节,即一个是 14 B

两层:第一层一页,能存储 16*1024/14 = 1170 个,即有1170个叶子节点,每一页能能存储 16K/1K = 16条记录,则总共能存储 1170 * 16 = 18720 条记录。
三层:原理同第二层,则能存储 1170 * 1170 * 16 = 21902400 条记录,达到千万级。

2.14 联合索引的存储结构是什么?它的有效方式是什么?

它的键值数量不是 1,而是大于等于 2,实际上可以看成是多个特征的拼接。要遵循最左前缀原则。

2.15 MySQL 的 Hash 索引和 B 树索引有什么区别?

Hash 索引: 进行查找时,调用一次 hash 函数就可以获取到相应的键值,之后进行回表查询获得实际数据。
B 树索引: 每一次的查询都是从根节点出发,查找到叶子节点方可以获得所查键值,然后根据查询判断是否需要回表查询数据。
不同点:

  • Hash 索引的底层是 hash 表,B 树索引的底层是 B+ 树。
  • Hash 索引进行等值查询(O(1)),B 树索引是(O(Log N))。
  • Hash 索引不支持范围查询,B 树索引支持。
  • Hash 索引不支持使用索引进行排序
  • Hash 索引不支持模糊查询和多列索引的最左前缀匹配原则
  • Hash 索引任何时候都避免不了回表查询数据
  • Hash 索引发生 hash 冲突时,效率可能极差

总结: 归根结底还是 hash 表和 B+ Tree 底层数据结构的原因。

2.16 聚簇索引和非聚簇索引有什么区别?

在 InnoDB 存储引擎中,可以将 B+ 树索引分为聚簇索引和辅助索引(非聚簇索引)。无论是何种索引,每个页的大小都为 16 KB,且不能更改。
聚簇索引: 根据主键创建的一棵 B+ 树,聚簇索引的叶子节点存放了完整的数据记录
辅助索引: 根据索引键创建的一棵 B+ 树,其叶子结点金存放索引键以及该索引键指向的主键。所以查找时需要进行两次索引。(1) 检查辅助索引获得主键。(2) 用主键到主索引中检索获得记录。

2.17 select in 语句中如何使用索引?

索引是否起作用,主要取决于字段类型:

  • 如果字段类型为字符串,需要给 in 查询中的数值与1字符串值都需要添加引号,索引才能起作用。
  • 如果字段类型为 int,则 in 查询中的值不需要添加引号,索引也会起作用。

2.18 模糊查询语句中如何使用索引?

使用 like 非左缀(1%)的情况,右缀(%1)和中缀(%1%)都会失效

3、事务

事务的操作

  • begin transaction:开始事务
  • commit transaction:提交事务
  • rollback transaction:回滚事务
  • save transaction:事务保存点。即事务回滚时,可以指定回滚到保存点,而不进行全部回滚。

3.1 说一说你对数据库事务的了解?

概念: 事务由一条或者一组复杂的 SQL 语句组成。在事务中的操作,要么都执行修改,要么都不执行,这就是事务的目的,也是事务模型区别于文件系统的重要特征之一。
事务的四大特性(ACID)

  • 原子性(Atomicity):整个事务中的所有操作,必须作为一个单元全部完成(或全部取消)。
  • 一致性(Consistency):事务在开始之前与结束之后,数据库都保持一致性状态,数据库的完整性约束没有被破坏。
  • 隔离性(Isolation):事物之间是相互独立的,一个事务不会影响其他事务,存在四个隔离级别。
  • 持久性(Durability):在事务提交之后,事务对数据库所作的更改将持久地保存在数据库中,并且不会被回滚。

事务可以分为以下几种类型:
主要有五种类型,扁平事务、带有保存点的扁平事务、链事务、嵌套事务和分布式事务。

  • 扁平事务: 是事务类型中最简单的一种,也是使用最为频繁的事务。所有操作都处于同一层次,其由 BEGIN WORK 开始,由 COMMIT WORK 或 ROOLLBACK WORK 结束。处于之间的操作是原子的,要么都执行,要么都回滚。
  • 带有保存点的扁平事务: 除了支持扁平事务支持的操作外,允许在事务执行过程中回滚到同一事务中较早的一个状态。保存点(savepoint)用来通知系统应该记住事务当前的状态,以便以后发生错误时,事务能回到该状态。
  • 链事务: 可视为保存点模式的一个变种,链事务的思想是:在提交一个事务时,释放不需要的数据对象,将必要的处理上下文隐式地传给下一个要开始的事务。注意,提交事务操作和开始下一个事务操作将合并为一个原子操作。
  • 嵌套事务: 是一个层次结构框架。有一个顶层事务(top-level transaction)控制着各个层次的事务。顶层事务之下嵌套的事务被称为子事务(subtransaction),其控制每一个局部的变换。
  • 分布式事务: 通常是在一个分布式环境下运行的扁平事务,因此需要根据数据所在位置访问网络中的不同节点。对于分布式事务,同样需要满足 ACID 特性,要么都发生,要么都失效。

对于 MySQL 的 InnoDB 存储引擎来说,它支持扁平事务、带有保存点的扁平事务、链事务和分布式事务。对于嵌套事务,MySQL 数据库并不是原生的,因此对于有平行事务需求的用户来说 MySQL 就无能为力了,但是用户可以通过带有保存点的事务来模拟串行的嵌套事务。

3.2 MySQL 的 ACID 特性分别是怎么实现的?

原子性:
主要依靠undo.log日志实现,它会存储事务期间对数据库进行的DML操作,这样在事务失败进行回滚的时候,数据库就可以根据undo.log中的记录,进行方向回滚数据库状态。
持久性:
主要依靠redo.log日志实现。首先,mysql 持久化是通过缓存来提高访问速度,即在select时先查看缓存,再查磁盘;在update时先更新缓存,再更新磁盘。以此来减少磁盘 io 次数,提高访问速度,但这样有问题,就是当断电的时候缓存中的数据就没有了。 通过redo.log日志就可以解决这个问题,在执行update时,先写入到redo.log日志,再写入缓存,这样即使断电,也能保证数据不丢失,达到持久性。

为什么 redo.log(存在磁盘中)会比从缓冲区读取速度快?

  1. 因为redo.log是追加模式的,在文件的尾部去追加,采用的顺序 io 存储这种机制去操作,而缓冲区是随机的。(就像顺序表和链表在尾部追加元素的区别)
  2. 修改数据时,一个小小的修改,往缓冲区写入时,要把整个页的数据写入。而redo.log只需要把真正需要的部分写入。
    redo.log的一种同步时机:在事务提交时将缓冲区的redo日志同步写入到磁盘,保证一定会写入成功。

隔离性:

  • 目的:是为了防止多事务之间相互影响。隔离性追求的是并发情形下事务之间互不干扰。
  • 解决办法:
    • 写写操作:通过加锁来实现隔离性
    • 读写操作:通过MVCC来实现隔离性。

一致性:
事务执行前后,数据库的完整性约束没有被破坏。可以说,一致性是事务追求的最终目标。前面提到的原子性、隔离性、持久性都是为了保证数据库状态的一致性。具体措施包括:

  • 保证原子性、隔离性和持久性,如果这些特性无法保证,事务的一致性也无法保证。
  • 数据库本身提供保障,例如不允许向整形列插入字符串值、字符串长度不能超过列的限制等。
  • 应用层面进行保障,例如如果转账操作只扣除转账者的余额,而没有增加接受者的余额,无论数据库实现的多么完美,也无法保证状态的一致性。

3.3. 读操作的三类问题

并发情况下,读操作可能出现三类问题:

  • 脏读: 当前事务(A)中读到其他事务(B)未提交的数据(脏数据),这种现象是脏读。(读了别人未提交的数据,结果人家后来又回滚了,呸,真脏)
  • 不可重复读: 在事务 A 先后两次读取同一个数据,两次读取的结果不一样,这种现象称为不可重复读。
    • 脏读与不可重复读的区别: 前者读到的是其他事务未提交的数据,后者读到的是其他事务已提交的数据。
  • 幻读: 在事务 A 中按照某个条件先后两次查询数据库,两次查询结果的条数不同,这种现象称为幻读。
    • 不可重复读和幻读的区别: 前者是数据变了,后者是数据的行数变了。

3.4 谈谈 MySQL 的事务隔离级别

SQL 标准定义了四种隔离级别,分别是:

  • 读未提交(READ UNCOMMITTED):对方事务还没有提交,我们当前事务可以读取到对方为提交的数据。
  • 读已提交(READ COMMITTED):对方事务提交之后的数据我方可以读取到。
  • 可重复读(REPEATABLE READ):对读锁进行修改,之前的读锁是:读取了数据之后就立刻释放读锁,现在修改是:在读取数据的时候加上 S 锁,但是要直到事务准备提交了才释放该 S 锁,X 锁还是一致。
  • 串行化(SERIALIZABLE):读的时候加共享锁,其他事务可以并发读,但是不能写。写的时候加排它锁,其他事务不能并发写也不能并发读。

事务隔离级别就是为了解决脏读、不可重复读和幻读,具体如下:

隔离级别 脏读 不可重复读 幻读
READ UNCOMMITTED 可能 可能 可能
READ COMMITTED(RC) 不可能 可能 可能
REPEATABLE READ(RE) 不可能 不可能 可能
SERIALIZABLE 不可能 不可能 不可能

扩展:InnoDB 默认的隔离级别是什么?

InnoDB 支持的默认隔离级别是可重复读。使用的 Next-Key Lock 的锁算法,因此也避免了幻读的产生。

3.5 MySQL 的事务隔离级别是怎么实现的?

  • 读未提交(READ UNCOMMITTED): 它什么也不做,几乎没有隔离效果。
  • 读已提交(READ COMMITTED): 通过 MVCC 可以实现,其中 ReadView 是每一次 select 就会生成一个,从而能解决读已提交,但是不能解决可重复读。
  • 可重复读(REPEATABLE READ): 通过 MVCC 可以实现,其中 ReadView 是以事务为单位的,假设一个事务有 3 个 select 语句,他们生成同一个 ReadView。所以每次 select 的时候都是一样的,从而可以解决可重复读。
  • 串行化(SERIALIZABLE): 通过加间歇锁来实现。例如第一次 select age>2 的数据,那就把 age>2 的数据锁起来,这样就不会产生幻读了。

3.6 MVCC 的详解

目的: 主要是为了提高数据库的并发性能。
最大的优点: 它最大的优点是读不加锁,因此读写不冲突,并发性能好。
总共有三部分组成。隐藏列、基于 undo log 的版本链和 ReadView。

  • 隐藏列: 隐藏列中包含了本行数据的事务id、指向undo log 的指针等。
  • 基于 undo log 的版本链: 每行数据的隐藏列中包含了指向 undo log 的指针,而每条 undo log 也会指向更早版本的 undo log,从而形成一条版本链。
  • ReadView: 通过隐藏列和版本链,MySQL 可以将数据恢复到指定版本。但是具体要恢复到那个版本,则需要根据 ReadView 来确定。所谓 ReadView,是指事务(记作事务A)在某一时刻给整个事务系统(trx_sys)打快照,之后再进行读操作时,会将读取到的数据中的事务id与trx_sys快照比较,从而判断数据对该ReadView是否可见,即对事务A是否可见。

总结:
隐藏列和版本链的目的就是能够找到每个记录不同版本的数据,能不能读主要看 ReadView 和当前数据的事务版本比较,比较的核心在于我当前事务执行的时候,这个版本的事务是不是已经提交了,如果提交就读取,否则去更早的版本。

3.7 事务可以嵌套吗?

可以,因为嵌套事务也是众多事务分类中的一种,它是一个层次结构框架。有一个顶层事务控制着各个层次的事务,顶层事务之下嵌套的事务被称为子事务,它控制每一个局部的变换。

3.8 MySQL 事务如何回滚?

在 MySQL 默认的配置下,事务都是自动提交和回滚的。当显式地开启一个事务时,可以使用 ROLLBACK 语句进行回滚。该语句有两种用法:

  • ROLLBACK:这个回滚会结束用户的事务,并撤销正在进行的所有未提交的修改。
  • ROLLBACK TO [SAVEPOINT] identifier:这个语句与 SAVEPOINT 命令一起使用。可以把事务回滚到标记点,而不回滚在此标记点之前的任何工作。

4、锁

4.1 行锁、表锁和间歇锁

行锁:
假设现在有事务a和事务b,假设a修改了表中的一行数据,如果a还没有commit,这个时候如果b也去修改这条数据,那么就会发生阻塞,直到a提交之后,b才能修改,或者b的这条DML超时。
表锁:
假设现在有事务a和事务b,假设a修改了表中的一行数据,如果它这条DML导致了表索引失效,那么这个时候就会转换为表锁,事务b不能够对这个表进行任何修改。
间歇锁(解决幻读):
假设下现在有事务a和事务b,表t有字段tno编号是不连续的,例如1、3、5、7;如果a操作了ttnobetween 1 and 5,这个时候b如果插入tno = 4 的时候就会发生间歇锁,插入不进去。

4.2 了解数据库的锁吗?

概念: 锁是数据库系统区别于文件系统的一个关键特性,锁机制用于管理对共享资源的并发访问。
锁的类型:

  • 共享锁(S lock): 允许事务读一行数据。对应读操作:快照读
  • 排他锁(X lock): 允许事务删除或更新一行数据。对应读操作:悲观读
    S 和 X 锁都是行锁,兼容是指对同一记录(row)锁的兼容性情况。
    锁的粒度:
  • 概念: InnoDB 存储引擎支持多粒度锁定,这种锁定允许事务在行级上的锁和表级上的锁同时存在。特此引入了意向锁的概念。意向锁是将锁定的对象分为多个层次。
  • InnoDB 支持两种意向锁:
    • 意向共享锁(IS Lock): 事务想要获得一张表中某几行的共享锁。
    • 意向排他锁(IX Lock): 事务想要获得一张表中某几行的排他锁。

锁的算法:
InnoDB 存储引擎有三种行锁的算法,其分别是:

  • Record Lock:单个行记录上的锁。
  • Gap Lock:间歇锁,锁定一个范围,但不包含记录本身。
  • Next-Key Lock:Gap Lock + Record Lock,锁定一个范围,并且锁定记录本身。

关于死锁:

  • 概念: 死锁是指多个事务在执行过程中,因争夺锁资源而造成的一种互相等待的现象。
  • 解决办法:
    • 超时: 当两个事务互相等待时,当一个等待时间超过设置的某一个阈值时,其中一个事务进行回滚。
    • wait-for graph(等待图): 在每个事务请求锁并发生等待时都会判断是否存在回路,若存在则有死锁,通常来说 InnoDB 存储引擎选择回滚 undo 量最小的事务。

锁的升级: 锁升级(Lock Escalation)是指将当前锁的粒度降低。举例来说,数据库可以把一个表的 1000 个行锁升级为一个页锁,或者将页锁升级为表锁。

4.3 介绍一下间歇锁

间歇锁用于锁定一个范围,但不包含记录本身。它的作用是为了阻止多个事务将记录插入到同一范围内,而这会导致幻读问题的产生。

4.4 InnoDB 中行级锁是怎么实现的?

InnoDB 行级锁是通过给索引上的索引项加锁来实现的。只有通过索引条件检索数据,InnoDB 才使用行级锁,否则,InnoDB 将使用表锁。

5、优化

5.1 索引优化方案

  • 全部用到索引
  • 最左前缀法则
  • 索引不要放在范围查询右边
  • 减少 select * 的使用
  • like 模糊查询,不要让索引失效
  • order by 优化

5.2 什么是数据库优化

MySQL 数据库优化是多方面的,原则是减少系统的瓶颈,减少资源的占用,增加系统的反应速度。

  • 针对查询: 可以通过使用索引、使用连接代替子查询的方式来提高查询速度。
  • 针对慢查询: 可以通过分析慢查询日志,来发现引起慢查询的原因,从而有针对性的进行优化。
  • 针对插入: 可以通过禁用索引、禁用检查等方式来提高插入速度,在插入之后再启用索引和检查。
  • 针对数据库结构: 可以通过将字段很多的表拆分成多张表、增加中间表、增加冗余字段等方式进行优化。

5.3 如何优化 MySQL 的查询

  • 使用索引(注意不要让索引失效)
  • 优化子查询:在 MySQL 中,可以使用连接(JOIN)查询来替代子查询。连接查询不需要建立临时表,其速度比子查询要快,如果查询中使用索引,性能会更好。

5.4 怎样插入数据才能更高效?

影响插入速度的主要是索引、唯一性校验、一次插入记录条数等。
对于 MyISAM 引擎,常见的优化方法如下:

  1. 禁用索引
  2. 禁用唯一性检查
  3. 使用批量插入
  4. 使用 LOAD DATA INFILE 批量导入

对于 InnoDB 引擎的表,常见的优化方法如下:

  1. 禁用唯一性检查
  2. 禁用外键检查
  3. 禁用事务的自动提交

5.5 表中包含几千万条数据该怎么办?

  1. 优化 SQL 和索引
  2. 增加缓存
  3. 读写分离
  4. 使用 MySQL 自带的分区表,这对应用是透明的,无需改代码,但 SQL 语句是要针对分区表做优化的。
  5. 做垂直划分,即根据模块的耦合度,将一个大的系统分为多个小的系统。
  6. 做水平划分。

5.6 MySQL 的慢查询优化有了解吗?

  1. 开启慢查询日志
  2. 分析慢查询日志
  3. 常见慢查询优化:
    1. 索引没起作用的情况
    2. 优化数据库结构
    3. 分解关联索引
    4. 优化 LIMIT 分页

5.7 explain 有什么用?

目的: EXPALIN 是用来分析查询语句的。

重点关注如下:

列名 备注
type 表示表的连接类型
key 是 MySQL 实际选用的索引
key_len 本次查询用于过滤的索引实际长度
rows 这次查询从数据表里读出的数据行的个数
Extra 提供了与关联操作有关的信息

type包含的结果,从上至下依次是由差到好:

类型 备注
ALL 执行full table scan,这是最差的一种方式。
index 执行full index scan,并且可以通过索引完成结果扫描并且直接从索引中取的想要的结果数据,也就是可以避免回表,比ALL略好,因为索引文件通常比全部数据要来的小。
range 利用索引进行范围查询,比index略好。
ref 基于索引的等值查询,或者表间等值连接。
const 基于主键或唯一索引唯一值查询,最多返回一条结果。
system 查询对象表只有一行数据,这是最好的情况。

6、其他

6.1 介绍一下数据库设计的三大范式

为什么需要数据规范化?

  • 信息重复
  • 更新异常
  • 插入异常
    • 无法正常显示信息
  • 删除异常
    • 丢失有效的信息

第一范式:
原子性:保证每一列不可再分
要求数据库表中的每一列都是不可分割的原子数据项。
第二范式:
前提:满足第一范式
每张表只满足一件事情。
第二范式需要确保数据库表中的每一列都和主键相关,而不能只与主键的某一部分相关(主要针对联合主键而言)。
第三范式:
前提:满足第一范式和第二范式
第三范式需要确保数据表中的每一列数据都和主键直接相关,而不能间接相关。

6.2 说说你对 MySQL 引擎的了解

MySQL 提供了多个不同的存储引擎,包括处理事务安全表的引擎和处理非事务安全表的引擎。
MySQL 支持的引擎有:InnoDB、MyISAM、Memory、Merge、Archive等
InnoDB 存储引擎: MySQL 5.5.5 之后,作为默认存储引擎。

  • 是事务性数据库的首选引擎,支持事务安全表(ACID)、支持行锁定和外键。
  • InnoDB 是为处理巨大数据量的最大性能设计
  • InnoDB 存储引擎完全与 MySQL 服务器整合,为在主内存中缓存数据和索引而维持它自己的缓冲池。
  • InnoDB 支持外键完整性约束(FORIGN KEY)。
  • InnoDB 被用在众多需要高性能的大型数据库站点上。

MyISAM 存储引擎:

  • 它是在 Web、数据存储和其他应用环境下最常使用的存储引擎之一。
  • MyISAM 拥有较高的插入、查询速度,但不支持事务。

6.3 说一说你对 redo log、undo log、binlog的了解

binlog(Binary Log):
二进制日志文件就是常说的 binlog。二进制日志记录了 MySQL 所有修改数据库的操作,然后以二进制的形式记录在日志文件中,其中还包括每条语句所执行的时间和所消耗的资源,以及相关的事务信息。
redo log:
重做日志用来实现事务的持久性,即事务 ACID 中的 D。它由两部分组成:一是内存中的重做日志缓存(redo log buffer),其是易失的;二是重做日志文件(redo log file),它是持久的。
undo log:
重做日志记录了事务的行为,可以很好地通过其对页进行“重做”操作。但是事务有时还需要进行回滚操作,这时就需要 undo。
redo log 和 undo log:

  • redo log 用来保证事务的持久性,undo log 用来帮助事务回滚及 MVCC 的功能。
  • redo log 基本上都是顺序写的,在数据库运行时不需要对 redo log 的文件进行读取操作。而 undo log 是需要进行随机读写的。
  • redo 存放在重做日志文件中,与 redo 不同,undo 存放在数据库内部的一个特殊段(segment)中,这个段称为 undo 段(undo segment),undo 段位于共享表空间内。

6.4 MySQL 主从同步是如何实现的?

复制(replication)是 MySQL 数据库提供的一种高可用高性能的解决方案,一般用于建立大型的应用。总体来说,replication 的工作原理分为以下 3 个步骤:

  1. 主服务器(master)把数据更改记录到二进制日志(binlog)中。
  2. 从服务器(slave)把主服务器的二进制日志复制到自己的中继日志(relay log)中。
  3. 从服务器重做中继日志中的日志,把更改应用到自己的数据库上,以达到数据的最终一致性。

要注意的是,复制不是完全实时地进行同步,而是异步实时
复制的工作原理是,从服务器有两个线程,一个是 I/O 线程,负责读取主服务器的二进制日志,并将其保存为中继日志;另一个是 SQL 线程,复制执行中继日志。

6.5 delete、truncate和drop的区别

功能: 三者都可以用来删除数据。
执行速度: drop > truncate > delete
不同点:

  • delete:
    • 属于 DML 语言
    • 会走事务,会触发 trigger
    • 删除只是标记为已删除,并没有从磁盘删除
      • 带 where 语句的删除,不管是 InnoDB 还是 MyISAM 都不会释放空间
      • 删除表的全部数据,MyISAM 会立刻释放空间,而 InnoDB 不会。
      • 不管 InnoDB 还是 MyISAM 在 delete 之后执行 optimize table 都会释放磁盘空间。
  • truncate:
    • 属于 DDL 语言
    • 不走事务,不会触发 trigger
    • 执行后立即生效,无法找回
    • 删除后会立刻释放磁盘空间
    • 行为类似于 drop table 然后 create
  • drop:
    • 属于 DDL 语言
    • 不走事务,不会触发 trigger
    • 执行后立即生效,无法找回
    • 删除后会立刻释放磁盘空间。并且和该表相关的所有结构。

总结: 一个很形象的比喻,对于一本书,delete 是把目录撕了,truncate 是把书的内容撕下来烧了,drop 是把书烧了。

6.6 创建一个数据库连接的开销

  • TCP 连接建立的网络开销
  • 连接的身份验证
  • session 的开销
  • 可能还有 IO 的开销

你可能感兴趣的:(秋招总结,c++,开发语言,后端,数据库开发)