BilibiliDown:跨平台B站视频下载解决方案的技术架构与高效使用指南
2026/4/23 19:06:37
视频看了几百小时还迷糊?关注我,几分钟让你秒懂!
在实际开发中,慢 SQL 是性能杀手的第一名!一个没加索引的WHERE条件,可能让接口从 10ms 变成 10s;一条全表扫描的JOIN,可能直接拖垮整个数据库。
本文将用通俗语言 + Spring Boot 实战代码 + 正反案例对比,手把手教你:
小白也能看懂,建议收藏!
// 用户服务:根据手机号查用户(看似正常) public User getUserByPhone(String phone) { return jdbcTemplate.queryForObject( "SELECT * FROM user WHERE phone = ?", new BeanPropertyRowMapper<>(User.class), phone ); }问题在哪?
user表有 1000 万条数据;phone字段没有索引;✅结论:慢 SQL 不只是“慢”,而是系统性风险!
-- 查看是否开启 SHOW VARIABLES LIKE 'slow_query_log'; -- 开启慢查询(临时) SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 1; -- 超过1秒记录 -- 指定日志文件(可选) SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';生产环境建议开启,定期分析日志。
前面我们讲过 Druid,它能自动记录执行时间超过阈值的 SQL。
# application.yml spring: datasource: druid: filter: stat: slow-sql-millis: 200 # 超过200ms算慢SQL log-slow-sql: true # 打印慢SQL日志启动后,控制台会输出类似:
[Druid-ConnectionPool-Log] slow sql 256 ms, url:..., sql: SELECT * FROM order WHERE create_time > '2025-01-01'但对新手来说,Druid + 慢日志已经够用!
定位 → 分析 → 优化 → 验证
通过上述方法找到具体 SQL。
EXPLAIN SELECT * FROM user WHERE phone = '13800138000';重点关注:
type:最好为const/ref,避免ALL(全表扫描);key:是否命中索引;rows:扫描行数,越少越好;Extra:避免Using filesort、Using temporary。✅理想结果:
type: ref key: idx_phone rows: 1 Extra: NULL❌危险信号:
type: ALL key: NULL rows: 10000000 Extra: Using where反例(无索引):
-- user 表 phone 无索引 SELECT id, name FROM user WHERE phone = '138...'; -- EXPLAIN 显示 type=ALL, rows=1000万正例(加索引):
ALTER TABLE user ADD INDEX idx_phone (phone);再次 EXPLAIN,
type=ref,rows=1,速度从 5s → 1ms!
⚠️ 注意:
WHERE YEAR(create_time) = 2025→ 索引失效!反例:
SELECT * FROM order WHERE user_id = 1001; -- 如果 order 表有 50 个字段,包含大文本(如 content)正例:
SELECT id, order_no, amount, status FROM order WHERE user_id = 1001;减少网络传输 + 内存占用,尤其当表有
TEXT/BLOB字段时效果显著。
反例(经典慢查询):
-- 跳过 10 万条,取 10 条 SELECT * FROM product ORDER BY id LIMIT 100000, 10; -- 随着 offset 增大,越来越慢!正例(基于游标分页):
-- 记住上一页最大 id = 100000 SELECT * FROM product WHERE id > 100000 ORDER BY id LIMIT 10;时间复杂度从 O(N) 降到 O(1),百万级数据也快如闪电!
💡 Spring Data JPA 可用
Pageable+@Query实现,MyBatis 同理。
反例(大表 JOIN 大表):
SELECT u.name, o.amount FROM user u JOIN order o ON u.id = o.user_id WHERE o.create_time > '2025-01-01'; -- 如果两表都千万级,且无索引,直接卡死正例:
order.user_id有索引;SELECT u.name, o.amount FROM user u JOIN ( SELECT user_id, amount FROM order WHERE create_time > '2025-01-01' ) o ON u.id = o.user_id;反例:
-- phone 是 VARCHAR 类型,但传了数字 SELECT * FROM user WHERE phone = 13800138000; -- MySQL 会把 phone 转成数字比较 → 索引失效!正例:
SELECT * FROM user WHERE phone = '13800138000'; -- 字符串必须加引号!Java 中用
PreparedStatement自动处理类型,但拼接 SQL 时要小心!
EXPLAIN看执行计划;System.currentTimeMillis()测试);@Component @Intercepts({@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class})}) public class SqlCostInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { long start = System.currentTimeMillis(); Object result = invocation.proceed(); long cost = System.currentTimeMillis() - start; if (cost > 200) { System.out.println("【慢SQL警告】耗时: " + cost + "ms"); } return result; } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } }超过 200ms 自动告警,早发现早治疗!
但小团队可用人工 Code Review + EXPLAIN 检查。
WHERE a=? AND b=?,索引应为(a,b),不是(b,a)。| 问题类型 | 优化手段 |
|---|---|
| 无索引 | 添加合适索引 |
| SELECT * | 只查必要字段 |
| 深分页 | 改用游标分页(基于 ID) |
| 大表 JOIN | 先过滤,再关联 |
| 函数操作索引字段 | 改写 SQL 避免函数 |
| 隐式类型转换 | 保证参数类型与字段一致 |
| 排序无索引 | 为 ORDER BY 字段加索引 |
记住:90% 的慢 SQL,靠一个正确索引就能解决!
视频看了几百小时还迷糊?关注我,几分钟让你秒懂!