北京云车易购科技 Vibe Coding 实习一面

1. 请介绍你的实习经历

2. 慢 SQL 你会怎么定位,如何判断瓶颈在索引、锁、排序还是回表

答案:

定位慢 SQL 我一般不会直接上来就加索引,而是先看慢查询日志、执行频率、平均耗时、P95/P99、扫描行数和返回行数。如果扫描行数远大于返回行数,通常要重点看索引和过滤条件;如果扫描不多但耗时很长,就要看锁等待、磁盘 IO、临时表、文件排序或下游连接池。

具体会用 EXPLAINEXPLAIN ANALYZE 看执行计划,包括 typekeyrowsfilteredExtra。如果出现 Using filesortUsing temporaryALL、回表过多,就要进一步分析索引设计。对于线上问题,还要结合 show processlist、InnoDB 锁等待、事务持续时间和 buffer pool 命中率。

EXPLAIN ANALYZE
SELECT id, device_id, status, created_at
FROM work_order
WHERE factory_id = 1001
  AND status = 'PENDING'
  AND created_at >= '2026-04-01'
ORDER BY created_at DESC
LIMIT 20;

如果这个 SQL 频繁用于分页查询,可以考虑建立联合索引:

CREATE INDEX idx_factory_status_time
ON work_order(factory_id, status, created_at DESC);

但索引顺序要结合等值条件、范围条件和排序字段设计。比如 factory_idstatus 是等值过滤,created_at 既做范围又做排序,放在后面比较合适。优化后还要看是否减少了扫描行数和 filesort,而不是只看有没有走索引。

3. 索引明明建了但没生效,常见原因有哪些

答案:

索引没生效不一定是 MySQL “不用索引”,很多时候是索引对当前查询不划算。常见原因包括:不符合最左前缀、字段上使用函数或表达式、隐式类型转换、模糊查询前置 %、范围条件后面的索引列无法继续用于有序定位、低选择性字段导致优化器认为全表扫描更便宜、统计信息不准、返回列太多导致回表成本高。

还有一种很隐蔽的是字符集或排序规则不一致,比如两个表 join 的字段类型看起来一样,但字符集不同,可能导致索引利用异常。线上排查时要对照表结构、字段类型、字符集、索引基数和执行计划,不能只看 SQL 表面。

-- 隐式类型转换,可能导致索引效果变差
SELECT *
FROM user_device
WHERE device_code = 123456;

-- 如果 device_code 是 varchar,应该这样写
SELECT *
FROM user_device
WHERE device_code = '123456';

对于函数导致索引失效:

-- 不推荐:对索引列做函数计算
SELECT *
FROM work_order
WHERE DATE(created_at) = '2026-04-20';

-- 推荐:改成范围查询
SELECT *
FROM work_order
WHERE created_at >= '2026-04-20 00:00:00'
  AND created_at <  '2026-04-21 00:00:00';

4. Redis 缓存和 MySQL 数据不一致,你会怎么处理

答案:

缓存和数据库不一致要先看业务能接受什么级别的一致性。大多数读多写少场景可以接受短暂最终一致;如果是库存、余额、审批状态这类强一致数据,就不能只依赖缓存。常见方案是写数据库后删除缓存,而不是写数据库后更新缓存,因为更新缓存可能引入并发覆盖和复杂的数据组装问题。

更稳的方式是延迟双删或基于消息队列异步删除。先更新数据库,再删除缓存;如果担心并发读把旧值写回缓存,可以延迟一段时间再删一次。对于极高一致性要求的场景,可以加版本号或逻辑时间戳,缓存里只接受更新版本的数据。

@Transactional
public void updateDeviceStatus(Long deviceId, String status) {
    deviceMapper.updateStatus(deviceId, status);
    redisTemplate.delete("device:" + deviceId);
    
    mqTemplate.convertAndSend("cache.delete.exchange", "device.status", deviceId);
}

消息消费者做延迟删除:

public void onCacheDelete(Long deviceId) {
    try {
        Thread.sleep(500);
        redisTemplate.delete("device:" + deviceId);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
}

这个方案不是绝对强一致,但能覆盖大多数缓存旧值回写问题。如果业务不能接受任何旧读,就需要改成读写都走数据库事务或引入更严格的版本校验。

5. Redis 热 key、大 key、缓存穿透同时出现时怎么治理

答案:

热 key 会导致单个 Redis 节点压力过高,大 key 会导致网络传输、序列化、删除和阻塞问题,缓存穿透会让不存在的数据持续打到数据库。三个问题如果同时出现,一般说明缓存设计已经有结构性问题。

热 key 可以做本地缓存、热点拆分、多副本读、限流和预热。大 key 要拆结构,比如一个超大的 hash 或 list 拆成多个分片 key;删除大 key 用异步删除,避免阻塞主线程。缓存穿透可以用空值缓存、布隆过滤器和参数校验解决。

public DeviceInfo getDevice(String deviceId) {
    if (!bloomFilter.mightContain(deviceId)) {
        return null;
    }

    String key = "device:" + deviceId;
    DeviceInfo cached = localCache.getIfPresent(key);
    if (cached != null) {
        return cached;
    }

    cached = redis.get(key, DeviceInfo.class);
    if (cached != null) {
        localCache.put(key, cached);
        return cached;
    }

    DeviceInfo db = deviceMapper.selectById(deviceId);
    if (db == null) {
        redis.set(key, DeviceInfo.empty(), Duration.ofSeconds(30));
        return null;
    }

    redis.set(key, db, Duration.ofMinutes(10));
    localCache.put(key, db);
    return db;
}

大 key 处理示例:

-- 非阻塞删除
UNLINK big:device:history:1001

如果是设备历史数据这种天然大集合,不应该全部塞一个 key,可以按设备和日期拆分:

device:history:{deviceId}:{yyyyMMdd}

6. 你自己写的代码和 AI 生成代码如何分工,比例怎么控制

答案:

我不会按固定比例分工,而是按风险分工。低风险、模式化、重复性强的部分可以让 AI 多参与,比如 DTO、VO、Mapper、单测模板、接口文档、异常枚举、SQL 草稿、日志分析脚本。高风险部分必须自己主导,比如事务边界、权限校验、状态机、并发控制、幂等、消息一致性和数据修复脚本。

一般流程是我先写设计和关键接口,让 AI 根据上下文补实现草案;然后我审查生成代码,修改边界条件和异常处理,再让 AI 补充测试。AI 更像副驾驶,不是最终提交者。真正上线的代码必须经过人工 review、测试和 CI。

适合 AI:
- 重复 CRUD
- 单测样例
- 文档注释
- 参数校验模板
- 日志分析脚本

必须人工主导:
- 分布式事务
- 并发写入
- 权限边界
- MQ 消费幂等
- 数据库变更
- 灰度和回滚逻辑

7. AI 生成代码出问题,你会怎么处理

答案:

先看问题是在需求理解、上下文缺失、代码实现、边界条件还是测试不足。如果是需求理解错了,需要重新补充约束,比如输入输出、异常场景、并发场景、依赖接口语义。如果是上下文缺失,就把相关类、表结构、调用链、报错栈补齐,而不是让 AI 继续猜。

处理上不会直接让 AI 重写整块代码,而是先最小化复现问题,写一个失败用例,再让 AI 或自己修复。修完以后要跑相关测试和回归。对于生产问题,还要看是否需要回滚、补偿数据和增加防线。

@Test
void shouldNotCreateDuplicateTaskWhenRetry() {
    String requestId = "req-001";

    taskService.createTask(requestId, "device-check");
    taskService.createTask(requestId, "device-check");

    int count = taskMapper.countByRequestId(requestId);
    assertEquals(1, count);
}

如果 AI 之前生成的代码没有幂等保护,就要补唯一键或幂等表:

ALTER TABLE diagnosis_task
ADD UNIQUE KEY uk_request_id(request_id);

8. 如何对 AI 生成代码做质量检查,流程是什么

答案:

质量检查不能只靠人工看一眼。我的流程一般是:先做 diff 范围控制,确认 AI 没有改无关文件;然后做编译、单测、静态扫描、依赖漏洞扫描;再做业务 review,重点看事务、异常、空值、并发、日志、权限和幂等;最后进入测试环境做接口回归和灰度。

对于 AI 生成代码,还会额外看有没有“看似合理但实际不存在”的 API、有没有吞异常、有没有硬编码、有没有把敏感信息写日志、有没有破坏原有兼容性。AI 很容易写出能编译但语义不对的代码,所以必须用测试和 review 把关。

mvn clean verify
mvn test -Dtest=DeviceTaskServiceTest
mvn spotbugs:check
mvn dependency-check:check

CI 里可以加质量门禁:

quality_gate:
  min_line_coverage: 80
  block_on_high_vulnerability: true
  block_on_compile_warning: false
  require_code_review: true

9. 对 AI 生成代码会做哪些测试,不只是单元测试

答案:

会分层测试。单元测试验证函数和服务逻辑,集成测试验证数据库、Redis、MQ 和外部接口协作,契约测试验证接口输入输出兼容性,回归测试验证历史功能不被破坏,压测验证性能和资源占用,故障注入验证超时、重试和降级。

AI 生成代码尤其要测边界条件,比如空输入、重复请求、并发请求、下游超时、消息重复、缓存不存在、数据库唯一键冲突。很多 AI 代码只覆盖 happy path,真正上线出问题通常都在异常路径。

@Test
void shouldRollbackWhenMqSendFailed() {
    doThrow(new RuntimeException("mq failed"))
            .when(mqClient).send(any());

    assertThrows(RuntimeException.class, () -> {
        orderService.createAndNotify(new CreateOrderCommand("u1", "sku1"));
    });

    assertEquals(0, orderMapper.countByUserId("u1"));
}

并发测试也很重要:

@Test
void shouldCreateOnlyOneRecordUnderConcurrentRequest() throws Exception {
    ExecutorService pool = Executors.newFixedThreadPool(20);
    CountDownLatch latch = new CountDownLatch(20);

    for (int i = 0; i < 20; i++) {
        pool.submit(() -> {
            try {
                taskService.createOnce("idem-001");
            } finally {
                latch.countDown();
            }
        });
    }

    latch.await();
    assertEquals(1, taskMapper.countByIdemKey("idem-001"));
}

10. 使用 AI 工具时,你有什么比别人更稳定的方法

答案:

我不会直接问“帮我实现某某功能”,而是先让 AI 明确边界。比如先给它角色、代码上下文、接口定义、表结构、异常语义、性能要求和不允许修改的文件。生成前先让它输出方案和影响面,确认后再生成代码。生成后再让它按 checklist 自查一次,但最终还是人工判断。

比较有效的是把需求写成规格说明,而不是自然语言愿望。比如明确输入、输出、幂等键、失败重试、事务边界、日志字段、测试用例。AI 在明确约束下表现稳定很多。

请只修改 DeviceTaskService 和对应测试类。
要求:
1. createTask 必须基于 requestId 幂等
2. 数据库写入和任务状态更新在同一事务
3. MQ 发送失败时任务状态置为 WAIT_RETRY
4. 不允许吞异常
5

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

AI-Agent面试实战专栏 文章被收录于专栏

本专栏聚焦 AI-Agent 面试高频考点,内容来自真实面试与项目实践。系统覆盖大模型基础、Prompt工程、RAG、Agent架构、工具调用、多Agent协作、记忆机制、评测、安全与部署优化等核心模块。以“原理+场景+实战”为主线,提供高频题解析、标准答题思路与工程落地方法,帮助你高效查漏补缺.

全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务