MySQL 实战精通系列 · 第1篇:MySQL 全局观与实战环境搭建
本篇目标:搞懂 MySQL 怎么跑起来,一条 SQL 从输入到返回经历了什么,并搭好贯穿全系列的电商实战环境。
文章目录
- [MySQL 实战精通系列 · 第1篇:MySQL 全局观与实战环境搭建](#MySQL 实战精通系列 · 第1篇:MySQL 全局观与实战环境搭建)
-
- [一、先建立全局观:MySQL 长什么样?](#一、先建立全局观:MySQL 长什么样?)
-
- [1.1 MySQL 三层架构](#1.1 MySQL 三层架构)
- [1.2 一条 SQL 的完整生命周期](#1.2 一条 SQL 的完整生命周期)
- [二、MySQL 适合什么场景?](#二、MySQL 适合什么场景?)
-
- [2.1 OLTP vs OLAP](#2.1 OLTP vs OLAP)
- [三、实战环境搭建:Docker 启动 MySQL 8.0](#三、实战环境搭建:Docker 启动 MySQL 8.0)
-
- [3.1 安装 Docker](#3.1 安装 Docker)
- [3.2 启动 MySQL 8.0](#3.2 启动 MySQL 8.0)
- [3.3 连接 MySQL](#3.3 连接 MySQL)
- [3.4 常用管理命令](#3.4 常用管理命令)
- [四、创建电商实战库 shop](#四、创建电商实战库 shop)
-
- [4.1 核心表设计](#4.1 核心表设计)
- [4.2 建表 SQL](#4.2 建表 SQL)
- [4.3 插入测试数据](#4.3 插入测试数据)
- [五、看一条 SQL 到底慢在哪:SHOW PROFILES](#五、看一条 SQL 到底慢在哪:SHOW PROFILES)
-
- [5.1 开启 profiling](#5.1 开启 profiling)
- [5.2 执行一条 SQL 并查看耗时](#5.2 执行一条 SQL 并查看耗时)
- [5.3 查看详细阶段](#5.3 查看详细阶段)
- 六、本篇实战任务
- 七、本篇小结
一、先建立全局观:MySQL 长什么样?
1.1 MySQL 三层架构
MySQL 可以分成三层,理解这三层,后面所有优化和排障都有了方向。
┌─────────────────────────────────────────────┐
│ 客户端 / 应用程序 │
└─────────────────────┬───────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 连接层 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 连接管理 │ │ 权限认证 │ │ 线程池 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────┬───────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Server 层 │
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌──────┐ │
│ │ SQL解析 │ │ 优化器 │ │ 执行器 │ │Binlog│ │
│ └────────┘ └────────┘ └────────┘ └──────┘ │
└─────────────────────┬───────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 存储引擎层 │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ InnoDB │ │ MyISAM │ │ Memory │ │
│ └────────┘ └────────┘ └────────┘ │
└─────────────────────┬───────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 磁盘 / 文件系统 │
└─────────────────────────────────────────────┘
| 层 | 通俗理解 | 负责什么 |
|---|---|---|
| 连接层 | 前台 | 接待客户端、验证身份、分配线程 |
| Server 层 | 大脑 | 解析 SQL、决定怎么查、执行查询 |
| 存储引擎层 | 仓库 | 真正把数据存到磁盘、从磁盘取出来 |
关键点 :MySQL 的 Server 层和存储引擎层是分离的,所以引擎可以插拔。现在默认且最常用的是 InnoDB。
1.2 一条 SQL 的完整生命周期
假设执行:
sql
SELECT * FROM users WHERE id = 100;
内部流程:
客户端
│
│ 发送 SQL
↓
连接层
│
│ 验证权限、分配线程
↓
解析器
│
│ 检查语法、生成语法树
↓
优化器
│
│ 决定用哪个索引、先查哪张表
↓
执行器
│
│ 按执行计划调用存储引擎接口
↓
InnoDB 引擎
│
│ 从 Buffer Pool 或磁盘读数据
↓
磁盘
│
│ 返回数据页
↓
InnoDB 引擎
│
│ 返回行
↓
执行器
│
│ 返回结果
↓
客户端
| 阶段 | 做什么 | 出问题会怎样 |
|---|---|---|
| 解析器 | 检查语法、表是否存在 | 语法错误、表不存在 |
| 优化器 | 决定用哪个索引、先查哪张表 | 选错索引,查询变慢 |
| 执行器 | 按执行计划调用引擎接口 | 执行效率低 |
| InnoDB | 从 Buffer Pool 或磁盘读数据 | 磁盘 IO 高、缓冲池命中率低 |
以后优化 SQL,本质上就是在影响 优化器 的选择,以及 InnoDB 的读取效率。
二、MySQL 适合什么场景?
2.1 OLTP vs OLAP
很多人用 MySQL 做报表、做大数据分析,然后发现特别慢。原因是没分清 OLTP 和 OLAP。
┌─────────────────────────┐ ┌─────────────────────────┐
│ OLTP 联机事务处理 │ │ OLAP 联机分析处理 │
│ │ │ │
│ · 电商下单 │ │ · 报表 │
│ · 支付 │ │ · 数据仓库 │
│ · 用户注册 │ │ · BI 分析 │
│ │ │ │
│ 高并发、短事务 │ │ 低并发、长查询 │
│ 小数据量 │ │ 大数据量 │
└───────────┬─────────────┘ └───────────┬─────────────┘
│ │
↓ ↓
┌─────────────┐ ┌─────────────────────┐
│ MySQL │ │ ClickHouse / Doris │
│ 主战场 │ │ / Hive / Spark │
└─────────────┘ └─────────────────────┘
| 维度 | OLTP | OLAP |
|---|---|---|
| 典型场景 | 电商下单、支付 | 报表、数据仓库 |
| 特点 | 高并发、短事务、小数据量 | 低并发、长查询、大数据量 |
| 常用数据库 | MySQL、PostgreSQL | ClickHouse、Hive、Spark |
| 优化目标 | 响应快、事务稳 | 吞吐大、扫描快 |
结论:
- MySQL 的主战场是 OLTP。
- 复杂报表应同步到 ClickHouse、Doris 等 OLAP 数据库。
- 不要指望 MySQL 既扛高并发写入,又跑复杂分析。
三、实战环境搭建:Docker 启动 MySQL 8.0
3.1 安装 Docker
去 Docker 官网安装,装好后验证:
bash
docker --version
能输出版本号即可。
3.2 启动 MySQL 8.0
bash
docker run -d \
--name mysql8 \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=root123 \
-e MYSQL_DATABASE=shop \
-v mysql8_data:/var/lib/mysql \
mysql:8.0 \
--character-set-server=utf8mb4 \
--collation-server=utf8mb4_unicode_ci
| 参数 | 作用 |
|---|---|
-d |
后台运行 |
--name mysql8 |
容器名 |
-p 3306:3306 |
端口映射 |
MYSQL_ROOT_PASSWORD |
root 密码 |
MYSQL_DATABASE |
自动创建 shop 库 |
-v mysql8_data |
数据卷,防止数据丢失 |
utf8mb4 |
支持 emoji 和完整 Unicode |
3.3 连接 MySQL
bash
docker exec -it mysql8 mysql -uroot -proot123
进入后验证:
sql
SHOW DATABASES;
USE shop;
SELECT VERSION();
看到版本号,说明环境 OK。
3.4 常用管理命令
bash
# 查看容器状态
docker ps -a
# 启动 / 停止 / 重启
docker start mysql8
docker stop mysql8
docker restart mysql8
# 查看日志
docker logs mysql8
# 进入容器 bash
docker exec -it mysql8 bash
四、创建电商实战库 shop
4.1 核心表设计
┌──────────┐ ┌──────────┐ ┌──────────┐
│ users │ │ orders │ │order_items│
├──────────┤ ├──────────┤ ├──────────┤
│ id (PK) │──┐ │ id (PK) │──┐ │ id (PK) │
│ username │ │ │ user_id │ │ │ order_id │
│ phone │ └───>│ total │ └───>│ product_id│
│ created │ │ status │ │ quantity │
└──────────┘ │ created │ │ price │
└──────────┘ └──────────┘
│ ↑
│ │
↓ │
┌──────────┐ ┌──────────┐
│ payments │ │ products │
├──────────┤ ├──────────┤
│ id (PK) │ │ id (PK) │
│ order_id │ │ name │
│ amount │ │ price │
│ status │ │ stock │
│ paid_at │ │category_id│
└──────────┘ │ created │
└──────────┘
↑
│
┌──────────┐
│categories│
├──────────┤
│ id (PK) │
│ name │
│ parent_id│
└──────────┘
┌──────────┐
│ addresses│
├──────────┤
│ id (PK) │
│ user_id │
│ detail │
│ phone │
└──────────┘
关系说明:
users 1 ──── N orders 一个用户多个订单
orders 1 ──── N order_items 一个订单多个订单项
products 1 ──── N order_items 一个商品可被多次购买
orders 1 ──── N payments 一个订单多次支付
users 1 ──── N addresses 一个用户多个地址
categories 1 ──── N products 一个分类多个商品
4.2 建表 SQL
sql
USE shop;
CREATE TABLE users (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
username VARCHAR(64) NOT NULL,
phone VARCHAR(20) NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uk_phone (phone)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE categories (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR(64) NOT NULL,
parent_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (id),
KEY idx_parent (parent_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE products (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR(128) NOT NULL,
price DECIMAL(10,2) NOT NULL,
stock INT NOT NULL DEFAULT 0,
category_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_name (name),
KEY idx_category (category_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE orders (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
user_id BIGINT UNSIGNED NOT NULL,
total_amount DECIMAL(10,2) NOT NULL DEFAULT 0.00,
status TINYINT NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_user_created (user_id, created_at),
KEY idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE order_items (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
order_id BIGINT UNSIGNED NOT NULL,
product_id BIGINT UNSIGNED NOT NULL,
quantity INT NOT NULL,
price DECIMAL(10,2) NOT NULL,
PRIMARY KEY (id),
KEY idx_order (order_id),
KEY idx_product (product_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE payments (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
order_id BIGINT UNSIGNED NOT NULL,
amount DECIMAL(10,2) NOT NULL,
status TINYINT NOT NULL DEFAULT 0,
paid_at DATETIME DEFAULT NULL,
PRIMARY KEY (id),
KEY idx_order (order_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE addresses (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
user_id BIGINT UNSIGNED NOT NULL,
detail VARCHAR(255) NOT NULL,
phone VARCHAR(20) NOT NULL,
PRIMARY KEY (id),
KEY idx_user (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4.3 插入测试数据
sql
INSERT INTO users (username, phone) VALUES
('张三', '13800000001'),
('李四', '13800000002'),
('王五', '13800000003');
INSERT INTO categories (name, parent_id) VALUES
('手机', 0),
('电脑', 0),
('苹果', 1),
('小米', 1),
('华为', 1);
INSERT INTO products (name, price, stock, category_id) VALUES
('iPhone 15', 5999.00, 100, 3),
('小米 14', 3999.00, 200, 4),
('华为 Mate 60', 6999.00, 150, 5);
INSERT INTO orders (user_id, total_amount, status) VALUES
(1, 5999.00, 1),
(1, 3999.00, 1),
(2, 6999.00, 0);
INSERT INTO order_items (order_id, product_id, quantity, price) VALUES
(1, 1, 1, 5999.00),
(2, 2, 1, 3999.00),
(3, 3, 1, 6999.00);
INSERT INTO payments (order_id, amount, status, paid_at) VALUES
(1, 5999.00, 1, NOW()),
(2, 3999.00, 1, NOW());
INSERT INTO addresses (user_id, detail, phone) VALUES
(1, '北京市朝阳区xxx', '13800000001'),
(2, '上海市浦东新区xxx', '13800000002');
五、看一条 SQL 到底慢在哪:SHOW PROFILES
5.1 开启 profiling
sql
SET profiling = 1;
5.2 执行一条 SQL 并查看耗时
sql
SELECT * FROM orders WHERE user_id = 1;
SHOW PROFILES;
结果类似:
| Query_ID | Duration | Query |
|---|---|---|
| 1 | 0.00045 | SELECT * FROM orders WHERE user_id = 1 |
5.3 查看详细阶段
sql
SHOW PROFILE FOR QUERY 1;
输出阶段按顺序如下:
starting
│
↓
checking permissions
│
↓
Opening tables
│
↓
init
│
↓
System lock
│
↓
optimizing
│
↓
statistics
│
↓
preparing
│
↓
executing
│
↓
Sending data
用表格看得更清楚:
| 顺序 | 阶段 | 含义 | 慢说明什么 |
|---|---|---|---|
| 1 | starting | 开始 | --- |
| 2 | checking permissions | 检查权限 | 权限配置复杂 |
| 3 | Opening tables | 打开表 | 表太多或元数据锁 |
| 4 | init | 初始化 | --- |
| 5 | System lock | 系统锁 | 锁竞争 |
| 6 | optimizing | 优化 | 索引统计信息有问题 |
| 7 | statistics | 统计 | 统计信息过期 |
| 8 | preparing | 准备 | --- |
| 9 | executing | 执行 | SQL 本身效率低 |
| 10 | Sending data | 发送数据 | 数据量大或网络慢 |
实战意义:这是你未来排查慢 SQL 的第一把工具。哪个阶段耗时最长,问题就往哪个方向查。
六、本篇实战任务
任务清单
- 用 Docker 启动 MySQL 8.0
- 连接 MySQL,创建 shop 库
- 执行全部建表 SQL
- 插入测试数据
- 开启 profiling,执行一条 SQL
- 用 SHOW PROFILES 查看各阶段耗时
- 用 SHOW PROFILE FOR QUERY 查看详细阶段
自检问题
- MySQL 三层架构分别是什么?
- 一条 SQL 从输入到返回经历了哪些阶段?
- OLTP 和 OLAP 的区别是什么?MySQL 适合哪个?
- SHOW PROFILES 能帮你看什么?
- 为什么说优化 SQL 本质是在影响优化器和 InnoDB?
七、本篇小结
第1篇 核心收获
│
├── 架构
│ ├── 连接层:接待客户端
│ ├── Server层:解析、优化、执行
│ └── 存储引擎层:InnoDB 负责读写
│
├── SQL生命周期
│ ├── 解析:检查语法
│ ├── 优化:选索引
│ ├── 执行:调引擎接口
│ └── 存储引擎:读数据页
│
├── 场景定位
│ ├── OLTP 是 MySQL 主战场
│ └── OLAP 交给 ClickHouse
│
├── 实战环境
│ ├── Docker 启动 MySQL 8.0
│ └── shop 电商库 7 张表
│
└── 排障工具
├── SHOW PROFILES
└── SHOW PROFILE FOR QUERY
下一篇:第2篇《数据建模与建表实战》