ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

史上最全的数据库面试题,不看后悔篇!

史上最全的数据库面试题,不看后悔篇!

一、引言:为什么数据库是面试的必考点

无论是校招还是社招,无论是后端开发、大数据还是运维岗位,数据库几乎都是面试中绕不开的一环。面试官考察的不只是「会写 SQL」,更重要的是你对存储原理、索引结构、事务特性、并发控制、性能优化等底层机制的掌握程度。本文系统整理了从基础概念到高阶优化、从 MySQL 到 Redis 的高频面试题,配以原理讲解、代码示例和对比表格,帮助你在面试前完成一次体系化的复习。

提示:本文约 2 万字,建议按目录顺序通读一遍,再针对薄弱环节重点复习。每个小节都给出了「考点分析」和「标准回答思路」,可以直接作为答题模板。

二、数据库基础概念高频题

2.1 什么是数据库、数据库管理系统和数据库系统

数据库(Database,DB)是指长期存储在计算机内、有组织、可共享的数据集合。数据库管理系统(Database Management System,DBMS)是位于用户与操作系统之间的一层数据管理软件,负责数据的组织、存储、维护和检索,例如 MySQL、Oracle、PostgreSQL。数据库系统(DBS)则是由数据库、DBMS、应用程序、数据库管理员(DBA)和用户共同构成的完整系统。

可以把三者的关系类比为:数据库是「仓库里的货物」,DBMS 是「仓库管理系统」,数据库系统是「整个仓储运营体系」。

2.2 关系型数据库和非关系型数据库的区别

对比维度关系型数据库(RDBMS)非关系型数据库(NoSQL)
数据模型二维表,行列结构,模式固定键值、文档、列族、图等多种模型
代表产品MySQL、Oracle、SQL Server、PostgreSQLRedis、MongoDB、HBase、Cassandra、Neo4j
事务支持ACID 强事务多数只提供弱一致性或 BASE
扩展方式以纵向扩展为主,分库分表复杂天然适合横向扩展
查询语言标准 SQL各产品自有 API,如 Redis 命令、Mongo 查询
适用场景金融、订单、ERP 等强一致场景缓存、海量日志、社交关系、高并发读

回答要点:不要简单说「SQL 和 NoSQL 谁更好」,而要强调场景匹配。关系型擅长复杂事务和多表关联;NoSQL 擅长高并发、海量数据和灵活结构。实践中常见的是「MySQL 做主存储 + Redis 做缓存」的混合架构。

2.3 什么是三范式?为什么有时要反范式设计

第一范式(1NF):字段不可再分,保证原子性。第二范式(2NF):在满足 1NF 基础上,消除非主属性对主键的部分函数依赖。第三范式(3NF):在满足 2NF 基础上,消除非主属性对主键的传递函数依赖。

范式设计的目的是减少数据冗余、避免更新异常。但在实际互联网业务中,为了减少多表 Join、提升查询性能,常常会进行反范式设计,例如在订单表中冗余用户昵称、在统计表中预聚合数据。本质是用「冗余换性能」,需要在一致性和查询效率之间权衡。

2.4 主键、外键、唯一键和索引的区别

  • 主键(Primary Key):唯一标识一行记录,非空且唯一,一张表只能有一个。
  • 外键(Foreign Key):用于建立表与表之间的引用关系,保证引用完整性。
  • 唯一键(Unique Key):保证列值唯一,但允许有多个 NULL(不同数据库对 NULL 的处理略有差异)。
  • 索引(Index):为加速查询而建立的数据结构,可以有多个,不要求唯一。

注意:主键和唯一键都会自动创建唯一索引,但索引是一种存储结构,主键/唯一键是一种约束,二者概念要区分清楚。

2.5 什么是视图、存储过程和触发器

视图(View)是一条被保存的查询语句,本身不存储数据,可简化复杂查询、隔离底层表结构、控制数据访问权限。存储过程(Stored Procedure)是预编译的一组 SQL 语句的集合,可减少网络传输、提升执行效率,但可移植性较差。触发器(Trigger)是在特定表上发生 INSERT、UPDATE、DELETE 时自动执行的程序,多用于审计、日志同步和约束增强,但过多触发器会降低 DML 性能、增加维护成本。

三、SQL 语法与查询高频题

3.1 SQL 的执行顺序是什么

这是非常高频的面试题。SQL 的关键字书写顺序与执行顺序并不一致,标准执行顺序如下:

  1. FROM:确定数据来源,执行笛卡尔积和关联。
  2. ON:执行连接条件过滤。
  3. JOIN:添加外部行(外连接时)。
  4. WHERE:对行进行条件过滤。
  5. GROUP BY:分组。
  6. HAVING:对分组结果过滤。
  7. SELECT:选取需要的列。
  8. DISTINCT:去重。
  9. ORDER BY:排序。
  10. LIMIT:限制返回行数。

理解了执行顺序,很多问题就迎刃而解:比如WHERE 不能使用 SELECT 里的别名(因为 WHERE 先于 SELECT 执行),而ORDER BY 可以使用别名(它执行得更晚)。

3.2 WHERE 和 HAVING 的区别

对比项WHEREHAVING
作用对象作用于行(分组前的原始数据)作用于组(分组后的聚合结果)
执行阶段GROUP BY 之前GROUP BY 之后
能否使用聚合函数不能能,如 COUNT、SUM
典型用途初步行过滤,减少数据量对分组结果的二次过滤

3.3 各种 JOIN 的区别

以 A、B 两张表为例:

  • INNER JOIN:只返回两表匹配的行。
  • LEFT JOIN:返回左表全部行,右表无匹配则为 NULL。
  • RIGHT JOIN:返回右表全部行,左表无匹配则为 NULL。
  • FULL OUTER JOIN:返回两表所有行,无匹配的一侧补 NULL(MySQL 不直接支持,可用 UNION 模拟)。
  • CROSS JOIN:笛卡尔积,返回两表所有组合。
-- 查询所有用户及其订单(无订单的用户也显示) SELECT u.id, u.name, o.order_no FROM user u LEFT JOIN orders o ON u.id = o.user_id; -- 查询没有下过单的用户 SELECT u.* FROM user u LEFT JOIN orders o ON u.id = o.user_id WHERE o.id IS NULL;

3.4 UNION 和 UNION ALL 的区别

UNION会对结果集去重并排序;UNION ALL不去重、不排序,直接把两个结果集拼接,性能更高。如果能确认结果没有重复,应优先使用 UNION ALL。另外 UNION 要求两个查询的列数相同、对应列类型兼容。

3.5 什么是子查询?相关子查询和非相关子查询的区别

子查询是嵌套在另一个查询中的 SELECT。按是否依赖外层查询可分为:

  • 非相关子查询:内层查询独立执行一次,结果供外层使用。例如SELECT * FROM t WHERE id IN (SELECT id FROM t2 WHERE status=1)。
  • 相关子查询:内层查询引用外层查询的列,外层每处理一行,内层都要重新执行一次。例如 EXISTS 关联查询。

相关子查询可能带来性能问题,实际开发中常改写为 JOIN 来优化。

3.6 COUNT(1)、COUNT(*) 和 COUNT(列名) 的区别

COUNT(*)和COUNT(1)都是统计所有行的数量,在 MySQL 的 InnoDB 引擎中二者性能基本没有差别。COUNT(列名)只统计该列非 NULL的值的个数。若要判断「某列有多少个非空值」,只能用 COUNT(列名)。

3.7 什么是窗口函数?常见的窗口函数有哪些

窗口函数(Window Function)可以在不改变结果集行数的情况下,对某一「窗口」内的行进行聚合计算,从而同时保留明细和汇总信息。相比 GROUP BY,它不会把多行压缩成一行,非常适合做排名、移动平均、累计求和等分析。

-- 按部门对员工薪资排名 SELECT name, department, salary, ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rn, RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS rk, DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS dr FROM employee;

常见窗口函数:ROW_NUMBER(不重复编号)、RANK(并列跳号)、DENSE_RANK(并列不跳号)、SUM/AVG/COUNT(聚合)、LEAD/LAG(前后行)等。关键语法是OVER (PARTITION BY ... ORDER BY ...)。

3.8 TRUNCATE、DELETE 和 DROP 的区别

操作类型是否可回滚能否带 WHERE自增值速度
DELETEDML可回滚(事务内)能不重置慢,逐行删
TRUNCATEDDL不可回滚不能重置快,整表释放
DROPDDL不可回滚不能-最快,删除表结构

TRUNCATE 本质是删除整张表再重建结构,所以速度很快且不可回滚;DELETE 是逐行删除,属于 DML,可以在事务中回滚。

四、索引原理与优化高频题

4.1 什么是索引?为什么能提升查询效率

索引是一种用于快速查找的数据结构,相当于书的目录。没有索引时,数据库需要全表扫描;建立索引后,可以通过索引快速定位到目标数据,把时间复杂度从 O(n) 降低到接近 O(log n)。

索引虽然加快查询,但也有代价:占用额外磁盘空间、降低写入性能(每次 INSERT/UPDATE/DELETE 都要同步维护索引)。因此索引并非越多越好。

4.2 B 树和 B+ 树的区别?为什么 MySQL 用 B+ 树

B 树的每个节点既存储键也存储数据,查找可能在非叶子节点就命中;B+ 树的所有数据都存储在叶子节点,非叶子节点只存键和指针,并且叶子节点之间通过双向链表连接,形成一个有序链表。

MySQL 的 InnoDB 选择 B+ 树,核心原因有三个:

  1. 更矮更胖:非叶子节点不存数据,单个节点能容纳更多键,树高更低,磁盘 IO 次数更少。
  2. 范围查询友好:叶子节点有序且链表相连,范围扫描、排序非常高效。
  3. 查询性能稳定:任何查找都必须到叶子节点,查询路径长度一致。

4.3 聚簇索引和非聚簇索引的区别

对比项聚簇索引(聚集索引)非聚簇索引(二级索引)
数据存储叶子节点直接存整行数据叶子节点存索引键 + 主键值
数量一张表只能有一个一张表可以有多个
默认选择InnoDB 默认用主键除主键外的其他索引
查找流程找到叶子即拿到数据找到主键后还需回表

在 InnoDB 中,如果没有显式定义主键,会优先选择第一个非空唯一索引;若都没有,则生成隐藏的 row_id 作为聚簇索引键。二级索引查找时,先定位到主键,再通过主键回表查询整行,这就是回表。

4.4 什么是回表?如何避免回表

回表是指通过二级索引查到主键后,还需要回到聚簇索引中查询完整行数据的过程。回表会带来额外的磁盘 IO,可以用覆盖索引(Covering Index)来避免:即查询所需的所有列恰好都在索引中,引擎直接返回结果,无需回表。

-- 假设存在联合索引 idx(user_id, status, create_time) -- 这条 SQL 需要回表,因为查询了索引外的列 total_amount SELECT user_id, status, create_time, total_amount FROM orders WHERE user_id = 123; -- 只查索引中的列,触发覆盖索引,不用回表 SELECT user_id, status, create_time FROM orders WHERE user_id = 123;

4.5 最左前缀原则是什么

对于联合索引idx(a, b, c),其索引结构是按 a、b、c 的顺序依次排序的。查询时只有从最左边列开始、且中间不能跳过,才能命中索引:

  • 能命中:WHERE a=1、WHERE a=1 AND b=2、WHERE a=1 AND b=2 AND c=3。
  • 部分命中:WHERE a=1 AND c=3(只能用 a,c 无法利用)。
  • 无法命中:WHERE b=2、WHERE c=3(未从最左边开始)。

因此设计联合索引时,应把区分度高、查询频繁且常在等值条件中的列放在前面。

4.6 索引失效的常见场景

  • 对索引列使用函数或运算:WHERE DATE(create_time) = '2026-10-07'会导致索引失效,应改写为范围条件。
  • 隐式类型转换:字符串列与数字比较,如WHERE phone = 13800138000,会被隐式转换导致索引失效,应写成WHERE phone = '13800138000'。
  • 前导模糊查询:LIKE '%keyword'无法命中索引,LIKE 'keyword%'才能正常使用索引。
  • 违反最左前缀原则:联合索引查询时跳过最左边列,无法整体命中索引。
  • 使用负向条件:NOT IN、!=、<>通常导致索引失效。
  • 使用 OR 连接非索引列:OR 两侧只要有一列未建索引,就可能退化为全表扫描。
  • 对索引列判断 NULL:IS NULL或IS NOT NULL在某些场景下会影响索引使用。

需要说明的是,索引失效并非绝对,最终是否走索引取决于优化器的成本估算。排查时应结合EXPLAIN观察type、key和rows,不要只凭经验判断。

五、事务与锁高频题

5.1 什么是事务?ACID 特性分别是什么

事务是一组不可分割的数据库操作集合,要么全部成功提交,要么全部回滚。事务的 ACID 特性包括:

  • 原子性(Atomicity):事务内的操作要么全部成功,要么全部失败,依靠 Undo Log 实现。
  • 一致性(Consistency):事务执行前后,数据都处于合法的约束状态,由数据库约束、事务机制共同保证。
  • 隔离性(Isolation):多个事务并发执行时互不干扰,依靠锁和 MVCC 实现。
  • 持久性(Durability):事务一旦提交,修改会永久保存,依靠 Redo Log 实现。

5.2 MySQL 的事务隔离级别有哪些?分别解决什么问题

隔离级别脏读不可重复读幻读
读未提交(Read Uncommitted)可能可能可能
读已提交(Read Committed)解决可能可能
可重复读(Repeatable Read)解决解决大部分解决
串行化(Serializable)解决解决解决

其中三类并发问题的含义是:脏读是读到其他事务未提交的数据;不可重复读是同一事务内两次读取同一行数据结果不同;幻读是同一事务内范围查询的结果行数发生变化。MySQL 默认隔离级别为可重复读,通过 MVCC 加间隙锁,在多数场景下也能避免幻读。

5.3 MVCC 是什么?如何实现

MVCC(多版本并发控制)通过保存数据的历史版本,让读操作不加锁也能读到一致的数据,从而提升并发性能。其实现依赖 InnoDB 隐藏列(如trx_id和roll_pointer)、Undo Log 版本链以及 ReadView 可见性判断。

普通 SELECT 是快照读,读取创建 ReadView 时可见的版本;SELECT ... FOR UPDATE和UPDATE等则是当前读,读取最新数据并加锁。理解这一区分,是解释隔离级别与并发控制的关键。

5.4 什么是死锁?如何避免和定位

死锁是指两个或多个事务相互持有对方需要的锁,又都在等待对方释放,从而陷入永久等待。InnoDB 会主动检测死锁并回滚其中一个事务,使其不真正卡死。

减少死锁的实践建议:

  • 固定加锁顺序:多事务访问相同资源时保持一致的顺序。
  • 缩小事务范围:减少事务持有锁的时间。
  • 合理建立索引:避免因为无索引而锁住过多行。

定位方式可通过SHOW ENGINE INNODB STATUS查看最近一次死锁信息,或开启innodb_print_all_deadlocks把死锁日志输出到错误日志。

六、MySQL 架构与日志高频题

6.1 MySQL 的整体架构是怎样的

MySQL 主要分为 Server 层和存储引擎层。Server 层负责连接、解析、优化和执行,包括连接器、查询缓存、分析器、优化器和执行器;存储引擎层负责数据的存储和读取,常用 InnoDB,也支持 MyISAM、Memory 等,插件式设计使其可替换。

6.2 redo log、undo log 和 binlog 有什么区别

日志层次类型主要作用
redo logInnoDB 引擎层物理日志崩溃恢复,保证持久性
undo logInnoDB 引擎层逻辑日志事务回滚与 MVCC
binlogServer 层逻辑日志主从复制与数据恢复

redo log 采用 WAL(Write-Ahead Logging)机制,先写日志再写盘,保证事务提交后即使宕机也能恢复;binlog 则是 MySQL 自身用于复制和恢复的日志,二者共同构成两阶段提交的经典组合。

6.3 InnoDB 的 Buffer Pool 有什么作用

Buffer Pool 是 InnoDB 在内存中缓存数据页和索引页的区域,能显著减少磁盘 IO。它通过类似 LRU 的算法管理页淘汰,并结合 Change Buffer 优化二级索引的写入。合理设置innodb_buffer_pool_size,使其约为物理内存的 50%-70%,是常见的调优起点。

七、SQL 优化与慢查询高频题

7.1 如何定位慢查询

首先开启慢查询日志并设置阈值:slow_query_log=ON与long_query_time=1。然后借助mysqldumpslow或pt-query-digest分析日志,找到执行次数多、耗时高的 SQL,再结合 EXPLAIN 进一步分析。

7.2 EXPLAIN 中常见的字段怎么看

EXPLAIN 是 SQL 优化的核心工具,关键字段如下:

  • type:访问类型,性能从好到差依次为system、const、eq_ref、ref、range、index、ALL。
  • key:实际使用的索引,为 NULL 说明可能未走索引。
  • rows:预估扫描行数,值越大越需要关注。
  • Extra:Using index表示覆盖索引,Using filesort和Using temporary通常需要优化。

7.3 分库分表有哪些常见方案

当单表数据量过大时,可进行拆分:垂直拆分是按业务或字段拆分,水平拆分是按分片键把同一张表数据分散到多个库表。拆分后需要处理跨库 Join、分布式事务和全局唯一 ID 等问题,因此应先考虑索引优化、缓存和读写分离,确实无法支撑再分库分表。

八、Redis 高频题

8.1 Redis 常见数据类型及应用场景

常见类型包括:

  • String:缓存、计数器、分布式锁。
  • Hash:对象属性存储、购物车。
  • List:消息队列、最新列表。
  • Set:去重、共同好友、标签。
  • ZSet:排行榜、延迟队列。
  • Bitmap、HyperLogLog、GEO、Stream:分别用于签到统计、基数估算、地理位置和消息流。

8.2 RDB 和 AOF 持久化的区别

对比项RDBAOF
机制定时生成内存快照追加记录每次写命令
文件体积较小,压缩快照通常较大
恢复速度快需重放命令,较慢
数据完整性可能丢失最近一次快照后的数据取决于刷盘策略,丢失更少

生产环境通常两者同时开启,利用 RDB 快速恢复、AOF 保证完整性;Redis 也支持混合持久化,在 AOF 重写时写入 RDB 格式提高效率。

8.3 缓存穿透、缓存击穿和缓存雪崩

缓存穿透是查询一个数据库和缓存都不存在的数据,攻击者可借此绕过缓存,可用布隆过滤器和缓存空值应对。缓存击穿是某个热点 key 过期瞬间大量请求打向数据库,可通过互斥锁或逻辑过期解决。缓存雪崩是大量 key 同时过期或 Redis 整体不可用,可通过给过期时间增加随机值、使用高可用集群等方式缓解。

8.4 Redis 如何实现分布式锁

核心命令是SET key value NX EX seconds,利用 NX 保证互斥、EX 防止死锁。释放锁时必须校验持有者,并用 Lua 脚本保证「判断 + 删除」的原子性。生产环境常用 Redisson 的看门狗机制自动续期,避免业务超时导致锁提前释放。

九、总结与复习建议

数据库面试题看似零散,其实主线清晰:基础概念建立认知,SQL 语法体现动手能力,索引和事务是区分度最高的部分,MySQL 架构日志与 Redis 则考察工程实践深度。建议复习时做到三个「能」:

  • 能画图:B+ 树、聚簇索引、MVCC、主从复制等都能手绘结构。
  • 能写 SQL:JOIN、窗口函数、EXPLAIN 都能熟练书写。
  • 能讲清为什么:不仅记住结论,还能说明背后的设计取舍和适用场景。

最后建议结合真实慢查询案例做一次完整优化复盘,从发现问题、分析执行计划到索引调优,形成自己的面试表达闭环。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进