# mysql **Repository Path**: middleware-lab/mysql ## Basic Information - **Project Name**: mysql - **Description**: mysql的场景 - **Primary Language**: Go - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-15 - **Last Updated**: 2026-09-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # MySQL 特性与问题模拟器 一个用 **Go + React** 搭建的交互式项目,在**真实 MySQL**(InnoDB)上复现常见的 MySQL 使用场景、特性以及容易踩坑的问题,帮助理解: - 事务与隔离级别:脏读、不可重复读、幻读、四种隔离级别 - 锁与死锁:行锁、共享锁/排他锁、锁等待超时、死锁 - 索引与执行计划:EXPLAIN 解读、覆盖索引、索引失效、慢查询 - 连接与高并发:连接池状态、连接泄漏、丢失更新与原子自增 后端把每个现象组织成「场景」,通过 REST API 在真实 MySQL 上实际执行 SQL、采集结果并返回给前端;前端用 React 渲染,点击「运行」即可逐步看到发生了什么。 --- ## 架构 ``` ┌──────────────┐ /api/* ┌────────────────┐ database/sql ┌─────────────┐ │ React 前端 │ ──────────▶ │ Go HTTP 服务 │ ───────────────▶ │ MySQL 8.4 │ │ (Vite + TS) │ ◀────────── │ (场景编排) │ ◀─────────────── │ (InnoDB) │ └──────────────┘ JSON └────────────────┘ 多会话并发执行 └─────────────┘ ``` - 后端使用标准库 `net/http`(Go 1.22+ 路由)+ `github.com/go-sql-driver/mysql`。 - 每个模块的演示通过**多条独立连接**模拟多个客户端会话,必要时用 goroutine + channel 编排并发,让死锁、锁等待等并发现象真实发生。 - 前端为 Vite + React + TypeScript + Tailwind CSS。 ## 目录结构 ``` mysql/ ├── docker-compose.yml # 一键启动 MySQL 8.4 ├── backend/ │ ├── cmd/server/main.go # 入口 │ └── internal/ │ ├── config/ # 环境变量配置 │ ├── db/ # 建表、种子数据、连接池 │ ├── api/ # HTTP 路由、静态托管、CORS │ └── scenarios/ # 四类场景的具体实现 └── frontend/ # React 前端(Vite + TS + Tailwind) ``` ## 快速开始 ### 一键启动(推荐) 已内置 `Makefile` 与脚本,一条命令即可拉起全站(自动启动 MySQL 容器、构建前端、编译并启动后端): ```bash make up # 生产模式:MySQL + 后端托管前端,访问 http://127.0.0.1:8080 make dev # 开发模式:MySQL + 后端 :8080 + Vite :5173(前端热更新) make stop # 停止后端(保留 MySQL 与数据) make down # 停止后端并删除 MySQL 容器 ``` 也可直接使用脚本(等价于上面的 make 命令): ```bash ./scripts/start.sh # 一键启动(生产模式) ./scripts/dev.sh # 一键启动(开发模式) ./scripts/stop.sh [--db] # 停止服务,加 --db 一并停止 MySQL ``` 所有命令都可通过环境变量覆盖:`ADDR`(默认 `:8080`)、`MYSQL_ROOT_PASSWORD`(默认 `mysql123`)。 其他常用命令:`make status`(查看状态)、`make logs`(MySQL 日志)、`make reset`(重置演示数据)、`make build`(仅构建前端)。 > **端口被占用怎么办?** 若 `:8080` 已被其他服务占用,脚本会直接报错退出(而不是静默启动却收不到请求)。此时换一个端口即可,Vite 的 `/api` 代理会自动跟随: > > ```bash > ADDR=:8090 make dev # 后端跑在 8090,前端仍访问 http://localhost:5173 > ``` > > 注意:`make up` 的生产模式是单端口(后端同时托管前端),换端口后用 `http://127.0.0.1:<端口>` 访问。 > > 启动脚本会把解析出的后端地址写入 `.backend-target`,因此之后单独运行 `npm run dev` 时,Vite 会自动读取并代理到正确的后端,无需重复设置环境变量。 ### 手动分步启动 #### 1. 启动 MySQL 需要 Docker 环境: ```bash docker compose up -d ``` 默认 root 密码为 `mysql123`,数据库 `mysql_demo`,端口 `3306`。 #### 2. 启动后端 ```bash cd backend go mod tidy go run ./cmd/server ``` 服务监听 `:8080`。可通过环境变量覆盖配置: | 环境变量 | 默认值 | 说明 | | --- | --- | --- | | `ADDR` | `:8080` | HTTP 监听地址 | | `MYSQL_DSN` | `root:mysql123@tcp(127.0.0.1:3306)/mysql_demo?parseTime=true&charset=utf8mb4&loc=Local` | MySQL 连接串(需含数据库名) | | `FRONTEND_DIR` | 空 | 前端构建产物目录;设置后后端会一并托管静态文件 | #### 3. 启动前端(开发模式) ```bash cd frontend npm install npm run dev ``` 打开 http://localhost:5173 ,Vite 会把 `/api` 代理到 `:8080`。 #### 生产部署(单进程) ```bash cd frontend && npm run build # 产物输出到 frontend/dist cd ../backend FRONTEND_DIR=../frontend/dist go run ./cmd/server ``` 然后直接访问 http://localhost:8080 即可,前端静态资源由 Go 服务托管。 ## 场景清单 ### 事务与隔离级别 | 场景 | key | 现象 | | --- | --- | --- | | 脏读 | `dirty-read` | READ UNCOMMITTED 下读到未提交数据 | | 不可重复读 | `non-repeatable-read` | READ COMMITTED 下两次读同一行结果不同 | | 幻读 | `phantom-read` | RC 下区间统计随并发插入变化,RR 快照读不变 | | 隔离级别矩阵 | `isolation-matrix` | 对 RU/RC/RR 实测三种现象并汇总 | ### 锁与死锁 | 场景 | key | 现象 | | --- | --- | --- | | 锁等待超时 | `lock-wait-timeout` | 行 X 锁导致另一事务阻塞至超时(错误 1205) | | 死锁 | `deadlock` | 互相等待对方行锁,InnoDB 回滚其一(错误 1213) | | 共享锁 vs 排他锁 | `lock-modes` | S 锁兼容 S 锁,但阻塞 X 锁 | ### 索引与执行计划 | 场景 | key | 现象 | | --- | --- | --- | | 全表扫描 vs 走索引 | `explain-compare` | type=ALL 与 type=ref 的对比 | | 覆盖索引 | `covering-index` | Extra=Using index 免回表 | | 索引失效 | `index-failures` | 前导通配、函数包裹、隐式转换、OR 等 | | 慢查询分析 | `slow-query` | 快/慢查询 EXPLAIN 与耗时对比 | ### 连接与高并发 | 场景 | key | 现象 | | --- | --- | --- | | 连接池状态 | `pool-stats` | 并发负载下连接复用与等待 | | 连接泄漏 | `connection-leak` | 不归还连接导致连接池耗尽 | | 丢失更新 | `lost-update` | 读-算-写覆盖 vs 原子自增 | ## API | 方法 | 路径 | 说明 | | --- | --- | --- | | GET | `/api/health` | 健康检查与 MySQL 版本 | | GET | `/api/overview` | 版本、表行数、连接池统计 | | GET | `/api/scenarios` | 场景与模块元信息 | | POST | `/api/scenarios/{key}` | 运行某个场景 | | POST | `/api/reset` | 重建表并重置种子数据 | ## 说明 - InnoDB 默认隔离级别为 `REPEATABLE READ`;普通 `SELECT` 基于 MVCC 一致性快照,因此 RR 下快照读不会出现幻读,但当前读(如 `SELECT ... FOR UPDATE`)仍可能看到新行。 - 死锁由 InnoDB 的 wait-for 图检测并自动选择一个事务回滚;`lock-wait-timeout` 与 `lost-update` 等场景需要真正并发执行,后端用多个连接 + goroutine 编排。 - 大表 `customers` 默认写入 5 万行,用于索引/慢查询演示;点击「重置演示数据」或 `POST /api/reset` 可重新生成。 ## License MIT