前端面试里的数据库题通常不会深到 DBA 级别,但会考察你做全栈项目时是否理解数据建模、索引、缓存和 ORM。

关系型数据库

MySQL 与 PostgreSQL

知识点讲解

关系型数据库用表、行、列和约束组织数据,适合结构清晰、事务要求高的业务。MySQL 使用广泛,生态成熟;PostgreSQL 类型系统、扩展能力和复杂查询能力强。

面试常问

问:关系型数据库适合什么场景?

答:适合订单、用户、支付、权限等需要强一致性、事务、复杂查询和结构化约束的场景。

事务与 ACID

知识点讲解

事务保证一组操作要么全部成功,要么全部失败。ACID 分别是原子性、一致性、隔离性、持久性。隔离级别越高,并发问题越少,但性能成本越高。

面试常问

问:事务能解决什么问题?

答:能保证关键业务状态一致,比如下单时扣库存、生成订单、写流水必须作为一个整体处理,不能只成功一半。

索引

知识点讲解

索引本质是用额外数据结构加速查询。MySQL InnoDB 常用 B+Tree 索引。索引不是越多越好,写入和更新也要维护索引。

面试常问

问:为什么索引会失效?

答:常见原因包括对索引列做函数计算、隐式类型转换、前置模糊匹配、联合索引不满足最左前缀、选择性太差。

缓存与 NoSQL

Redis

知识点讲解

Redis 是内存数据存储,常用于缓存、分布式锁、计数器、排行榜、会话和消息队列。常见数据结构有 String、Hash、List、Set、ZSet。

面试常问

问:缓存穿透、击穿、雪崩区别?

答:穿透是查询不存在数据打到数据库,可用空值缓存和布隆过滤器;击穿是热点 key 失效瞬间大量请求打到数据库,可用互斥锁和逻辑过期;雪崩是大量 key 同时失效,可用随机过期时间和限流降级。

MongoDB

知识点讲解

MongoDB 是文档数据库,数据以 BSON 文档存储,适合字段变化频繁、文档聚合读取明显的场景。但如果业务强事务、复杂 join 很多,关系型数据库更合适。

面试常问

问:MongoDB 适合替代 MySQL 吗?

答:不是简单替代关系。要看数据模型和查询方式。文档型、结构灵活、读写文档整体的场景适合 MongoDB;强关系和事务场景更适合关系型数据库。

ORM 与 Prisma

ORM 的价值

知识点讲解

ORM 把数据库表映射成语言里的模型,减少手写 SQL,提升类型提示和迁移管理体验。但复杂查询、性能优化和事务边界仍需要理解 SQL。

面试常问

问:ORM 有什么缺点?

答:可能生成低效 SQL,隐藏查询成本,复杂查询表达不直观。工程里要能查看 SQL、分析慢查询,而不是完全依赖 ORM。

Prisma

知识点讲解

Prisma 通过 schema 定义模型,生成类型安全的 client,并提供 migration 管理数据库结构。它适合 TypeScript 全栈项目。

面试常问

问:Prisma 的类型安全体现在哪?

答:模型字段、查询参数和返回值都能由 schema 推导,字段名写错或类型不匹配会在编译阶段暴露。

数据建模

表设计

知识点讲解

表设计要关注主键、外键、唯一约束、字段类型、索引、冗余和扩展性。不要为了省事把所有数据塞进 JSON,也不要过度范式化导致查询复杂。

面试常问

问:如何设计用户和角色权限?

答:通常拆用户表、角色表、权限表,以及用户角色关联表、角色权限关联表。这样支持多角色、多权限和后续扩展。

底层追问与代码示例

B+Tree 索引为什么适合数据库

知识点讲解

B+Tree 的非叶子节点只存 key 和指针,叶子节点存数据或主键,并且叶子节点有链表连接。这样树高更低,范围查询更友好,磁盘 I/O 更少。

1
2
3
4
5
6
7
8
CREATE TABLE users (
id BIGINT PRIMARY KEY,
email VARCHAR(128) NOT NULL,
name VARCHAR(64) NOT NULL,
created_at DATETIME NOT NULL,
UNIQUE KEY uniq_email (email),
KEY idx_created_at (created_at)
);

面试常问

问:聚簇索引和非聚簇索引区别?

答:InnoDB 的主键索引是聚簇索引,叶子节点存整行数据;普通二级索引叶子节点存主键值。通过二级索引查到主键后,再回到主键索引查整行,这叫回表。

联合索引与最左前缀

知识点讲解

联合索引 (a, b, c) 可以支持按最左前缀的查询,比如 aa,ba,b,c。如果跳过 a 只查 b,通常无法充分利用该联合索引。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CREATE INDEX idx_user_status_time
ON orders (user_id, status, created_at);

-- 能利用 user_id、status,并可用于 created_at 范围
SELECT *
FROM orders
WHERE user_id = 10
AND status = 'paid'
AND created_at >= '2026-01-01';

-- 难以充分利用该索引
SELECT *
FROM orders
WHERE status = 'paid';

面试常问

问:联合索引字段顺序怎么设计?

答:优先考虑查询模式。常见做法是等值条件放前面,范围条件放后面,同时兼顾字段区分度、排序和覆盖索引。

Redis 分布式锁

知识点讲解

Redis 锁必须保证加锁原子性、设置过期时间、释放时校验 owner,不能直接 del key

1
2
3
4
5
// 加锁:SET lockKey requestId NX PX 30000
await redis.set(lockKey, requestId, {
NX: true,
PX: 30_000
})

释放锁需要 Lua 保证“判断并删除”原子:

1
2
3
4
5
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end

面试常问

问:Redis 锁过期时间怎么定?

答:要大于业务正常执行时间,并考虑续期机制。过短会导致锁提前释放,过长会影响故障恢复。高可靠场景还要评估 Redlock 或数据库事务方案。

Prisma 事务示例

知识点讲解

下单这类操作要保证库存和订单一致,不能拆成独立请求随意执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
await prisma.$transaction(async tx => {
const product = await tx.product.findUnique({
where: { id: productId }
})

if (!product || product.stock <= 0) {
throw new Error('库存不足')
}

await tx.product.update({
where: { id: productId },
data: { stock: { decrement: 1 } }
})

await tx.order.create({
data: {
userId,
productId,
status: 'paid'
}
})
})

专业回答要补充并发问题:高并发扣库存可能需要数据库行锁、乐观锁版本号、唯一约束或队列削峰。