
背景上周线上监控报警有一个用户列表查询接口的平均响应时间达到了 2.3 秒高峰期甚至超过 5 秒严重影响了用户体验。这个接口是我一年前写的当时数据量小没觉得有什么问题。现在用户表已经有 500 多万条数据了性能问题就暴露出来了。今天我把整个优化过程记录下来从问题定位到最终优化完成一步步带大家看如何把一个慢接口优化到 50ms 以内。第一步定位问题首先用 Postman 测试了一下接口确实很慢。然后我打开了 MySQL 的慢查询日志找到了对应的 SQL 语句sqlSELECT u.id, u.username, u.email, u.phone, r.role_name, d.dept_name FROM user u LEFT JOIN role r ON u.role_id r.id LEFT JOIN dept d ON u.dept_id d.id WHERE u.status 1 ORDER BY u.create_time DESC LIMIT 10 OFFSET 100000;执行时间2.1 秒。问题 1深度分页导致的性能问题LIMIT 10 OFFSET 100000这种写法会让 MySQL 扫描 100010 行数据然后丢弃前 100000 行只返回最后 10 行。当偏移量很大时性能会急剧下降。优化方案使用游标分页sql-- 优化前 SELECT * FROM user WHERE status 1 ORDER BY id DESC LIMIT 10 OFFSET 100000; -- 优化后 SELECT * FROM user WHERE status 1 AND id 100000 ORDER BY id DESC LIMIT 10;这样 MySQL 只需要扫描 10 行数据执行时间从 2.1 秒降到了 0.01 秒。问题 2没有合适的索引查看了一下表结构发现status、create_time和关联字段role_id、dept_id都没有建索引。优化方案添加合适的索引sql-- 给查询条件和排序字段建联合索引 CREATE INDEX idx_user_status_create_time ON user(status, create_time DESC); -- 给关联字段建索引 CREATE INDEX idx_user_role_id ON user(role_id); CREATE INDEX idx_user_dept_id ON user(dept_id);添加索引后查询时间降到了 0.3 秒。问题 3不必要的关联查询查看代码发现这个接口返回的角色名称和部门名称其实很少变化而且大部分用户都属于同一个部门和角色。优化方案使用缓存java运行// 原来的代码 ListUserVO userList userMapper.selectUserList(query); for (UserVO user : userList) { Role role roleMapper.selectById(user.getRoleId()); user.setRoleName(role.getName()); Dept dept deptMapper.selectById(user.getDeptId()); user.setDeptName(dept.getName()); } // 优化后的代码 // 先把所有角色和部门加载到缓存中 MapLong, String roleMap roleMapper.selectAll().stream() .collect(Collectors.toMap(Role::getId, Role::getName)); MapLong, String deptMap deptMapper.selectAll().stream() .collect(Collectors.toMap(Dept::getId, Dept::getName)); // 然后在循环中直接从缓存中获取 for (UserVO user : userList) { user.setRoleName(roleMap.get(user.getRoleId())); user.setDeptName(deptMap.get(user.getDeptId())); }这样就把原来的 N1 次查询变成了 3 次查询接口响应时间降到了 0.1 秒。问题 4返回了不必要的字段查看接口返回结果发现返回了很多不需要的字段比如password、salt、update_time等。优化方案只返回需要的字段java运行// 原来的代码 ListUser userList userMapper.selectList(queryWrapper); // 优化后的代码 ListUserVO userList userMapper.selectUserList(query);在 Mapper 中只查询需要的字段减少数据传输量。最终优化结果经过以上几步优化接口的平均响应时间从 2.3 秒降到了 45ms高峰期也不超过 100ms完美解决了性能问题。总结接口优化是一个系统性的工作需要从多个方面入手先定位慢查询优化 SQL 语句添加合适的索引避免 N1 查询使用缓存只返回需要的字段考虑使用 Redis 缓存热点数据记住过早优化是万恶之源但不优化也是万恶之源。在开发初期就要考虑到性能问题避免后期踩大坑。