分享

后端通用教程(一)

 夜猫速读 2022-05-05 发布于湖北

1. 课程简介

本课程的核心内容可以分为三个部分,分别是需要理解记忆的计算机底层基础,后端通用组件以及需要不断编码练习的数据结构和算法。

计算机底层基础可以包含计算机网络、操作系统、编译原理、计算机组成原理,后两者在面试中出现的频率很少,课程主要关注网络和系统两个模块,计算机网络模块主要介绍了常见的 TCP 协议、HTTP/HTTPS 协议,操作系统模块主要介绍了操作系统的进程和线程、内存管理的页面置换算法等高频题。

后端通用组件主要分为存储持久化数据的数据库,存储临时数据的缓存以及通信中间件。常用的支持持久化存储的数据库有 Oracle Database、SQLite 以及 MySQL,其中 MysQL 已经是后端必备的数据库技能。常用的缓存方案有 memcached、guava cache、Redis,其中 Redis 是目前大型应用系统首选的缓存组件,课程介绍了 Redis 的数据结构和应用问题。通信中间件则介绍了 RabbitMQ 的常见应用问题。

数据结构和算法部分最能提现候选人的编程基本功以及逻辑思考能力,课程主要介绍了最常见的算法案例,例如快排、链表、二叉树、动态规划。

课程总体上是面向工作 3 年内的初中级程序员以及准备面试后端岗位的应届生,因为篇幅原因并不能涵盖所有的面试题,候选人需要做到举一反三,例如能够通过对于二叉树的各种遍历操作总结得到解决二叉树问题的递归算法模板,这也是本课程的最终目的。

2. 为什么要做这门面试教程?提升面试能力与了解市场现状

就业市场和所有的市场相同,影响就业指标的关键就是供需关系。在互联网行业,供需主要分为两类,需求方是作为招聘方的互联网公司,供给方则是作为候选人的程序员。如果互联网公司对于程序员的需求大于市场上流动的人员数量,此时求职的程序员就会有更大的议价能力,这种情况一般发生在每年的金三银四时间段。如果程序员数量过剩,互联网公司则会普遍提高选拔指标,尝试筛选出更符合条件的候选人。

从供给侧来看,根据 2021 年高考填报志愿的统计数据,计算机和金融已经明显成为两大金砖专业,越来越多的高水平学生流入到计算机专业,以及观察考研数据,可以发现计算机已经是最受欢迎的目标专业。

从需求侧来看,国内互联网经历了 2010 年之后的移动互联网流量大爆炸时期,增速已经逐渐放缓,各种大厂的竞争也从寻找增量的用户转为到存量市场博弈,例如在电商领域继淘宝和京东之后出现的拼多多、唯品会等各种细分应用,或者从企业微信、阿里钉钉以及字节跳动飞书的办公软件领域的竞争都可以看出存量市场竞争的激烈。蛋糕已经很难做大,所以大家都开始花精力研究如何分到更多的蛋糕。

所以未来的趋势很明显,互联网的供给增速飞快,但是需求增速放缓,作为找工作的一方,不管是应届在校生还是工作时间不长的程序员,都需要提高自己的核心专业素养。

3. 校园招聘简介

在开始介绍校招题库之前,我们首先需要了解关于校招的基础常识。之前遇到过一些同学,在毕业前两个月甚至领到毕业证之后,才开始了解关于校园招聘的事情,这时候才发现自己已经错过了大部分的企业招聘机会,只能捡漏或者直接参加难度较高的社招。这种情况非常可惜,但是年年都会发生,可见确实有一些同学对校园招聘了解甚少。所以本小节,我们首先给大家介绍下互联网技术校园招聘的情况。

一般来说,互联网校招分为秋招和春招,为了能够更清楚的说明时间节点,我们以下图为例讲个故事。


校招时间线

2019年9月,小明本科入学,开始了为期四年的大学生涯,2020年9月,小红硕士入学,迎接了自己3年时长的学硕生活。时间飞逝,到了2022年7月,各大互联网陆续开展了秋季校园招聘(简称秋招)提前批,提前批是各大厂的抢人大战,目标是提前锚定学校中最优秀的那批学生。到了当年9月,所有的互联网公司都开放了秋招正式批招聘流程,目标人群就是小明和小红这两类2023年毕业的同学,到了11月,大小厂都陆陆续续的发放了所有的offer。

小明由于准备不充分,整个秋招"颗粒无收",没有找到合适的工作,但是在毕业前他还有一个机会,就是春季校园招聘(简称春招),因为在秋招中,一些比较优秀的同学会同时拿到多个互联网公司的offer,但是在秋招后期会毁约大部分公司,这些公司就会空余出一些HC(HeadCount,互联网公司每年招聘时预计的招聘人数),所以在2023年的春季,一般是3月到4月这段时间,会有少部分公司重新放开招聘,这时候就是小明的第二次机会。

好了,故事讲完了,给大家总结一下,秋招一般是在每年的的7月开始,11月进入尾声,参与秋招的公司数量很多,岗位以及HC也比较富余,找到工作的机会很多。春招一般是针对前年秋招的补充招聘,招聘公司以及HC都较少,应届生找到好工作的难度较大。综上,作为应届生,应该尽可能抓住秋招的机会。

4. 为什么需要掌握题库

秋招开始时,本科生还处于大三阶段,一般还需要完成一些专业课,研究生则是在研二阶段,可能还在帮导师做项目。两者的时间都谈不上充裕,所以如何利用有限的时间收获最好的 Offer 就需要技巧。

同时,校招时 BAT(百度、阿里、腾讯)等大厂都会对参与校招的候选人进行评级,一般分为三档,也就是口口相传的白菜 Offer(评级普通),sp(Special Offer,评级优秀)、ssp(Super Special Offer,评级优秀),每档之间在年薪上可能会有3~10w的差距,而决定评级的,除了候选人的学历等硬件条件,最重要的就是在面试过程中的表现,也就是对于面试题目的剖析能力。

抛开简历书写、简历投递、Offer 谈薪这些模块,在本节内容中,我们的重点在于讲解后端校招中最经典、最高频的面试题,深入分析题目中最需要关注的知识点。在熟练掌握这些题解之后,对于面试中遇到的同类题目,我们能够给面试官良好的技术印象,以此助力同学们在校招中拿到心怡的 Offer。

1. 前言

在校招或者社招面试中,无论你是 Java 后端、Cpp 后端、Python 后端,面试官都会详细地考察各种语法细节、框架知识,但是大多数候选人入职之后,都会体会到 "面试造火箭,上班拧螺丝"。面试时我们熟悉各种知识细节,入职后却发现大部分工作都是重复的 CRUD(Create - 增加,Retrieve - 查询,Update - 更新,Delete - 删除),这种现象其实很正常。后端开发的核心职责就是倒腾数据,面临的基础问题例如如何设计数据的存储结构并且选择合适的数据库进行存储,如何在各种限制条件下查询数据。当我们负责的系统非常庞大之后,还会面临如何解决分布式系统的数据一致性这类更复杂的问题。

总结来说,操作数据库是后端程序员的必备技能。数据库按照存储数据结构的方式,可以分为关系型数据库以及非关系型数据库。关系型数据库中数据都是以二维表的形式存储,最常用的数据库有 Oracle、MySQL,非关系型数据库中数据都是以结构化的形式存储,最常用的数据库有 Redis、MongoDB、Hbase 等。目前互联网一二线大厂的校招要求,MySQL 基本是必备技能,非关系性数据库例如 Redis 这类不是必考内容,当然掌握了会是锦上添花。

本小节中会介绍关于 MySQL 常见高频面试题,以及核心回答思路解析。

2. MyISAM 和 InnoDB

面试官提问: 介绍下 InnoDB 和 MyISAM 存储引擎的区别,以及具体的应用场景?

题目解析:

首先要明确存储引擎的定义:MySQL 提供不同的技术存储数据,这些技术使用不同的数据存储机制、索引建立方式、锁方式来完成数据的构建,这些技术统称为存储引擎。

MySQL Client 终端输入 show engines 可查看支持的存储引擎类型。


MySQL 5.7 实验结果

如图可见 MySQL 至少支持 9 种存储引擎,而面试中最受关注的是 InnoDB 和 MyISAM 存储引擎,一般的面试场景是这样的:

在上图的实验结果中,我们会发现在 MySQL 的所有存储引擎中,只有 InnoDB 支持 Transaction(事务)、XA(分布式事务),其他引擎均不支持,关于事务的支持能力是需要强调的点。关于两种引擎之间的区别,可以按下表进行分点阐述。

 InnoDBMyISAM
事务支持,强调的是保持数据一致性的高级功能不支持,强调的是性能,查询速度比 InnoDB 快
外键支持不支持
索引使用聚集索引,索引文件和数据文件绑定使用非聚集索引,索引文件和数据文件分开存储,索引中保存的是数据文件的指针
支持表级锁、行级锁;行级锁粒度小,处理并发的能力更强支持表级锁,用户在执行 insert、update、select、delete 时都会给表自动加锁,效率低
主键表必须有唯一索引(例如用户规定 id 作为主键),没有的话会用默认隐藏列 row_id 作为唯一索引没有要求
存储文件在操作系统中的存储文件:.frm:表定义文件 .ibd:数据文件在操作系统中的存储文件:.frm:表定义文件 .myd:数据文件 .myi:索引文件

在阐述完两种存储引擎的区别之后,再根据两者的特点,枚举一些使用场景:

  1. MyISAM 对于不支持事务并且存在大量 SELECT 的读场景比较合适;

  2. 如果业务代码中要支持事务,必须选择 InnoDB 存储引擎;

  3. 如果业务代码中要支持外键,必须选择 InnoDB 存储引擎;

  4. 例如对电商公司来说,大部分的业务情况是要支持事务回滚的,例如下单流程失败之后要回滚已有的 Insert 语句,并且数据并发量高,这种场景肯定都是选择 InnoDB,笔者在生产环境中从未看到使用 MyISAM 存储引擎,两者对比更侧重考察候选人的理论知识。

3. 小结

本章节介绍了 MySQL 中两种不同的数据存储引擎,面试官针对回答可能会涉及到一些扩展性的问题,例如当谈到 InooDB 对事务的支持时,面试官很可能转为考察候选人对事务的理解程度。上述提到了聚集索引和非聚集索引,面试官可能会特意考察这两种索引的实现区别。MySQL 的外键定义,枚举使用场景和案例。这些问题也是候选人需要掌握的知识点。

1. 前言

在 MySQL 中使用 select 查询语句的时候,一般都会加上 where 语句或者 limit 语句限定查询结果的范围,两种子句都是过滤的作用。另外还有和 group by 语句配合使用的 having 限制条件。区分 where 和 having 语句的作用也是比较基础的题目。

2. where 和 having

面试官提问: 请阐述下在 MySQL 中 where 和 having 关键词有什么区别?

题目解析: 为了更加清楚地阐述两个关键用法的不同,我们还是从实际例子出发。

进入上一节创建的测试数据库:

USE mooc_demo;

然后进入测试数据库,创建 user 表:

USE `mooc_demo`;
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '数据库主键',
  `username` varchar(32) DEFAULT NULL COMMENT '用户名',
  `password` varchar(32) DEFAULT NULL COMMENT '密码',
  `gender` int(2) DEFAULT NULL COMMENT '1:男性,2:女性',
  `age` int(10) DEFAULT NULL COMMENT '年龄',
  PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

然后再插入一些测试数据:

insert into user (username,password,gender,age) values ('小明','123456',1,20);
insert into user (username,password,gender,age) values ('小红','123457',2,22);
insert into user (username,password,gender,age) values ('小王','123458',1,24);
insert into user (username,password,gender,age) values ('小刘','123459',2,26);

2.1 where 和 having 都能使用的场景

select username,password,gender,age from user where age >= 20;
select username,password,gender,age from user having age >= 20;

上述两条 sql 都能执行成功,如果 select 之后的字段包含 having 修饰的字段,这种情况下 where 和 having 是等效的。

2.2 只能用 where,不能用 having 的场景

select username,password,gender from user where age >= 20;
select username,password,gender from user having age >= 20;

第一条 sql 执行成功,执行第二条 sql 会报错:ERROR 1054 (42S22): Unknown column 'age' in 'having clause',翻译过来就是 "对于 having 语句,age 字段是未知的"。

having 执行的前置条件是:select xxx,... 筛选出的字段包含 having 修饰的关键词。

两者的执行顺序不同:因为 having 是从前面筛选出来的字段中再进行二次筛选,where 则是针对全表先进行筛选。

2.3 只能用 having,不能用 where 的场景

select count(*),gender,avg(age) as avg_age from user group by gender where avg_age > 20;
select count(*),gender,avg(age) as avg_age from user group by gender having avg_age > 20;

我们的目的是 "按照性别进行分组,统计平均年龄大于 20 的人数、性别和平均年龄"。

但是第一条 sql 执行会报错:ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'where avg_age > 20' at line 1,即对于 where 语句,全表并没有 avg_age 平均年龄这个字段,所以会搜索失败。

从上述的实验结果总结来看,where 和 having 的核心区别有三点:

① 使用范围的不同:

  • where:能够用于 select、update、delete 语句;

  • having:只能用于 select 查询语句。

② 执行顺序不同:

  • where 修饰的搜索条件是在分组完成之前执行;

  • having 修饰的搜索条件是在分组完成之后执行。如果一条 sql 语句同时包含 where 和 having 关键词,会先执行 where 搜索,再执行 having 搜索条件。

③ 聚合函数的联合使用:

  • where:不能联合聚合函数(sum、avg、count、max、min 这类)使用;

  • having:能够联合聚合函数使用。

3. 小结

本章节介绍了 where 和 having 语句的用法和不同点,候选人在阐述的时候需要抓住最核心的一点,where 语句在聚合之前筛选数据,having 语句在聚合之后对数据进行筛选,作用在 group by 语句之后。

1. 前言

MySQL 中支持的数据类型从整体上可以分为数值类型和日期时间类型,其中数值类型可以分为整数类型、浮点数类型、定点数类型和位类型。整数类型包含常见的 SMALLINT、MEDIUMINT、INT、BIGINT,浮点数类型主要是 FLOAT 单精度浮点数类型和 DOUBLE 双精度浮点数类型。日期类型也有 DATE、TIME、YEAR、DATETIME、TIMESTAMP 类型。关于整数类型和浮点数类型存在一些比较常见的误区,经常被面试官考察。

2. int (3) 和 int (11)

面试官提问: MySQL 中 int (3) 和 int (11) 这两种用法有什么区别呢?

题目解析:

这道题非常常见,但是没有仔细了解过 MySQL 中 int 数据类型用法的同学,很容易掉进误区。

我们知道 varchar(m) 用于修饰变长字符,其中 m 表示能够存储的字符上限。

例如 username varchar(2) 在 MySQL 5.0 之后的版本表示最多接受 2 个汉字的字符作为用户名存储,如果长度超限会报错:ERROR 1406 (22001): Data too long for column 'username' at row 1 。所以候选人可能会想当然的认为 int(m) 中的 m 表示存储数字的长度,int(3)int(11) 分别表示最多存储 3 位数和 11 位数,这种观点是完全错误的!

2.1 int (3) 和 int (11) 占用的硬件存储空间完全相同

首先,我们在申明某个字段数据类型为 int 的时候,不管是 int(3) 还是 int(11),在 MySQL 中存储时都占用 4 个字节的长度。

1 个字节(Byte) = 8 个二进制位(bit),所以 1 个 int = 4 Byte = 4 * 8 bit = 32 bit,计算机中使用首个比特位存储数字符号(参考补码的定义),所以可以算出 int(m) 的存储范围在 [-2147483648,2147483647] 之间。

2.2 int (3) 和 int (11) 在 zerofill 关键词修饰时展示不同

我们在之前创建的 mooc_demo 数据库中创建一张测试表:

DROP TABLE IF EXISTS `test_int`;
CREATE TABLE `test_int` (
  `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '数据库主键',
  `num1` int(3)  zerofill,
  `num2` int(11)  zerofill
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
代码块1234567

再插入一条测试数据:

insert into test_int (num1, num2) values (1,1);
代码块1

现在执行 select * from test_int; 查询语句,查询结果如图:


查询结果

如上图所示,存储相同的数字 1,num1 前补全了 2 个 0,num2 前补全了 10 个 0,

所以可以得出结论:int(m) 中的 m 表示在 zerofill 修饰时,数字长度不足 m 时前缀补充的 0 的个数,除此之外,两者使用时没有任何区别。

3. double(m,n)

面试官: MySQL 中 double (m,n) 中的 m 和 n 有什么含义?

题目解析: 这道题容易和上题一起出现,混淆视听,但是难度相对就简单多了。

double(m,n)float(m,n) 以及 decimal(m,n) 中的 m 和 n 定义均相同,而且比较清晰:

  • m:数据精度,即数据的总长度;

  • n:小数点精度,即浮点数小数点后的长度。

  • 举例说明float(6,2) 表示最多能存储 6 位长度的浮点数,并且小数点精度为 2。

实战验证下上述结论, 还是在之前创建的 mooc_demo 数据库中创建一张测试表:

DROP TABLE IF EXISTS `test_float`;
CREATE TABLE `test_float` (
  `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '数据库主键',
  `num1` float(6,2)  zerofill,
  `num2` double(6,2)  zerofill
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
代码块1234567

继续插入测试数据:

insert into test_float (num1, num2) values (1234.5678,1234.5678);
代码块1

执行 select * from test_float; 查询语句,查询结果如图:


查询结果

如上图所示,小数点 2 位之后的数据被截断,符合 SQL 定义时的预期。

4. 小结

MySQL 基础数据类型的知识学习可以从两个方面入手,一点是基本语法,学习基础语法的目的是能够上手使用这些数据类型,另一点是如何选择在合适的场景使用合适的数据类型,需要明确这种数据类型会占用多少的字节空间,数据类型的最小值和最大值是什么,选择不同数据类型可能会存在什么样的潜在问题。

1. 前言

在面向对象语言中涉及到诸多的设计模式,例如单例模式、适配器模式,设计模式的存在是为了让系统中的代码逻辑更加清晰,帮助开发者建立更加健壮的系统,同时满足易修改特性和易扩展特性。数据库设计时也存在类似设计模式的通用规范,被称为数据库范式。满足范式的数据库是简洁的,表与表之间的关系也清晰且明确,不会存储过多的冗余信息,在增删改查的时候也可以避免冗余的操作。

2. 数据库设计三大范式

面试官提问: 请描述下数据库设计的三大范式?

题目解析: 回答本题时,可以从总分的结构来阐述,即先阐述数据库范式的定义,再挨个解释每种范式的设计原则。

数据库范式定义:为了建立逻辑结构合理、冗余较小的数据库,在设计数据表时必须要遵循的设计规范。

接下来可以分点阐述第一、第二、第三范式的定义和案例。

2.1 数据库第一范式(1NF)

数据库第一范式是设计数据库时需要满足的最基本范式:

① 定义:第一范式(First Normal Form)要求数据库表中的所有字段都是不可拆分的原子字段,换句话说,每个字段不可以再进行拆分。

② 案例解释:对于一张最简单的用户信息表,定义了用户编号、姓名、年龄、电话这三个字段,user_info 表如下:

用户编号 (user_id)姓名 (username)年龄 (age)电话 (phone)
1小明2010086
2小红2110087
3小王2210088

其中电话 (phone) 这个字段可能存储的是座机电话号码、也可能是手机电话号码,定义上并不明确,这就违背了第一范式的原子性。所以为了满足第一范式,我们可以将电话字段拆分为座机电话 (fixed_phone) 和手机电话 (cell_phone) 两个字段,拆分后的 user_info 表如下:

用户编号 (user_id)姓名 (username)年龄 (age)座机电话 (fixed_phone)手机电话 (cell_phone)
1小明201008618010002000
2小红211008718010002001
3小王221008818010002002

③ 范式优点:拆分之后,字段定义定义清晰。在查询数据库时我们可以明确过滤的是座机号码还是手机号码,方便业务层逻辑开发,而且后续维护也方便。

2.2 数据库第二范式(2NF)

在满足第一范式的基础上,数据库第二范式对字段定义进行了更严格的约束:

① 定义:第二范式(Second Normal Form)要求数据库中的每一列都和主键相关,不能和主键的一部分相关。

② 案例解释:在电商环境下,我们需要设计一个订单表,因为订单和商品绑定, 所以将商品编号和订单编号作为订单表的联合主键,初始设计的订单(order)表如下:

订单编号 (order_id)商品编号 (good_id)购买数量 (order_num)单位 (unit)商品单价 (good_price)购买时间 (purchase_time)
1000188881千克1002020-10-11
1000288881千克1002020-10-12
10003889033002020-10-13

仔细观察,我们就能发现这种设计的问题在于:good_id = 8888 的商品,对于 order_id = 10001 和 10002 记录都存储了相同的单位和商品价格,这种冗余存储在数据量大的场景下是不能接收的,并且违反了第二范式设计原则,商品价格只和商品编号有关,和订单编号无关,我们将这张表进行拆分:

拆分的原则是:将属于商品的信息单独提炼为一张商品表,在原有的订单表只保留商品编号作为联合查询时的查询依据,优化后的订单(order)表如下:

订单编号 (order_id)商品编号 (good_id)购买数量 (order_num)购买时间 (purchase_time)
10001888812020-10-11
10002888812020-10-12
10003888932020-10-13

单独拆分出的商品(good)表如下:

商品编号 (good_id)单位 (unit)商品单价 (good_price)
8888千克100
8889300

③ 范式优点:拆分之后,降低了数据库的冗余存储,并且逻辑清晰,要查询商品信息即走 good 表,要查询订单信息即走 order 表。

2.3 数据库第三范式(3NF)

① 定义:第三范式(Third Normal Form)要求数据库表中的每个字段和主键都直接相关,不能间接相关。

② 案例解释:还是以第一范式中的 user_info 表作为案例,如果要存储每个用户的省份和省会城市,我们可能会设计出下面这样一张表:

用户编号 (user_id)姓名 (username)年龄 (age)座机电话 (fixed_phone)手机电话 (cell_phone)省份 (province)省会城市 (city)
1小明201008618010002000北京市北京市
2小红211008718010002001黑龙江省哈尔滨市
3小王221008818010002002贵州省贵阳市

我们将用户编号 (user_id) 作为主键,则姓名、年龄、座机电话、手机电话都和 "用户" 这个主体强相关,和主键直接相关,而省份和省会城市则和 "用户" 这个主体是弱相关,和主键间接相关,并且存在依赖关系:用户编号 -> 姓名,姓名 -> 省份,省份 -> 省会城市,这样构建了用户编号 -> 省会城市的间接传递关系,这种关系会导致数据冗余,而且在执行删除 / 修改 / 增加操作的时候,会产生异常情况:删除所有 "贵州省" 下的用户信息(即 user_id = 3 的记录),"贵州省" 和 "贵阳市" 的信息也被删除了(显然不合理,因为省份这个定义和省份下的人员记录并没有关系)。

所以我们需要将 user_info 表拆分,我们通过省份构建数据关系,优化后的用户(user_info)表如下:

用户编号 (user_id)姓名 (username)年龄 (age)座机电话 (fixed_phone)手机电话 (cell_phone)省份 (province)
1小明201008618010002000北京市
2小红211008718010002001黑龙江省
3小王221008818010002002贵州省

独立拆分出的省份(province)表如下:

省份 (province)省会城市 (city)
北京市北京市
黑龙江省哈尔滨市
贵州省贵阳市

③ 范式优点:提高了表的独立性,降低数据存储冗余。

3. 小结

作为开发,在日常设计数据库表的时候可能不会特意注意使用数据库范式,但是细心关注大部分企业项目的表结构,就会发现大部分表都是遵循数据库范式设计的,第二范式和第三范式可能会混淆概念,第二范式的核心是关注非主键列是否依赖主键或者主键的一部分,地三藩市的核心是关注非主键列是否依赖主键,还是依赖其他的非主键列。

1. 前言

在之前的章节谈到了数据库设计范式,遵循范式之后,数据会被组织成不同的结构分散存储在不同的表内,例如所有学生会被存储在一张学生表,所有学生的成绩会被存储在一张成绩表,如果我们同时需要两张表的数据,就需要计算两张表间数据的映射关系,MySQL 数据库中最常用的方法就是连接。

2. 左连接、右连接、全连接

面试官: 请阐述下 MySQL 中左连接、右连接、全连接的定义和区别?

题目解析:

定义:MySQL 的连接表示多表(一般就是两张表)之间联合查询的操作。

分类:根据操作性质的不同,分为内连接和外连接,外连接又可以细分为左外连接和右外连接。除此之外,还有一种全连接操作,不过 MySQL 数据库并不支持。

定义解释比较抽象,下面我们通过实战来讲解这几种连接的区别,首先进入 MySQL 终端,首先创建一个测试数据库:

CREATE DATABASE mooc_demo;

创建一张测试表 test_a

DROP TABLE IF EXISTS `test_a`;
CREATE TABLE `test_a` ( `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '数据库主键', `name` varchar(32) DEFAULT NULL COMMENT '姓名', `part` varchar(32) DEFAULT NULL COMMENT '部门'ENGINE=InnoDB DEFAULT CHARSET=utf8;

然后插入一些测试数据:

insert into test_a (`name`, `part`) values ('小明','文艺部');insert into test_a (`name`, `part`) values ('小红','学习部');insert into test_a (`name``part`values ('小王','体育部');

继续创建另外一张测试表 test_b

DROP TABLE IF EXISTS `test_b`;
CREATE TABLE `test_b` ( `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '数据库主键', `name` varchar(32) DEFAULT NULL COMMENT '姓名', `group` varchar(32) DEFAULT NULL COMMENT '小组'ENGINE=InnoDB DEFAULT CHARSET=utf8;

插入一些测试数据

insert into test_b (`name`, `group`) values ('小明', '1号小组');insert into test_b (`name`, `group`) values ('小红', '2号小组');insert into test_b (`name``group`values ('小李''3号小组');

执行完成之后,两张表的数据如下:

两张表数据

2.1 内连接

SQL 语法:inner join table_name on table_name

构建一条测试 SQL:select * from test_a a inner join test_b b on a.name = b.name;,执行结果如下图:

内连接结果

执行结果解释:组装两张表满足 a.name = b.name 的查询结果。

我们以数据中的集合类比,表 test_a 和表 test_b 是两个数据集合,内连接则表示查询两个表都符合条件的数据,即集合的交集操作。

集合交集

2.2 左连接

SQL 语法:...left join table_name on table_name

构建一条测试 SQL:select * from test_a a left join test_b b on a.name = b.name; ,执行结果如下图:

左连接执行结果

执行结果解释:左连接(left join)是左外连接(left outer join)的简写,左连接会将左表(test_a)的所有记录都展示出来,而右表(test_b)只会展示符合搜索条件(上图中的 on condition)的搜索记录,其他记录以 NULL 作为补全。

即展示两个集合的交集以及左边集合的剩余部分数据:

集合左交集

2.3 右连接

SQL 语法:right join table_name on table_name

构建一条测试 SQL:select * from test_a a right join test_b b on a.name = b.name; ,执行结果如下图:

右连接执行结果

执行结果解释:右连接(right join)是右外连接(right outer join)的简写,右连接会将右表(test_b)的所有记录都展示出来,而左表(test_a)只展示符合后置条件(on condition)的记录展示,其他记录以 NULL 作为补全。

即展示两个集合的交集以及右边集合的剩余部分:

集合右交集

3. 小结

SQL 查询可以拆分为两种情况,一种是单表查询,即根据 where 条件语句查询得到中间表,然后执行 select 语句选择需要的列返回给控制台。另一种是多表查询,对多张表求笛卡尔积,使用 on 语句作为连接条件得到中间表,之后还是通过 where 语句过滤中间表的记录,选择需要的列返回给控制台,本章节介绍的就是第二种查询方式。

1. 前言

对于常见的应用系统,读的流量远远高于写的流量,比如电商网站,商家在数据库中写入商品的价格和库存之后,访问页面的顾客会产生大部分的读流量。所以常见的现象是当应用系统的流量逐渐增加时,写操作不会成为数据库的性能瓶颈,但是复杂查询语句消耗的查询时间会越来越长,读操作更容易触碰数据库的查询性能瓶颈。MySQL 自身为了优化查询效率,更快的查询目标集合,定义了索引,也就是常用的 "键"(Key),MySQL 中的索引是单独存储在磁盘上的数据结构,使用索引可以快速查询满足特定条件的记录。

2. 谈一谈 InnoDB 存储引擎的索引数据结构

2.1 不同引擎的数据结构

面试官提问: MySQL 中 InnoDB 存储引擎底层的数据结构是什么?

题目解析:

以 MySQL 5.7 为例,首先查询官方文档,可以发现存储引擎和索引数据结构的对应关系,例如 InnoDB 对应 BTREE 索引,MEMORY 存储引擎对应哈希索引和 BTREE 索引,注意这里的 BTREE 实际指代的是 B+ 树,我们重点关注树的数据结构。

存储引擎和索引类型的对应关系,表格来自 MySQL 5.7 官网

2.2 B 树和 B + 树

如果能正确回答 InnoDB 索引的底层数据结构是 B+ 树,面试官接下来可能会先考察候选人对数据结构本身的理解程度。

面试官:学过数据结构课程的同学,应该都听过 B 树和 B+ 树吧,这两种树有什么区别呢?

题目解析:我们尽量需要通过在白纸上画出 B 树和 B+ 树,画图的同时给面试官解释两种树的区别,需要从数据结构、优缺点方面分析(一般来说不需要深入到节点的插入和删除流程,因为比较复杂)首先我们画出一个简化后的 B 树,如下图:

B 树,图中绿色节点表示具体数据,蓝色节点表示指针,黄色表示键值,整个节点指代一个磁盘块

参考上图,我们定义一个 m 阶的 B 树的数据结构:

① 根结点至少有两个子节点;② 除了根节点外,每个子节点都包含 n-1 个元素(数据)和 n 个子节点指针,其中 m/2 <= n <= m;③ 所有的叶子结点都位于同一层;④ 有序性:每个节点中的元素从小到大排列,节点当中 k-1 个元素正好是 k 个孩子包含的元素的值域分划。

画出 B 树只是为了衬托 B+ 树,B 树不会是面试的重点,接下来我们在白纸上画出一个典型的 B+ 树结构:

B + 树,图中绿色节点表示具体数据,蓝色节点表示指针,黄色表示键值,整个节点指代一个磁盘块

对于一个 m 阶的 B+ 树,基本定义同 B 树相同:① 除了根之外的每个节点都包含最少 m/2 个元素最多 m-1 个元素,对于任意的结点有最多 m 个子指针;② 所有的叶子节点都在同一层;

除此之外,B+ 树相对于 B 树,需要特别区分的不同点有:① 数据存储方式不同:B+ 树中间节点并不存储真正的数据,而是保存其叶子节点中最小值作为索引。例如上图中磁盘块 2 和磁盘块 3 中并没有黄色的 data 节点;② 数据查找方式不同:每个叶子节点存在一个 next 指针,指向下一个叶子节点,形成了有序双向链表,从图中能明显看出来。所以 B 树只能由根节点往下二分查找,B+ 树除了这种查找方式,还支持在叶子节点中直接顺序遍历查找。

2.3 为什么使用 B + 树

在给面试官阐述清楚了 B 树和 B+ 树数据结构的区别之后,接下来面试官大概率会追根溯源,引出更深一步的问题:

** 面试官:** 你能说说为什么 MySQL 要选择 B+ 树作为索引的数据结构实现吗?

题目解析:

结合我们画出的数据结构图示,这个问题翻译过来其实是相对于 B 树,为什么要选择 B+ 树?首先要分析数据库的读写瓶颈受限原因:计算机的存储是分层次的,CPU 里的 Cache 访问速度最快,速度更慢的是内存(容量小,断电情况下数据会丢失),读写最慢的是硬盘(容量大,数据可长期存储)。MySQL 的数据是持久化存储在硬盘中的,硬盘访问慢是因为:CPU 访问硬盘时会经过三个耗时步骤:

(1)寻道耗时:磁臂移动到磁道;(2)旋转耗时:例如一个 7200 转的磁盘,表示每分钟能转 7200 次;(3)数据传输耗时:从磁盘读出数据或者写入数据的过程。前两者都依赖于磁盘的机械运动,耗时非常久。所以磁盘 I/O 是一种非常昂贵的操作,数据库的读写操作需要经过尽可能少的磁盘 I/O。

我们在这个硬件基础上,我们依据 B 树的图示,模拟查询关键词 6 的一次查询过程:

(1)磁盘第一次 I/O 操作:找到磁盘块 1,读入内存;(2)磁盘第二次 I/O 操作:比较关键词 6 在区间(0,7)之间,找到磁盘块 1 的左指针,根据指针找到磁盘块 2,读入内存;(3) 磁盘第三次 I/O 操作:比较关键词 6 在区间(5,7)之间,找到磁盘块 2 的最右侧指针,根据指针找到磁盘块 6,读入内存;(4) 寻找关键词:在磁盘块 6 中找到关键词 6,以及对应 data 数据。

可以发现,树的深度越深,查找需要的磁盘 I/O 次数就越多。现在我们再来分析 B + 树的优势:

(1) B + 树的磁盘 I/O 次数相对更少:利用 B 树 / B+ 树的有序性,从根节点每往子节点每查找一次,都要经过一次磁盘 I/O。相对 B 树,B+ 树的内部节点只包含下级指针,并不存放数据信息,所以对于同样大小的磁盘块,能够存储的记录个数更多(树的阶 m 更大),B+ 树的深度更低,所以磁盘 I/O 次数更少。(2) B+ 树更适合范围查找:在进行范围查询时,B 树查询只能通过从根节点开始的递归查询,因为相邻节点在硬盘中不一定连续,缓存命中率差,而 B+ 树因为叶子节点形成有序链表,可以直接进行线性遍历。

综上,回答本题的核心点要抓住:(1)明确磁盘 I/O 是数据库读写的硬件瓶颈。(2)能够结合两种树不同的数据结构,分析得到 B+ 树的优势。

3. 小结

本章节介绍了 MySQL 中 InnoDB 存储引擎的底层 B+ 树数据结构,候选人需要区分 B 树和 B+ 树,以及从查询效率和硬件成本角度分析为什么在 MySQL 中会优选使用 B+ 树作为支撑。

1. 前言

MySQL 中事务(Transaction)的定义是对于一个或者多个 SQL 语句,要么全部执行成功,要么一个都不执行成功。在实际应用场景中,有很多需要事务的场景,例如在电商网站,顾客下单、付款以及商品扣减库存就应该在一个事务中执行,如果不能保证事务特性,就可能出现用户已经下单并且成功付款,但是在扣减库存逻辑出现异常,发货失败的情况。所以事务中的某个环节出现异常,之前执行的所有 SQL 语句都应该回滚。

2. 事务

2.1 事务 ACID 特性

面试官提问: MySQL 中事务的特性是什么?

题目解析:

ACID 是衡量事务的 4 个维度,分别的定义是:

(1)原子性(Atomic,简写 A):原子性要求事务是一个不可分割的执行单位,如果一个事务包含多条 SQL 语句,要么所有的 SQL 都执行成功,要么所有的 SQL 都执行失败,不存在两者之间的中间状态。如果事务中的任意一条 SQL 执行失败,那么已执行成功的也需要回滚。MySQL 中 InnoDB 引擎利用 undo log 实现原子性,undo log 记录了所有已执行的 SQL 记录,如果事务执行失败调用了 rollback 语句,那么使用 undo log 的记录回滚已执行的 SQL。(2)持久性(Durability,简写 D):持久性要求事务一旦提交(commit),对数据库的改变就应该是永久的,其他的操作不会对已提交的事务有影响。InnoDB 引擎中使用 redo log 实现持久性,如果 MySQl 服务器宕机,那么在重启时可以读取 redo log 中的记录恢复数据库。(3)隔离性(Isolation,简写 I):要求一个事务的执行不受到其他并发执行事务的影响。(4)一致性(Consistency,简写 C):事务将数据库从一种状态转换到另一种状态,但是两种状态从数据上是一致的。例如用户下单扣库存让库存减少了一个单位,那么在订单中就会增加一个单位的商品,库存和订单中的商品数量和是不会改变的。

2.2 事务隔离级别

面试官提问:ACID 特性中的隔离性在 MySQL 中的具体定义是什么?

题目解析:

MySQL 提供了 4 种事务隔离级别,分别是:

(1)读未提交(Read Uncommitted):所有事务可以看到其他事务未提交的执行结果;(2)读已提交(Read Committed):所有事务只能看到其他事务已提交的执行结果;(3)可重复读(Repeatable Read):MySQL 默认的隔离级别,所有事务能看到其他事务已提交后的修改后数据,但是如果第一次读取到这个修改后的数据,如果其他事务继续修改了数据并且提交,这个事务读到的也是第一次读到的值,不会读到修改后的新值。(4)串行化(Serializable):最高隔离级别,可以理解为让所有并发执行的事务都进入队列,挨个串行执行,永远不可能发生冲突。

我们关注事务,关注点在于不同事务的并发冲突,而且重点在于读写操作。对于同一条数据,在执行并发事务时可能会产生读写上的问题,有三种:

(1)脏读(Dirty Read):如果事务 A 更新了一份数据,比如将记录 a 更新为记录 b,那么在事务 B 中读取到的记录是 b,此时事务 A 进行了回滚操作,记录 b 回滚为记录 a,那么事务 B 读到的记录 b 则是非法数据。(2)不可重复读(Non-Repeatable Read):如果事务 A 更新了一份数据,比如将记录 a 更新为记录 b,那么在事务 B 中读取到的记录是 b,此时事务 A 继续将记录 b 更新为记录 c,那么事务 B 第二次读到的记录是 c,两次读取的结果不同。(3)幻读(Phantom Read):如果事务 B 查询到了几行数据,此时事务 A 又插入了几行新数据,那么事务 B 会读到多出来的几行数据,读到了上次读取没出现的数据。

4 种隔离级别对应的问题应对能力如下表:

隔离级别脏读不可重复读幻读
读未提交
读已提交
可重复读
串行化

从解决问题的能力上看,串行化能解决所有的并发读写问题,但是串行执行效率太低,比如在电商网站的秒杀商品下单流程,就会导致所有的用户需要等某一个用户执行完下单操作后才能继续抢购,不具有实战意义。MySQL 默认的隔离级别是可重复读,这个级别能解决脏读和不可重复读的问题,效率上相对比较快。读未提交的执行效率最高,但是数据的一致性保障最差, 一般不会在实战中使用。

在 MySQL 客户端执行 show variables like 'transaction_isolation'; 语句可查看隔离级别:

MySQL 默认隔离级别

3. 小结

本小结概括了事务的 ACID 特性以及 4 种隔离级别的定义,候选人可以自行使用小样本数据测试 MySQL 不同隔离级别下事务的读写区别。


    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多