不是"优化前 / 优化后"两个按钮的演示 Demo。 是给你一条慢 SQL 和一份正确结果,你自己改,平台负责验对错、测耗时、出执行计划的训练场。
- 前端:Vue 3 + Vite + Element Plus
- 后端:Spring Boot 3.5 + MyBatis + JDK 17
- 数据:MySQL 8.0,订单 100 万 / 明细 300 万,数据分布是刻意做歪的(倾斜、低区分度、软删除、时间热点)
& 'C:\Program Files\MySQL\MySQL Server 8.0\bin\mysql.exe' -u root -p --default-character-set=utf8mb4 -e "source D:/sql/db/00_bootstrap.sql"会创建:
| 账号 | 权限 | 用途 |
|---|---|---|
sqllab |
sqllab 库全部权限 | 应用连接、索引沙箱 DDL |
sqllab_ro |
只有 SELECT | 你手写的 SQL 都跑在这个账号上 —— 这是安全边界 |
$mysql = 'C:\Program Files\MySQL\MySQL Server 8.0\bin\mysql.exe'
& $mysql -u sqllab -pSqlLab@2026 --default-character-set=utf8mb4 -e "source D:/sql/db/01_schema.sql"
& $mysql -u sqllab -pSqlLab@2026 --default-character-set=utf8mb4 -e "source D:/sql/db/02_baseline_index.sql"cd D:\sql\backend
.\mvnw spring-boot:run "-Dspring-boot.run.arguments=--gen.run=true"跑完日志里会打印数据分布,确认 t_order 100 万、t_order_item 约 300 万后 Ctrl+C 停掉。
可调规模:--gen.orders=200000(想快点验证流程时用)。
# 后端
cd D:\sql\backend
.\mvnw spring-boot:run
# 前端(另开一个终端)
cd D:\sql\frontend
npm run dev- ① 线上现状:选预设 ③「第 5000 页」,点「跑一遍现状」。先亲眼看到它有多慢。
- ③ 执行计划:对现状 SQL 跑 EXPLAIN。重点看
type/key/rows/Extra。 勾上 EXPLAIN ANALYZE,对比估算行数和实际行数。 - ④ 索引沙箱:想到什么索引就建,建完回第 ① 步重跑,看数字变没变。
- ② 我的改写:把现状 SQL 粘过来改。平台会逐行比对结果集 —— 改快了但结果不对,不算数。
- ⑤ 成绩板:每次提交都留痕,连当时的索引状态一起记下来。过两天回来还能看懂。
- ⑥ 答案:做不动了再点。里面给的是"怎么发现",不只是"怎么改"。
做完一遍,去 ④ 索引沙箱 → 一键还原,然后重做一遍。 第二遍能不看答案独立走完,这道题才算真的会了。
题库就是 D:\sql\labs\ 下的目录,加题不用改任何代码:
D:\sql\labs\lab02-你的新题\
meta.json 题面 + 参数表单 schema + 预设场景
baseline_xxx.sql 待优化的 SQL 模板
D:\sql\answers\lab02-你的新题\
answer.md 解法讲解
refs.json 参考实现清单
refN_xxx.sql 参考实现
meta.json 的写法直接抄 lab01-order-list。SQL 模板语法:
| 写法 | 效果 |
|---|---|
{{name}} |
按声明类型渲染成字面量(字符串自动加引号转义) |
{{name!}} |
原样插入,只允许整数(LIMIT / OFFSET 用) |
{{name:like}} |
渲染成 '%value%' |
{{#if name}}…{{/if}} |
参数非空才保留 |
{{#unless name}}…{{/unless}} |
参数为空才保留 |
offset |
内置派生参数 = (page - 1) * size |
SQL 文件每次请求都重新读盘,改完刷新页面就生效,不用重启后端。
D:\sql\
├─ db\ 建库脚本 + 索引还原存储过程
├─ backend\ Spring Boot 后端
├─ frontend\ Vue 前端
├─ labs\ 题库(题面,不含答案)
├─ answers\ 答案区(只有点「显示答案」才会被读取)
└─ docs\ 题面文档、面试讲解稿模板
为什么 SQL 用文本模板而不是 MyBatis 动态标签?
因为平台的核心动作是"把渲染好的 SQL 原文交给你去改"。参数必须是字面量而不是 ? 占位符,
否则你看到的和实际跑的不是一个东西,EXPLAIN 也没法看。
平台自己的持久化(成绩板)用的是 MyBatis。
为什么计时要预热 + 多轮? InnoDB 上单次计时没有意义:第一次读磁盘,第二次读 buffer pool,能差几十倍。 拿冷热两次比出来的"提升 400 倍",在面试里会被当场问穿。
为什么手写 SQL 要用只读账号?
正则拦关键字只是给出人话报错的第二道防线。真正的边界是那个只有 SELECT 权限的
sqllab_ro —— 无论你(或者以后接手的人)写出什么,它都改不了数据。