CHAPTER 11 · PERFORMANCE & CACHE
11
相册只有 100 个用户时很快,10 万个用户以后为什么越来越慢?
性能问题可能发生在任何一层。优化前必须先测量,再定位瓶颈,而不是凭感觉直接加 Redis。
真实现象
“慢”可能来自很多地方。
打开需要 5 秒
相册列表需要 2 秒
照片很多时页面卡顿
MySQL CPU 持续升高
内存不断增加
Redis 加上以后反而数据错误
慢的是前端渲染,还是后端接口?
接口慢,是 Go 代码慢还是 MySQL 慢?
图片加载慢,应该优化 React、OSS 还是网络?
没有测量数据,能不能直接开始优化?
先测量 → 找瓶颈 → 做优化 → 再验证
全链路视角
用户一次操作,会经过很多层。
React
页面层重复请求、重复渲染、资源加载过多、图片过大。
Go / MySQL
服务层连接池配置错误、慢查询、N+1、深分页、返回字段过多。
Redis / OSS
外部依赖缓存不一致、缓存击穿、原图过大、签名或网络耗时。
前端性能
相册系统最容易慢在请求和图片。
减少重复请求
检查页面初始化是否重复请求、useEffect 依赖是否错误、是否禁止重复提交、是否处理请求竞态。
分页与按需加载
首次加载 20 张,滚动或翻页时再加载下一批。前端把 1 万条全部请求回来再分页,没有意义。
图片优化
列表不要加载原图,应使用 OSS 缩略图、懒加载、固定图片尺寸,首屏只加载可见图片。
构建资源
关注生产构建、文件 Hash、浏览器缓存、按页面拆包,以及是否引入体积巨大的依赖。
页面只显示 300×200 的缩略图,就不应该下载一张 10 MB 的原图。
接口与网络
响应体越大、接口越多,页面越容易慢。
错误信号
SELECT * 后把无关字段、内部字段、OSS Object Key、审核信息全部返回给列表页。两个极端
一个页面请求 30 个接口
一个接口返回整个系统全部数据
更合理
按页面需要聚合
字段按展示需求返回
列表、详情、统计分清边界
查询字段和接口字段都应该按实际页面需要设计。减少接口数量,不等于制造万能接口。
Go 服务端
不要先优化语言细节,先看时间花在哪里。
总耗时 2 秒,其中 MySQL 1.6 秒,此时优化 Go 循环几乎没有意义。先解决最慢的一层。
外部调用没有超时
危险MySQL、Redis、OSS 或 HTTP 服务卡住,会占用请求和 Goroutine。
连接池只看单实例
不够5 个 Go 实例,每个 MaxOpenConns=50,理论最大连接数就是 250。
慢就多开 Goroutine
误区并发可能把 MySQL、OSS 或内存打爆,并不等于性能更好。
MySQL 性能
ORM 不能代替 SQL 性能审查。
深分页
LIMIT 20 OFFSET 200000
翻到很深时,数据库仍然要跳过大量数据。可以进一步考虑基于 created_at + id 的游标分页。
索引服务于查询
WHERE album_id = ?
ORDER BY created_at DESC
LIMIT 20
索引要围绕 WHERE、ORDER BY、稳定排序和返回数量设计。
是否使用索引
使用了哪个索引
预计扫描多少行
是否全表扫描
是否额外排序或临时表N+1 查询
查询 20 个相册 → 20 次查询封面 → 20 次 COUNT 照片,最终可能执行 41 次 SQL。要检查 GORM 最终生成的 SQL,而不是只看 Go 代码是否简洁。
Redis
MySQL 已经能保存数据,为什么还需要 Redis?
Redis 适合做加速层,而不是替代 MySQL 成为业务事实来源。
MySQL 是数据事实来源 + Redis 是加速层
热门公开相册、用户基础信息、照片数量统计
可以评估高频读取、变化较少、允许短时间不一致。
每月访问一次的设置页
通常不需要低频接口加缓存可能收益很小,复杂度却增加。
唯一主数据、密码、强一致账户余额
不适合简单缓存这类数据不能依赖 Redis 作为唯一来源。
更新时先更新 MySQL,再删除 Redis 缓存;下次读取时重新构建。不要只更新 Redis。
缓存风险
缓存会让系统更快,也会让系统更复杂。
Redis 不是银弹。缓存会引入一致性、穿透、击穿和雪崩问题,低频接口或本来就很快的查询未必需要缓存。
Nginx 与静态资源
静态资源可以缓存,带权限的 API 不要乱缓存。
可以优化
gzip / Brotli、Keep-Alive、带 Hash 的 JS/CSS 长期缓存、HTML 短缓存。
要小心
/api 不应被错误缓存,尤其是用户私有数据和带权限的接口。
正确顺序
性能优化不是“感觉慢就加 Redis”。
不合格决策
感觉慢 → 直接加 Redis请分析相册列表接口的性能问题,不要直接重写全部代码。
分析重点:
1. 梳理 React、Nginx、Go、MySQL 的完整链路。
2. 给出需要采集的耗时数据。
3. 检查重复请求、N+1、SELECT * 和无分页。
4. 检查 GORM 最终生成的 SQL。
5. 给出需要验证的 EXPLAIN 结果。
6. 只有确认高频重复读取时,才考虑 Redis。
7. Redis 必须说明 Key、TTL、更新和删除策略。
8. 给出优化前后的验证方法。
9. 不要为了性能牺牲权限校验和数据正确性。本章总结
- 性能优化必须从测量开始。
- 前端、网络、Nginx、Go、MySQL、Redis 和 OSS 都可能是瓶颈。
- 前端应减少重复请求,并使用分页、懒加载和缩略图。
- Go 服务应控制超时、连接池和并发边界。
- MySQL 优化应围绕真实 SQL、索引和执行计划。
- ORM 不能代替 SQL 性能审查。
- Redis 是加速层,不是 MySQL 的替代品。
- 缓存会引入一致性、穿透、击穿和雪崩问题。
- 不是所有接口都需要 Redis。
- 优化完成后必须重新测量,证明它确实有效。
不要因为系统慢就直接加 Redis。先找到最慢的一层,再解决最重要的问题。