综合练习
练习方式
不要一上来就看答案。每个练习按这个顺序做:
- 圈出实体。
- 判断关系。
- 写表结构。
- 写核心 SQL。
- 设计 FastAPI 接口。
- 思考并发和幂等。
- 用面试语言解释你的设计。
通用技巧
建表前问
txt
这个对象是否需要自己的 ID?
是否会独立查询?
是否会被其他对象引用?
是否有状态?
是否要保存历史?
是否有唯一性要求?写接口前问
txt
这个接口是查询还是修改?
是否需要鉴权?
是否会重复调用?
修改了几张表?
是否需要事务?
失败后能否重试?写 SQL 前问
txt
主表是哪张?
过滤条件是什么?
是否需要 JOIN?
是否需要分页?
是否需要排序?
哪个索引能支持这个查询?练习 1:用户地址管理
需求
用户可以维护多个收货地址。每个用户最多只能有一个默认地址。地址包含收件人、手机号、省市区、详细地址。
你要完成
- 设计表结构。
- 写“新增地址”的 SQL 或伪代码。
- 写“设置默认地址”的事务逻辑。
- 设计 FastAPI 接口。
思考点
txt
默认地址如何保证只有一个?
删除默认地址后怎么办?
手机号用字符串还是数字?
地址是否需要软删除?参考方向
表:
sql
CREATE TABLE user_addresses (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT UNSIGNED NOT NULL,
receiver_name VARCHAR(64) NOT NULL,
phone VARCHAR(32) NOT NULL,
province VARCHAR(64) NOT NULL,
city VARCHAR(64) NOT NULL,
district VARCHAR(64) NOT NULL,
detail VARCHAR(255) NOT NULL,
is_default TINYINT(1) NOT NULL DEFAULT 0,
deleted_at DATETIME(3) NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
ON UPDATE CURRENT_TIMESTAMP(3),
KEY idx_user_created (user_id, created_at)
);设置默认地址时,在事务里:
sql
UPDATE user_addresses
SET is_default = 0
WHERE user_id = ? AND deleted_at IS NULL;
UPDATE user_addresses
SET is_default = 1
WHERE id = ? AND user_id = ? AND deleted_at IS NULL;接口:
txt
POST /addresses
GET /addresses
PATCH /addresses/{id}
POST /addresses/{id}/set-default
DELETE /addresses/{id}练习 2:RBAC 权限
需求
系统有用户、角色、权限。一个用户可以有多个角色,一个角色可以有多个权限。
你要完成
- 设计 5 张表。
- 查询某个用户拥有的所有权限。
- 给用户分配角色时避免重复。
参考方向
核心表:
txt
users
roles
permissions
user_roles
role_permissions中间表用联合主键:
sql
PRIMARY KEY (user_id, role_id)查询权限:
sql
SELECT DISTINCT p.code
FROM users u
JOIN user_roles ur ON ur.user_id = u.id
JOIN role_permissions rp ON rp.role_id = ur.role_id
JOIN permissions p ON p.id = rp.permission_id
WHERE u.id = ?;练习 3:文档解析任务
需求
用户上传文档后,系统创建解析任务。任务有 pending、running、success、failed 四种状态。失败任务可以重试。
你要完成
- 设计 documents 和 parse_jobs。
- 写 worker 抢任务 SQL。
- 防止多个 worker 同时执行同一个任务。
- 设计重试接口。
参考方向
任务表:
sql
CREATE TABLE document_parse_jobs (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
document_id BIGINT UNSIGNED NOT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'pending',
retry_count INT UNSIGNED NOT NULL DEFAULT 0,
error_message TEXT NULL,
locked_at DATETIME(3) NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
ON UPDATE CURRENT_TIMESTAMP(3),
KEY idx_status_created (status, created_at),
KEY idx_document_id (document_id)
);抢任务可以用条件更新:
sql
UPDATE document_parse_jobs
SET status = 'running', locked_at = NOW(3)
WHERE id = ?
AND status = 'pending';影响行数为 1 才算抢到任务。
练习 4:库存扣减
需求
用户下单购买商品,库存不能扣成负数,同一个请求不能重复创建订单。
你要完成
- 设计 products、orders。
- 写扣库存 SQL。
- 设计幂等键。
- 写 FastAPI 伪代码。
参考方向
扣库存:
sql
UPDATE products
SET stock = stock - ?
WHERE id = ? AND stock >= ?;订单表加幂等键:
sql
UNIQUE KEY uk_idempotency_key (idempotency_key)业务逻辑:
python
async with session.begin():
existing = await get_order_by_idempotency_key(session, key)
if existing:
return existing
success = await decrease_stock(session, product_id, quantity)
if not success:
raise AppError("OUT_OF_STOCK", "库存不足")
return await create_order(session, payload)练习 5:BI 报表任务
需求
用户选择数据集、时间范围和筛选条件后生成报表。相同参数的报表任务不应重复创建。
你要完成
- 设计 report_tasks。
- 设计参数 hash。
- 查询当前用户最近 20 个任务。
- 处理重复提交。
参考方向
sql
CREATE TABLE report_tasks (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT UNSIGNED NOT NULL,
dataset_id BIGINT UNSIGNED NOT NULL,
params_hash CHAR(64) NOT NULL,
params_json JSON NOT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'pending',
result_file_key VARCHAR(500) NULL,
error_message TEXT NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
ON UPDATE CURRENT_TIMESTAMP(3),
UNIQUE KEY uk_user_dataset_params (user_id, dataset_id, params_hash),
KEY idx_user_created (user_id, created_at)
);练习 6:Agent 会话记录
需求
用户可以创建 Agent 会话,每个会话包含多条消息。消息分为 user、assistant、tool。需要支持按会话分页加载。
你要完成
- 设计 sessions 和 messages。
- 查询某个会话最新 30 条消息。
- 思考消息是否允许修改。
- 设计删除会话策略。
参考方向
sql
CREATE TABLE agent_sessions (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT UNSIGNED NOT NULL,
title VARCHAR(200) NOT NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
ON UPDATE CURRENT_TIMESTAMP(3),
KEY idx_user_updated (user_id, updated_at)
);
CREATE TABLE agent_messages (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
session_id BIGINT UNSIGNED NOT NULL,
role VARCHAR(32) NOT NULL,
content MEDIUMTEXT NOT NULL,
metadata JSON NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
KEY idx_session_created (session_id, created_at)
);练习 7:把一个模块讲给面试官
任选上面一个练习,用这个模板讲:
txt
这个模块的核心实体是……
实体之间的关系是……
我设计了哪些唯一约束和索引……
核心接口包括……
涉及多表写入时我会用事务……
涉及重复提交时我会用幂等键……
涉及并发更新时我会用条件更新 / 行锁 / 乐观锁……
这个设计的取舍是……能讲清楚这一段,你的后端感就已经明显比“只会写 CRUD”强很多。