数据库面试题


Redis

​什么是 Redis?
​定位:开源的内存数据结构存储系统,支持多种数据类型(字符串、哈希、列表、集合、有序集合)。
​特点:高性能(单线程模型)、支持持久化、原子操作、丰富的数据结构。
​应用场景:缓存、消息队列、实时排行榜、分布式锁、计数器等

Redis 支持哪些数据类型

字符串(String)​:最基本类型,最大 512MB。

SET key value
GET key

​哈希(Hash)​:键值对的集合,适合存储对象。

HSET user:1 name "Alice" age 25
HGETALL user:1

​列表(List)​:双端链表,支持 push/pop 操作。

LPUSH list 1 2 3
LRANGE list 0 -1

​集合(Set)​:无序不重复元素集合,支持交集、并集等操作。

SADD set 1 2 3
SMEMBERS set

​有序集合(Sorted Set)​:元素按分数排序,适合排行榜。

ZADD scoreboard 90 "Alice" 85 "Bob"
ZRANGE scoreboard 0 -1 WITHSCORES

如何用 Redis 实现分布式锁

RedLock 算法:基于多个独立 Redis 实例的分布式锁。
​步骤:
获取当前时间戳。
依次向多个 Redis 实例尝试获取锁(使用 SETNX)。
计算获取锁的总耗时,若小于锁的有效时间(如 10 秒),则认为获取成功。
释放锁时,需向所有实例发送释放命令。
Java + Redisson

RLock lock = redissonClient.getLock("myLock");
boolean isLocked = lock.tryLock(10, TimeUnit.SECONDS);
if (isLocked) {
    try {
        // 业务逻辑
    } finally {
        lock.unlock();
    }
}

持久化机制

RDB 和 AOF 的区别?
| ​对比维度 |​RDB(Redis Database)​ |​AOF(Append-Only File)​|
| ​原理| 定期生成数据快照(fork 子进程写入磁盘) |记录每个写操作日志(append 模式追加)|
| ​文件大小| 较小(仅保存某个时间点的数据) |较大(所有写操作日志累积)|
| ​恢复速度 |快(直接加载快照)| 慢(需重放所有日志)|
| ​数据完整性 |可能丢失最后一次快照后的数据 |最多丢失 1 秒内的数据(取决于刷盘策略)|
| ​适用场景 |全量备份、灾难恢复 |实时持久化、数据零丢失|
配置 AOF 持久化:在配置文件redis.conf中配置

事务与并发控制

Redis 事务如何保证原子性?

​MULTI/EXEC:将多个命令打包成一个事务,要么全部执行,要么全部失败。

MULTI
SET key1 value1
SET key2 value2
EXEC

​WATCH 命令:监控某个键,若在事务执行前被修改,则事务取消。

WATCH key
MULTI
...  # 若 key 被修改,事务不会执行
EXEC

Redis 是否支持回滚?

​不支持传统回滚:Redis 事务不提供 ROLLBACK,若某条命令失败,后续命令仍会执行。
​替代方案:通过乐观锁(WATCH)或 Lua 脚本实现原子性操作

常见问题与陷阱

​为什么 Redis 是单线程的?
​原因:避免多线程竞争带来的锁开销,简化内存操作。
​性能瓶颈:受限于 CPU 单核性能,可通过集群横向扩展。

​如何避免缓存穿透?
​缓存空值:对不存在的 Key 也缓存空值,设置较短过期时间。
使用布隆过滤器(Bloom Filter)拦截非法 Key。
​如何解决缓存雪崩?
​原因:大量缓存 Key 同时失效,导致数据库压力骤增。
​解决方案:
设置随机过期时间。
使用二级缓存(本地缓存 + Redis)。
限流降级(如 Sentinel)。

MongoDB

​什么是 MongoDB?
​定位:面向文档的 NoSQL 数据库,以 BSON(二进制 JSON)格式存储数据,支持动态模式(Schema-less)。
​特点:
灵活的数据模型(文档、数组、嵌套结构)。
高可扩展性(分片集群支持水平扩展)。
支持复杂查询和聚合操作。
​应用场景:内容管理系统、实时日志分析、物联网数据存储、用户行为分析等

​常用查询操作符

​比较操作符:$eq(等于)、$gt(大于)、$in(在列表中)。
​逻辑操作符:$and、$or、$not。
​正则表达式:{ name: { $regex: /^A/ } }(匹配以 A 开头的名称)。
​范围查询:{ date: { $gte: ISODate(“2023-01-01”) } }

MySQL

  1. 查询中不使用select * 的原因
    增加查询分析器解析成本。

增减字段容易与 resultMap 配置不一致。
不需要的列会增加数据传输时间和网络开销
对于无用的大字段,如 varchar、blob、text,会增加 io 操作
失去MySQL优化器“覆盖索引”策略优化的可能性
2. 主键ID生成方案
主键id的生成方案
数据库中主键id的生成方案,主要有三种
数据库自增ID
采用随机数生成不重复的ID
采用jdk提供的uuid

主键id性能排列
自增ID > 雪花算法生成的ID >uuid生成的ID。

实际使用
在实际使用过程中,推荐使用主键自增ID和雪花算法生成的随机ID。

  1. 联合索引的优势
    减少开销
    覆盖索引
    效率高
  2. 索引是建的越多越好吗
    数据量小的表不需要建立索引,建立会增加额外的索引开销

不经常引用的列不要建立索引,因为不常用,即使建立了索引也没有多大意义

经常频繁更新的列不要建立索引,因为肯定会影响插入或更新的效率

数据重复且分布平均的字段,因此他建立索引就没有太大的效果(例如性别字段,只有男女,不适合建立索引)

数据变更需要维护索引,意味着索引越多维护成本越高。

  1. 什么情况下考虑分库分表?
    不管是IO瓶颈还是CPU瓶颈,最终都会导致数据库的活跃连接数增加,进而逼近甚至达到数据库可承载的活跃连接数的阈值。在业务service来看, 就是可用数据库连接少甚至无连接可用,接下来就可以想象了(并发量、吞吐量、崩溃)。
    IO瓶颈
    第一种:磁盘读IO瓶颈,热点数据太多,数据库缓存放不下,每次查询会产生大量的IO,降低查询速度->分库和垂直分表
    第二种:网络IO瓶颈,请求的数据太多,网络带宽不够 ->分库
    CPU瓶颈
    第一种:SQl问题:如SQL中包含join,group by, order by,非索引字段条件查询等,增加CPU运算的操作->SQL优化,建立合适的索引,在业务Service层进行业务计算。
    第二种:单表数据量太大,查询时扫描的行太多,SQl效率低,增加CPU运算的操作。->水平分表。
    能不分就不分
    数据量过大,正常运维影响业务访问
    随着业务发展,需要对某些字段垂直拆分
    数据量快速增长

  2. 分库分表带来的问题
    事务一致性问题
    分布式事务
    当更新内容同时存在于不同库找那个,不可避免会带来跨库事务问题。跨分片事务也是分布式事务,没有简单的方案,一般可使用“XA协议”和“两阶段提交”处理。分布式事务能最大限度保证了数据库操作的原子性。但在提交事务时需要协调多个节点,推后了提交事务的时间点,延长了事务的执行时间,导致事务在访问共享资源时发生冲突或死锁的概率增高。随着数据库节点的增多,这种趋势会越来越严重,从而成为系统在数据库层面上水平扩展的枷锁。
    最终一致性
    对于那些性能要求很高,但对一致性要求不高的系统,往往不苛求系统的实时一致性,只要在允许的时间段内达到最终一致性即可,可采用事务补偿的方式。与事务在执行中发生错误立刻回滚的方式不同,事务补偿是一种事后检查补救的措施,一些常见的实现方法有:对数据进行对账检查,基于日志进行对比,定期同标准数据来源进行同步等。
    跨节点关联查询join问题
    切分之前,系统中很多列表和详情表的数据可以通过join来完成,但是切分之后,数据可能分布在不同的节点上,此时join带来的问题就比较麻烦了,考虑到性能,尽量避免使用Join查询。解决的一些方法:
    全局表
    全局表,也可看做“数据字典表”,就是系统中所有模块都可能依赖的一些表,为了避免库join查询,可以将这类表在每个数据库中都保存一份。这些数据通常很少修改,所以不必担心一致性的问题。
    字段冗余
    一种典型的反范式设计,利用空间换时间,为了性能而避免join查询。例如,订单表在保存userId的时候,也将userName也冗余的保存一份,这样查询订单详情顺表就可以查到用户名userName,就不用查询买家user表了。但这种方法适用场景也有限,比较适用依赖字段比较少的情况,而冗余字段的一致性也较难保证。
    数据组装
    在系统service业务层面,分两次查询,第一次查询的结果集找出关联的数据id,然后根据id发起器二次请求得到关联数据,最后将获得的结果进行字段组装。这是比较常用的方法。
    ER分片
    关系型数据库中,如果已经确定了表之间的关联关系(如订单表和订单详情表),并且将那些存在关联关系的表记录存放在同一个分片上,那么就能较好地避免跨分片join的问题,可以在一个分片内进行join。在1:1或1:n的情况下,通常按照主表的ID进行主键切分。
    跨节点分页、排序、函数问题
    跨节点多库进行查询时,会出现limit分页、order by 排序等问题。分页需要按照指定字段进行排序,当排序字段就是分页字段时,通过分片规则就比较容易定位到指定的分片;当排序字段非分片字段时,就变得比较复杂.需要先在不同的分片节点中将数据进行排序并返回,然后将不同分片返回的结果集进行汇总和再次排序,最终返回给用户 

  3. delete和truncate的区别

  4. delete,drop,truncate 都有删除表的作用,区别在于:

  5. delete 和 truncate 仅仅删除表数据,drop 连表数据和表结构一起删除,打个比方,delete 是单杀,truncate 是团灭,drop 是把电脑摔了。

  6. delete 是 DML 语句,操作完以后如果没有不想提交事务还可以回滚,truncate 和 drop 是 DDL 语句,操作完马上生效,不能回滚,打个比方,delete 是发微信说分手,后悔还可以撤回,truncate 和 drop 是直接扇耳光说滚,不能反悔。

  7. 执行的速度上,drop>truncate>delete,打个比方,drop 是神舟火箭,truncate 是和谐号动车,delete 是自行车。

where和having的区别?(!!)
where是对分组前的条件进行限定。having是对分组后的内容进行限定。
where后面不能加聚合函数,having后可以跟聚合函数。


文章作者: zrh
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 zrh !
  目录