面经-数据库
数据库
SQL与NoSQL
structured query language
区别
- SQL结构严谨,NoSQL结构松散


- SQL外键关联,不能随便删除数据;NoSQL通过json文档嵌套记录关联


- sql查询语句格式固定,nosql查询语句格式不固定。
- sql满足事务的acid,nosql满足事务的base。
- sql磁盘存储数据,nosql内存存储数据。
MySQL和Reids的区别
- Redis是一个基于内存的键值存储数据库,通过持久化机制将数据写入磁盘,MySQL通常将数据存储在磁盘上。
- Redis支持多种数据结构;而MySQL是一种关系型数据库,使用表来组织数据。
- Redis使用自己的命令集,MySQL使用SQL来进行数据查询和操作。
总结,Redis以高性能和低延迟为目标,适用于读多写少的应用场景,MySQL 适用于需要支持复杂查询、事务处理、拥有大规模数据集的场景。在实际应用中,很多系统会同时使用 MySQL 和 Redis。
mysql
数据库三大范式
第一范式(1NF):属性不可分割,即每个属性都是不可分割的原子项。
第二范式(2NF):满足第一范式;且不存在部分依赖,即非主属性必须完全依赖于主属性。(主属性即主键;完全依赖是针对于联合主键的情况,非主键列不能只依赖于主键的一部分)
- 部分依赖?
第三范式(3NF):满足第二范式;且不存在传递依赖,即非主属性不能与非主属性之间有依赖关系,非主属性必须直接依赖于主属性,不能间接依赖主属性。(A -> B, B ->C, A -> C)
多叉树
硬盘的特性:硬盘读取物理地址连续的多个字节和读取单个字节耗时几乎没有区别。
二叉树和多叉树它们访问结点是在硬盘上进行的,结点内的数据操作是在内存中进行的。
B树

二叉树规定了节点的插入顺序,让左子节点的元素永远小于右子节点,可以快速的查询到某一个元素。但随着数据量的不断增大,树的深度也不断增大,不方便数据的查找,由此产生了多叉树,也就是B树, 多叉搜索树中的节点,可以存储多个元素,这样,导致多叉搜索树有多个分支,除了根节点是二叉之外,其它节点是多叉的;整体来说,B树可以像二叉搜索树那样,快速查找到元素。
B树从无到有开始插入数据时,首先要确定是几阶B树,然后再进行数据插入操作。
删除非叶子节点的内容,最后都落在了删除叶子节点上(用相邻前驱和后动的叶子节点来替换它)。
B+树

B+树是在B树的基础上发展而来的,B+ 树的所有元素(数据),全部存放在叶子节点;不同于B树的m个分支,m个节点,B+树有m个分支,m-1个节点;非叶节点是对应叶子节点的最大值。
使用B+树来做索引的好处
- 单点查询:
- B 树进行单个索引查询时,最快可以在 O(1) 的时间内就查到。但是 B 树的查询波动会比较大,因为每个节点既存索引又存记录,所以有时候访问到了非叶子节点就可以找到索引,而有时需要访问到叶子节点才能找到索引。
- B+树的非叶子节点不存放实际的记录数据,仅存放索引,所以数据量相同的情况下,相比既存索引又存记录的 B 树,B+树的非叶子节点可以存放更多的索引,层数更少,因此查询底层节点的磁盘 I/O次数会更少。
- 范围查询:
- B+ 树所有叶子节点间有一个链表进行连接,而 B 树没有将所有叶子节点用链表串联起来的结构,因此只能通过树的遍历来完成范围查询,这会涉及多个节点的磁盘 I/O 操作,范围查询效率不如 B+ 树。
- (可略过)存在大量范围检索的场景,适合使用 B+树,比如mysql数据库。而对于大量的单个索引查询的场景,可以考虑 B 树,(比如nosql的MongoDB)。
- 插入和删除效率:
- B+ 树有大量的冗余节点,删除一个节点的时候,可以直接从叶子节点中删除,甚至可以不动非叶子节点,删除非常快。B 树没有冗余节点,删除节点的时候非常复杂,可能涉及复杂的树的变形。
- (插入节点时也是一样)B+ 树的插入也是一样,有冗余节点,插入可能存在节点的分裂(如果节点饱和),但是最多只涉及树的一条路径。
慢查询以及优化
数据库查询的执行时间超过指定的阈值时间时,就被称为慢查询。
等待时间长
- 并发冲突:当多个查询同时访问相同的资源时,可能发生并发冲突,导致查询变慢。
执行时间长
- 查询语句比较复杂:查询涉及多个表,包含复杂的连接和子查询,可能导致执行时间较长。
- 缺少索引或者索引失效:如果查询的表没有合适的索引,需要遍历整张表才能找到结果,查询速度较慢。
- 查询数据量大:当查询的数据量庞大时,即使查询本身并不复杂,也可能导致较长的执行时间。
- 数据库设计不合理:数据库表设计庞大,查询时可能需要较多时间。
- 硬件资源不足(服务器调优及各个参数的设置):如果MySQL服务器上同时运行了太多的查询,会导致服务器负载过高,从而导致查询变慢
优化:
等待时间长
order by limit
形式的sql语句,让排序的表优先查或者其它的并发线程调度方式来进行优化;
执行时间长
- 用explain:显示mysql如何使用索引来处理select语句以及连接表,可以帮助选择更好的索引、写出更优化的查询语句
- 考虑建立索引原则
- 查询区分度最高的字段 ,如主键id;只读取自己需要的列,少用
select *
1 | # 开启MySQL慢日志查询开关 |
一条SQL查询语句是如何执行的?

- 连接器: 连接器负责跟客户端建立连接、获取权限、维持和管理连接。
- 查询缓存:
MySQL
拿到一个查询请求后,会先到查询缓存看看,之前是不是执行过这条语句。之前执行过的语句及其结果可能会以key-value
键值对的形式,被直接缓存在内存中;在8.0版本后被废弃,因为性能不高,一旦有更新,之前的缓存就要废弃。 - 分析器: 类似于代码编译的过程。需要分析出代码具体要做什么,查询哪个表,什么条件等等。
- 优化器: 决定sql代码执行顺序,找到一条它认为的最优执行顺序。如在表里面有多个索引的时候,决定使用哪个索引;
- 执行器: 执行器用来执行语句。
以上是server层,下面的是执行引擎层也叫存储引擎层。
MySQL的执行引擎有哪些?
MySQL的执行引擎主要负责查询的执行和数据的存储, 其执行引擎主要有Memory
、MyISAM
、InnoDB
等。
Memory
就是将数据放在内存中,访问速度快,但数据在数据库服务器重启后会丢失。MyISAM
引擎是早期的默认存储引擎,支持全文索引,但是不支持事务,也不支持行级锁和外键约束,适用于快速读取且数据量不大的场景。oInnoDB
引擎提供了对事务ACID的支持,还提供了行级锁和外键的约束,是目前MySQL的默认存储引擎,适用于需要事务和高并发的应用。
事务的四大特性有哪些?
事务的四大特性通常被称为 ACID
特性
- 原子性(atomicity):确保事务的所有操作要么全部执行成功,要么全部失败回滚,不存在部分成功的情况。
- 一致性(consistency):一致性是通过一致性约束来实现的,如定义银行卡余额不得为负,那么余额为0时转账失败进行事务回滚。
- 隔离性(isolation):多个事务并发执行时,每个事务都应该被隔离开来,一个事务的执行失败不应该影响其他事务的执行。
- 持久性(durability):一旦事务被提交,它对数据库的改变就是永久性的,即使在系统故障或崩溃后也能够保持。
数据库的事务隔离级别有哪些?
事务隔离级别是为了解决并发事务对同一个数据进行读写操作,所产生的一系列线程不安全的问题。
读未提交(Read Uncommitted):
- 允许一个事务读取另一个事务尚未提交的数据修改。
- 最低的隔离级别,存在脏读、不可重复读和幻读的问题。
读已提交(Read Committed):oracle默认事务隔离级别
- 一个事务只能读取已经提交的数据或数据修改。
- 解决了脏读问题,但仍可能出现不可重复读和幻读。
可重复读(Repeatable Read):mysql默认事务隔离级别
- 事务执行期间,多次读取同一数据会得到相同的结果,即在事务开始和结束之间,其他事务对数据的修改不可见。
- 解决了不可重复读问题,但仍可能出现幻读。
序列化(Serializable):
- 最高的隔离级别,确保事务之间的并发执行效果与串行执行的效果相同,即不会出现脏读、不可重复读和幻读。
1
2
3
4
5
6
7
8-- 查看当前事务的隔离级别
select @@global.tx_isolation;
-- 设置事务的隔离级别
set session transaction isolation level repeatable read;
-- 脏读,看到其它事务还没有提交的数据,其它事务可能产生回滚。
-- 幻读,其它事务插入相同的内容,该事务先运行查询没查到,插入时因为唯一性索引而报已经存在;(可重复读)插入时因为其它事务也在插入但尚未提交,而陷入到忙等待(读已提交)。
-- 重复读,事务内相同的查询语句多次查询查询到的是相同的内容。
索引失效的场景
索引失效意味着查询操作不能有效利用索引进行数据检索,从而导致性能下降,下面一些场景会发生索引失效。
联合索引:
- 不遵循最左前缀法则:最左前缀法则指的是查询从联合索引的最左列开始,并且不跳过联合索引中的列。
- 使用非等值查询:当使用
!=
或<>
等范围索引号时,索引可能不会被使用。
B+树结构
- 使用LIKE语句:以通配符
%
开头的LIKE查询会导致索引失效(加在前面会导致失效,加在后面不会)。
其它
- 函数或表达式:使用函数或表达式作为查询条件(运算操作),通常会导致索引失效。
- 使用OR条件:当使用OR连接多个条件,并且每个条件用到不同的索引列时,索引可能不会被使用。
- 对列进行类型转换: 如果在查询中对列进行类型转换,例如将字符列转换为数字或日期(不加引号),索引可能会失效。
undo log、redo log、binlog 有什么用?
undo log
是Innodb
存储引擎层生成的日志,实现了事务中的原子性,主要用于事务回滚和MVCC
(多版本并发控制)。 (解决四大特性中的原子性)redo log
也是Innodb
存储引擎层生成物理日志,实现了事务中的持久性,记录了某个数据页做了什么修改,每当执行一个事务就会产生一条或者多条物理日志。(解决四大特性中的持久性)binlog
(归档日志)是Server
层生成的日志,主要用于数据备份和主从复制。
较详细介绍
MySQL是一款流行的关系型数据库,其日志是其关键功能之一。MySQL包括三种类型的日志,分别是binlog,redolog和undolog,它们分别有不同的作用和特点。
- binlog , binlog (Binary log)是 MySQL 中的二进制日志文件,用于记录 MySQL 服务器上的所有更新和修改操作。它可以记录所有的DDL (Data Definition Language)和DML (Data Modification Language)操作,包括对表结构的更改、数据的插入、修改、删除等等。binlog是在事务提交后生成的,因此可以用于恢复数据库。
- redolog, redolog (Redo log)用于恢复数据,保证数据的一致性和持久性。当MySQL发生修改时, redolog会将这些操作记录下来,并写入磁盘。这样,当MySQL发生宕机或崩溃时,通过重放redolog就可以恢复数据。
- undolog, undolog (Undo log)用于回滚操作。当MySQL发生事务回滚时, undolog会记录这些操作并将其写入磁盘。这样,当MySQL 需要回滚时,通过重放 undolog 就可以回滚事务。
区别:
- binlog 和 redolog 都是 MySQL 中的二进制日志,但是它们的作用和实现方式有所不同。binlog 是 MySQL 记录所有的操作,而redolog则是用于保证数据的一致性和持久性。此外, binlog是逻辑日志,redolog是物理日志。binlog记录的是SQL语句,而redolog记录的是数据页的修改,所以binlog可以跨平台使用,而redolog不能。undolog和redolog的区别是, undolog是用于回滚操作的,而redolog是用于恢复数据的。
MySQL的执行引擎有哪些?
MySQL的执行引擎主要负责查询和存储数据, 其执行引擎主要有MyISAM
、InnoDB
、Memory
等。
MyISAM
引擎是早期的默认存储引擎,支持全文索引,但是不支持事务,也不支持行级锁和外键约束,适用于快速读取且数据量不大的场景。InnoDB
引擎提供了对事务ACID的支持,还提供了行级锁和外键的约束,是目前MySQL的默认存储引擎,适用于需要事务和高并发的应用。Memory
就是将数据放在内存中,访问速度快,但数据在数据库服务器重启后会丢失
非聚簇索引
将数据
和索引
分开存储,表数据存储顺序与索引顺序无关

聚簇索引
在主索引数外还存在根据主键id或者其它唯一索引的辅助索引树
;
每次查询先从辅助索引树
中获取主键id或者其它唯一索引,据此再从主索引树中获取全部数据,所以只查id的效率最高。

行级锁、表级锁、意向锁
行级锁
仅对特定的行加锁,允许其他事务并发访问不同的行,适用于高并发场景。
表级锁
对整个表加锁,其他事务无法对该表进行任何读写操作,适用于需要保证完整性的小型表。
意向锁
意向锁是一种 MySQL数据库中的锁,用于表级锁协调多个行级锁的使用。在表级锁定一个表之前,MySQL 需要先获得一个意向锁。以表明要获取的锁类型(读锁或写锁),避免其他事务锁定整个表或锁定一部分表时引发死锁。意向锁是一种轻量级锁,它不会影响其他事务的读操作,只有在某个事务要对表进行写操作时才会加上意向锁,而其他事务在读取表时只需要获取读锁,不需要等待意向锁的释放。意向锁可以提高数据库并发性能,防止死锁的发生。它是表级锁,而不是行级锁。
MVCC
MVCC (Multi-Version Concurrency Control,多版本并发控制)是一种并发控制机制,允许多个事务同时读取和写入数据库,而无需互相等待,从而提高数据库的并发性能。
在MVCC中,数据库为每个事务创建一个数据快照。每当数据被修改时,MySQL不会立即覆盖原有数据,而是生成新版本的记录。每个记录都保留了对应的版本号或时间戳。多版本之间串联起来就形成了一条版本链,这样不同时刻启动的事务可以无锁地获得不同版本的数据(普通读)。此时读(普通读)写操作不会阻塞。写操作可以继续写,无非就是会创建新的数据版本(但只有在事务提交后,新版本才会对其他事务可见。未提交的事务修改不会影响其他事务的读取) ,历史版本记录可供已经启动的事务读取。
如何设计mysql表
设计表的时候,在满足业务需求的情况下,需要额外考虑表结构的高效性、扩展性以及维护性。
选择合适的数据类型:为字段选择合适的数据类型可以有效减少存储空间,并提高查询效率。例如:
- 整数型使用
INT
而不是BIGINT
,前提是如果数据不会超出 INT 范围。 - 浮点型使用
FLOAT
而不是DOUBLE
。 - 字符型使用
VARCHAR
而不是TEXT
,如果字段长度比较短且可变。 - 使用
DATE
、DATETIME
或TIMESTAMP
而不是VARCHAR
来存储日期时间信息。
- 整数型使用
主键与唯一约束主键是表的唯一标识符,每个表应该有一个主键。如果需要确保某些字段唯一性,可以使用 唯一约束(
UNIQUE
)。索引的设计:索引是提高查询性能的关键。但是设计时应避免过多索引,以免对写操作造成负担。对于经常查询的字段(如
WHERE
、JOIN
、ORDER BY
中使用的字段)应该创建索引。考虑 复合索引,可以将多个列组成一个索引,优化复合查询性能。避免在低基数列(如布尔值、性别字段)上创建索引,因为它们不会带来太大的查询性能提升。表的范式化(
Normalization
) :规范化(通常遵循到3NF
)有助于消除数据冗余,提高数据一致性,避免数据更新异常。但在某些场景下,可以选择一定的 反规范化 来提高查询性能。例如:冗余一些常用字段,避免关联表查询,提升性能。预留一些扩展字段,例如extendld
等,便于后续扩展。部分格式不可控字段可以设计为 json 格式,防止频繁变更表结构。
mysql批处理方案
批处理方案:
- 普通for循环逐条插入,速度极差,不推荐
- MP的批量新增,基于预编译的批处理,性能不错
- 配置jdbc参数,开rewriteBatchedStatements,性能最好
Redis
redis简介
remote dictionary server 远程词典服务器

高性能
假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存储在缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快
高并发
直接操作缓存能够承受的请求是远远大于直接访问数据库的
Redis的数据类型有哪些

Redis 常见的五种数据类型:**String(字符串),List(列表),Hash(哈希表)、Set(集合)及 Zset(sorted set:有序集合)**。
- 字符串
STRING
:存储字符串数据,最基本的数据类型。 - 列表
LIST
:存储有序的字符串元素列表。 - 哈希表
HASH
:存储字段和值的映射,用于存储对象。 - 集合
SET
:存储唯一的字符串元素,无序。 - 有序集合
ZSET
:类似于集合,但每个元素都关联一个分数,可以按分数进行排序。
Redis版本更新,又增加了几种数据类型,
BitMap
: 存储位字节的数据结构,可以用于处理一些位运算操作。GEO
: 存储地理位置信息的数据结构。Stream
:专门为消息队列设计的数据结构。HyperLog
:用于基数估算的数据结构,用于统计元素的唯一数量。
Redis是单线程的还是多线程的,为什么?
这个问题是随着redis版本的变化而变化的,redis5之前是客户端请求单线程以及I/O多路复用。这样的设计选择有几个关键原因:
- 单线程操作省去了上下文切换带来的开销,同时不存在资源竞争,避免了死锁现象的发生。
- I/O多路复用机制同时监听多个Socket(连接),根据Socket上的事件来选择对应的事件处理器进行处理。
Redis的单线程模型并不意味着它在处理客户端请求时不高效。实际上,由于其操作主要在内存中进行,Redis可以满足高性能和低延迟。
此外,Redis 6.0
将客户端请求部分设置为多线程,充分利用CPU资源,减少网络I/O阻塞带来的性能损耗。

说一说Redis持久化机制有哪些
Redis持久化机制是Redis中的数据由内存存储到磁盘中,避免因断电或意外而产生数据丢失。
- RDB 快照:在指定的时间间隔,将redis中的内存数据,以二进制的方式写入磁盘;
- AOF 日志:每执行一条写操作命令,就把该命令以追加的方式写入到一个文件里;上电后按序读取文件中的内容。
- 混合持久化方式:Redis 4.0 新增的方式,集成了 RBD 和 AOF 的优点;
介绍一下Redis缓存雪崩和缓存穿透,如何解决这些问题?
- 缓存雪崩是指在某个时间点,大量缓存同时失效,导致请求直接访问底层存储系统,增加了系统负载。
对于缓存雪崩,可以通过合理设置缓存的过期时间,分散缓存失效时间点;或者采用永不过期的策略,再结合监听业务变化,定期更新缓存。
- 缓存击穿是有大量并发请求访问这个缓存中不存在的数据,导致这些请求直接访问到底层存储系统,增加了系统负载。
对于缓存击穿,可以采用互斥锁(例如分布式锁),只允许一个请求从数据库中取数据,数据缓存到redis后把互斥锁释放掉。
- 缓存穿透是指查询一个在缓存和数据库都不存在的数据,这个数据始终无法被缓存到,导致每次请求都直接访问数据库,增加数据库的负载。典型的情况是攻击者可能通过构造不存在的 key 大量访问缓存,导致对数据库的频繁查询,增加了系统负载。
对于缓存穿透,可以采用布隆过滤器等手段来过滤掉恶意请求,或者在查询数据库前先进行参数的合法性校验。
Redis有什么优缺点?为什么用Redis查询会比较快
(1) Redis有什么优缺点?
优点:
Redis
是一个基于内存的键值存储数据库,读写速度非常快,通常可被用作缓存、消息队列、分布式锁。- 它支持多种数据结构,如字符串、列表、哈希表、集合、有序集合等。
- Redis 还提供了分布式特性,可以将数据分布在多个节点上,以提高可扩展性和可用性。
缺点:
- 但是
Redis
使用物理内存,相比磁盘存储成本更高,不适合存储超大量数据。
(2)为什么Redis查询快
- 基于内存操作: 相比于传统的磁盘文件操作减少了IO,提高了操作的速度。
- 高效的数据结构:Redis专门设计了字符串、列表、哈希表、集合、有序集合等高效的数据结构,可以根据实际需要选择对应的数据结构。
- 客户端请求单线程以及I/O多路复用:
- 单线程操作省去了上下文切换带来的开销,同时不存在资源竞争,避免了死锁现象的发生。
- I/O多路复用机制同时监听多个Socket(连接),根据Socket上的事件来选择对应的事件处理器进行处理。
如何保证数据库和缓存的一致性
缓存双删策略
- 更新数据库之前,删除一次缓存;更新完数据库后,再进行一次延迟删除。
- 这个方案是为了避免旧数据被回种,才再等待一段时间后再延迟删除缓存。可以使用消息队列、定时任务或者延迟任务等方式去实现延迟删除:

Binlog异步更新
- 先修改数据库,然后通过Canal监听数据库的binlog日志,记录数据库的修改信息,接着通过消息队列异步修改缓存的数据。这里需要注意保证缓存中数据按顺序更新,然后再加上重试机制,避免因为网络问题导致更新失败。

分布式锁
分布式锁是在分布式系统中用于控制对共享资源或临界区的访问的一种锁机制。它可以确保在多个节点或实例上同一时间只有一个进程能够获取锁,从而保证数据的一致性和避免并发访问下的数据竞争和冲突。
注意事项如下:
- 需要合理设置超时释放时间,服务器崩溃也可以释放锁,避免造成死锁。
- 业务处理完毕后及时释放锁,防止业务阻塞。
- 必须由当前线程释放锁,不能释放其他线程加的锁,防止业务执行受到影响。
- 合理设置抢锁等待时间,避免长时间无效等待。
- 可以利用看门狗机制实现锁的续期,防止由于业务处理时间大于锁超时释放时间,导致一把锁被多个线程拥有,从而出现错误。
消息队列
消息队列(Message Queue)是一种异步通信机制,用于在分布式系统中解耦发送方和接收方之间的通信。它通过在消息生产者和消费者之间引入一个中间缓冲区(常见为 broker) ,将消息存储在 broker 中,然后由消费者从 broker 中读取和处理消息。
常见用途:
- 解耦:生产者和消费者无需同时在线,生产者可以发送消息后立即返回,而消费者在合适的时机处理消息。
- 削峰填谷:在高并发场景下,消息队列可以暂存大量请求,平滑高峰流量,避免系统过载。
- 异步处理:可以将不需要立即处理的任务放入消息队列中异步执行,减少用户请求的响应时间。
常见的消息队列实现:
RabbitMQ:
- RabbitMQ 是基于 AMQP 协议的消息队列,适合复杂路由和多种消息模式的场景。
- 具有强大的消息路由能力,包括Direct、Fanout、 Topic 等多种交换机类型。
- 支持消息的持久化、确认机制和死信队列,确保消息的可靠传输。
Kafka:
- Kafka是一种高吞吐量、分布式的消息队列,适用于实时数据流和大数据分析的场景。
- Kafka 的数据是以分区(Partition)为单位存储,支持水平扩展。
- Kafka 的消费者可以以消费组的方式消费消息,实现消息的多次消费和负载均衡。
RocketMQ:
- RocketMQ是阿里巴巴开源的一款消息队列,具有高吞吐、低延迟的特点,适用于企业级应用场景。
- 支持事务消息、延时消息和顺序消息,适用于金融支付、交易系统等对数据一致性要求较高的场景。