Skip to content

About

sqltrain

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

Repository files navigation

SQL 优化训练台

不是"优化前 / 优化后"两个按钮的演示 Demo。 是给你一条慢 SQL 和一份正确结果,你自己改,平台负责验对错、测耗时、出执行计划的训练场。

  • 前端:Vue 3 + Vite + Element Plus
  • 后端:Spring Boot 3.5 + MyBatis + JDK 17
  • 数据:MySQL 8.0,订单 100 万 / 明细 300 万,数据分布是刻意做歪的(倾斜、低区分度、软删除、时间热点)

一次性初始化

1. 建库建账号(需要 root,只做一次)

& '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 都跑在这个账号上 —— 这是安全边界

2. 建表 + 存储过程

$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"

3. 造数(几分钟)

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

打开 http://localhost:5178


怎么用(这部分比启动步骤重要)

  1. ① 线上现状:选预设 ③「第 5000 页」,点「跑一遍现状」。先亲眼看到它有多慢。
  2. ③ 执行计划:对现状 SQL 跑 EXPLAIN。重点看 type / key / rows / Extra。 勾上 EXPLAIN ANALYZE,对比估算行数和实际行数。
  3. ④ 索引沙箱:想到什么索引就建,建完回第 ① 步重跑,看数字变没变。
  4. ② 我的改写:把现状 SQL 粘过来改。平台会逐行比对结果集 —— 改快了但结果不对,不算数。
  5. ⑤ 成绩板:每次提交都留痕,连当时的索引状态一起记下来。过两天回来还能看懂。
  6. ⑥ 答案:做不动了再点。里面给的是"怎么发现",不只是"怎么改"。

做完一遍,去 ④ 索引沙箱 → 一键还原,然后重做一遍。 第二遍能不看答案独立走完,这道题才算真的会了。


加新题

题库就是 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 —— 无论你(或者以后接手的人)写出什么,它都改不了数据。

About

sqltrain

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages