Java后端面试系列-数据库篇
- 1 概述
- 1.1 关系型数据库主要考点
- 1.2 一个面试题引发的"血案"
- 2 索引模块
- 2.1 为什要使用索引
- 2.2 什么样的信息能够成为索引
- 2.3 索引的数据结构
- 2.3.1 二叉查找树上阵
- 2.3.2 B-Tree
- 2.3.3 B+ -Tree
- 2.3.4 结论
- 2.3.5 Hash索引
- 2.3.6 BitMap索引
- 2.4 密集索引和稀疏索引的区别
- 2.5 衍生出来的问题,以mysql为例
- 2.5.1 如何定位并优化慢查询Sql
- 2.5.2 联合索引的最左匹配原则的成因
- 2.5.3 索引是建立得越多越好吗
- 3 锁模块
- 3.1 MyISAM与InnoDB关于锁方面的区别是什么
- 3.2 适用场景
- 3.2.1 MyISAM适合的场景
- 3.2.2 InnoDB适合的场景
- 3.3 锁的分类
- 3.4 数据库事务的四大特性
- 3.5 事务隔离级别以及各级别下的并发访问问题
- 3.6 InnoDB可重复读隔离级别下如何避免幻读
- 3.7 RC、RR级别下的InnoDB的非阻塞读如何实现
- 4 语法部分
- 5 数据库三范式
1 概述
1.1 关系型数据库主要考点
![Java后端面试系列-数据库篇_第1张图片](http://img.e-com-net.com/image/info8/2513f8ef75fc49bbae38659927ef65c0.jpg)
1.2 一个面试题引发的"血案"
- 问:如何设计一个关系型数据库?
- 答:
![Java后端面试系列-数据库篇_第2张图片](http://img.e-com-net.com/image/info8/0b473d4ba8c44d59b90f46caf7e2064c.jpg)
- 存储(文件系统):持久化
- 程序实例:包含以下8个模块。
存储模块:磁盘IO速率是瓶颈,得设计优化方法,比如:一次读取多条数据进入内存。
缓存机制:不宜过大,且有淘汰机制比如:LRU
SQL解析:将SQL语句进行解析
日志管理:记录操作记录,比如binlog
权限划分:记录多用户管理的权限划分模块,一般DBA关心
容灾机制:
索引管理:优化数据查询速率
锁管理:使之支持并发操作
2 索引模块
常见问题
- 为什要使用索引?
- 什么样的信息能够成为索引?
- 索引的数据结构?
- 密集索引和稀疏索引的区别?
2.1 为什要使用索引
答:快速查询数据。
若使用全表扫描,在数据量小的时候没有问题,一旦数据量大了,查询效率会降低;而索引类似于字典的偏旁部首,先找偏旁部首,可以更快速找到想要的数据。
2.2 什么样的信息能够成为索引
答:主键、唯一键以及普通键等
2.3 索引的数据结构
- 生成索引,建立二叉查找树进行二分查找
- 生成索引,建立B-Tree结构进行查找
- 生成索引,建立B±Tree结构进行查找
- 生成索引,建立Hash结构进行查找
2.3.1 二叉查找树上阵
![Java后端面试系列-数据库篇_第3张图片](http://img.e-com-net.com/image/info8/d318905a0224402b9f34d880ebea901a.jpg)
首先先说二叉查找树,这是会出现一个问题:当往一边增加会出现下面的情况:
![Java后端面试系列-数据库篇_第4张图片](http://img.e-com-net.com/image/info8/61a7267fb23e4655958a02a0b04488f1.jpg)
结果是:二叉查找树退化成一个链表,时间复杂度为O(N)。
此时一个解决办法是通过旋转,维持树的平衡,比如:平衡二叉树、红黑树。但是又有一个问题:磁盘IO是关键,而磁盘IO访问次数与树的深度直接相关,比如访问数字“6”,就需要三次,还可以优化吗?
很明显,问题出在这些平衡树的子树最多两个,能不能多个呢,此时B-Tree站出来了。
2.3.2 B-Tree
![Java后端面试系列-数据库篇_第5张图片](http://img.e-com-net.com/image/info8/fd7847ae6228465c95311c74f28f739f.jpg)
定义:
- 根节点至少包括两个孩子
- 树中每个节点最多包含有m个孩子(m>=2)
- 除根节点和叶节点外,其他每个节点至少有ceil(m/2)个孩子
- 所有叶子节点都位于同一层
- 假设每个非终端节点中包含有 n n n个关键字信息,其中:
- K i ( i = 1... n ) K_i(i=1...n) Ki(i=1...n)为关键字,且关键字按顺序升序排序 k ( i − 1 ) < K i k(i-1)k(i−1)<Ki
- 关键字的个数 n n n必须满足: [ c e i l ( m / 2 ) − 1 ] < = n < = m − 1 [ceil(m/2)-1] <= n <= m-1 [ceil(m/2)−1]<=n<=m−1
- 非叶子结点的指针:P[1],P[2],…,P[M];其中P[1]指向关键字小于K[1]的子树,P[M]指向关键字大于K[M-1]的子树,其他P[i]指向关键字属于(K[i-1],K[i])的子树
看起来,B-Tree已经不错了,但还有更好地选择。
2.3.3 B+ -Tree
![Java后端面试系列-数据库篇_第6张图片](http://img.e-com-net.com/image/info8/5a9e1dd8b3a84a4e82a93769ef5af621.jpg)
B+树是B树的变体,其定义基本与B树相同,除了:
- 非叶子结点的子树指针与关键字个数相同
- 非叶子结点的子树指针P[i],指向关键字值[K[i],K[i+1])的子树
- 非叶子结点仅用来索引,数据都保存的叶子节点中
- 所有叶子节点均有一个链指针指向下一个叶子节点
2.3.4 结论
B+Tree更适合用来做存储索引
- B+树的磁盘读写代价更低
- B+树的查询效率更加稳定
- B+树更有利于对数据库的扫描
2.3.5 Hash索引
![Java后端面试系列-数据库篇_第7张图片](http://img.e-com-net.com/image/info8/92ccd61f2667431d8a3ed2aece64ac10.jpg)
优点:查询效率高
缺点:
- 仅能满足“=”,“IN”,不能使用范围查询
- 无法用来避免数据的排序操作
- 不能利用部分索引建查询
- 不能避免表扫描
- 遇到大量Hash值相等的情况后性能并不一定比B-tree索引高
2.3.6 BitMap索引
![Java后端面试系列-数据库篇_第8张图片](http://img.e-com-net.com/image/info8/eb8a0e7188134399a72904fb0ecaa391.jpg)
- 高效,适合统计
- 只适用于某个字段只有有限个取值的情况下
- 锁的粒度大
2.4 密集索引和稀疏索引的区别
- 密集索引文件中的每个搜索码值都对应一个索引值
- 稀疏索引文件置为索引码的某些值建立索引项
![Java后端面试系列-数据库篇_第9张图片](http://img.e-com-net.com/image/info8/6fdc18e8eb7f4df8a625d3cb1808762c.jpg)
![Java后端面试系列-数据库篇_第10张图片](http://img.e-com-net.com/image/info8/e32eed92c2cc414cbc30d0d205f3b24b.jpg)
2.4.1 额外知识
针对Mysql,MyISAM都是稀疏索引,而InnoDB有且仅有一个密集索引
InnoDB
- 若一个主键被定义,该主键则作为密集索引
- 若没有主键被定义,该表第一个唯一非空索引则作为密集索引
- 如不满足上述条件,则InnoDB内部会生成一个隐藏主键(密集索引)
- 非主键索引存储相关键位和其对应的主键值,包含两次查找
![Java后端面试系列-数据库篇_第11张图片](http://img.e-com-net.com/image/info8/8e405641f126455a802f727ce94aa76d.jpg)
实际的样子
![Java后端面试系列-数据库篇_第12张图片](http://img.e-com-net.com/image/info8/0b17f3e44bf04bb2a8ef7362d6ebb75e.png)
上面两个文件就是InnoDB
的,下面三个是MyISAM
的
.frm
:代表表的结构
.ibd
:代表InnoDB表的数据和索引
.MYD
:代表MyISAM的数据
.MYI
:代表MyISAM的索引
2.5 衍生出来的问题,以mysql为例
- 如何定位并优化慢查询Sql
- 联合索引的最左匹配原则的成因
- 索引是建立得越多越好吗
2.5.1 如何定位并优化慢查询Sql
大致思路如下:
- 根据慢日志定位慢查询sql
- 使用 explain等工具分析sql
- 修改sql或者尽量让sql走索引
(1)根据慢日志定位慢查询sql
SHOW VARIABLES LIKE '%quer%';
查询一些系统变量:
![Java后端面试系列-数据库篇_第13张图片](http://img.e-com-net.com/image/info8/ddb3530b61834dbdb11294afd53fbb40.png)
主要由三个变量值得注意:
long_query_time
:查过这个时间,就算是个慢查询
slow_query_log
:是否开启慢差日志
slow_query_log_file
:慢差日志保存位置
修改命令如下:
SET GLOBAL long_query_time = 1;
SET GLOBAL slow_query_log = ON;
SET GLOBAL slow_query_log_file = 'E:\\program-software\\mysql5.7.23\\slow.log';
开始查询:
SELECT NAME FROM t_teacher ORDER BY NAME DESC;
可以打开slow.log
![Java后端面试系列-数据库篇_第14张图片](http://img.e-com-net.com/image/info8/d6986fb842b5403dbe0fff123bfc87d9.png)
花了近10秒
也可用通过命令查询本次连接慢差的次数:
SHOW STATUS LIKE '%slow_queries%';
![在这里插入图片描述](http://img.e-com-net.com/image/info8/b5a1ab5a220949c799edf2e9030f9a07.png)
(2)根据explain
工具分析这条sql
EXPLAIN SELECT NAME FROM t_teacher ORDER BY NAME DESC;
重点是看type
和Extra
两个字段
- type
![在这里插入图片描述](http://img.e-com-net.com/image/info8/4e31d56b321b4fb18a1a620f7e0cafab.jpg)
- extra
![Java后端面试系列-数据库篇_第15张图片](http://img.e-com-net.com/image/info8/29e45ce97e8f45ea99fe962cd60ecbe3.jpg)
(3)修改sql或者尽量让sql走索引
改
EXPLAIN SELECT id FROM t_teacher ORDER BY id DESC;
加索引
ALTER TABLE t_teacher ADD INDEX idx_name(NAME);
2.5.2 联合索引的最左匹配原则的成因
![Java后端面试系列-数据库篇_第16张图片](http://img.e-com-net.com/image/info8/6769c5fb92f24448a8f4ee16b8d767b2.jpg)
2.5.3 索引是建立得越多越好吗
不是。
- 数据量小的表不需要建立索引,建立会增加额外的开销。
- 数据变更需要维护索引,因此需要更多的维护成本
- 更多的索引意味着需要更多的空间开销。
3 锁模块
常见问题
- MyISAM与InnoDB关于锁方面的区别是什么
- 数据库事务的四大特性
- 事务隔离级别以及各级别下的并发访问问题
- InnoDB可重复读隔离级别下如何避免幻读
- RC、RR级别下的InnoDB的非阻塞读如何实现
3.1 MyISAM与InnoDB关于锁方面的区别是什么
![Java后端面试系列-数据库篇_第17张图片](http://img.e-com-net.com/image/info8/1d909f4bd78f43f881c68d2694453100.jpg)
InnoDB中在SQL没用到索引时,走的表级锁;用到索引,走的行级索。
![Java后端面试系列-数据库篇_第18张图片](http://img.e-com-net.com/image/info8/c4e74938fed94c3d807b3cf12e791fe6.jpg)
3.2 适用场景
3.2.1 MyISAM适合的场景
- 频繁使用全表count语句
- 对数据库进行增删改的频率不高,查询非常频繁
- 没有十五
3.2.2 InnoDB适合的场景
- 数据库的增删改操作很多时
- 对数据库的可靠性要求高时,要求支持事务
3.3 锁的分类
- 按锁的粒度划分,可分为表级锁、行级锁、页级锁
- 按锁级别划分,可分为共享锁、排它锁
- 按加锁方式划分,可分为自动锁、显式锁
- 按操作分,可分为DML锁,DDL锁
- 按使用方式划分,可分为乐观锁,悲观锁
3.4 数据库事务的四大特性
ACID
3.5 事务隔离级别以及各级别下的并发访问问题
问题:
- 更新丢失——read uncommitted(读未提交)解决
- 脏读——read committed(读已提交)解决
- 不可重复读——repeatable read (可重复读)解决
- 幻读——serializable(序列化)解决
3.6 InnoDB可重复读隔离级别下如何避免幻读
- 表象:快照读(非阻塞读)——伪MVCC
- 内在:next-key锁(行锁+gap锁)
问:对主键索引或唯一索引会用Gap锁吗?
- 如果where条件全部命中,则不会用Gap锁,只会加记录锁。
![Java后端面试系列-数据库篇_第19张图片](http://img.e-com-net.com/image/info8/b7679f947fc549e98c96b8b1b8fbb5cb.jpg)
- 如果where条件部分命令或全部不命中,则会用Gap锁,记录锁。
Gap锁会用在费唯一索引或者不走索引的当前读中
3.7 RC、RR级别下的InnoDB的非阻塞读如何实现
- 数据行里的DB_TRX_ID(最后一次修改的事务ID)、DB_ROLL_PTR(回滚指针,指向undo log中修改前的行)、DB_ROW_ID(插入数据时自增)字段
- undo日志(存储的老版本数据,分别insert undo log和update undo log)
insert undo log
指定的insert操作的undo log,只在事务回滚时需要,并且在提交事务之后就会丢弃;
update undo log
:存储update、detele操作,不仅在事务回滚时需要,快照读时也需要,只有当快照中不涉及该日志中的操作,才会被删除。
![Java后端面试系列-数据库篇_第20张图片](http://img.e-com-net.com/image/info8/4ff90ca8ba6f4c34aeebac32618ccfae.jpg)
- read view(还会根据你的事务ID得出你可见的数据,因为越新打开的事务,事务ID越大)
注
:只有非序列化隔离级别下的select
语句为快照读;其他是当前读,比如:select .. lock in share mode
,select ... for update
,update delete insert
4 语法部分
关键语法:
- GROUP BY
- HAVING
- 统计相关:CUNT、SUM、MAX、MIN、AVG
4.1 GROUP
- 满足“select 子句中的列名必须为分组列或列函数”
- 列函数对于group by子句定义的每个组各返回一个结果
![Java后端面试系列-数据库篇_第21张图片](http://img.e-com-net.com/image/info8/8ec7aa132d994f9291bb4acbcef38311.jpg)
SELECT student_id,COUNT(course_id),SUM(score)
FROM score
GROUP BY student_id
SELECT score.student_id,student.`name`,COUNT(course_id),SUM(score)
FROM score, student
WHERE score.`student_id` = student.`student_id`
GROUP BY score.student_id
4.2 HAVING
- 通常与GROUP BY子句一起使用
- WHERE过滤行,HAVING过滤组
- 出现在同意sql的顺序:WHERE>GROUP BY>HAVING
SELECT student_id, AVG(score)
FROM score
GROUP BY student_id
HAVING AVG(score) > 60
SELECT s.student_id, s.name
FROM student s, score sco
WHERE s.student_id = sco.student_id
GROUP BY sco.student_id
HAVING COUNT(sco.course_id) != (SELECT COUNT(*) FROM course);
5 数据库三范式
-
1NF:字段不可分;
-
2NF:有主键,非主键字段依赖主键;
-
3NF:非主键字段不能相互依赖,不存在传递依赖