Files
cs-note/hhs/MySQL/01-入门基础/01-MySQL 架构与入门.md
2026-05-24 11:42:38 +08:00

269 lines
9.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [MySQL, 架构, 进程模型, Server]
create time: 2026-05-16 00:00
---
# MySQL 架构与进程模型
## 概述
理解 MySQL 的内部架构是性能调优和故障排查的前提。本文将从 Client-Server 模型出发,逐层拆解 MySQL 的四大功能层以及不同存储引擎下的线程调度机制。
## 四层架构
MySQL 遵循经典的三层 Client-Server 架构,但在服务端内部被清晰划分为四层:
```mermaid
graph BT
subgraph "应用层"
A["应用程序<br/>(Go / Java / Python)"]
end
subgraph "连接层<br/>Connection Layer"
B["Thread Pool"]
C["Authentication"]
D["Connection Management"]
end
subgraph "SQL 层<br/>SQL Layer"
E["Query Cache ⚠️ 8.0 已移除"]
F["Parser → Preprocessor"]
G["Optimizer"]
H["Executor"]
end
subgraph "存储引擎层<br/>Storage Engine"
I["InnoDB"]
J["MyISAM"]
K["Memory"]
L["Archive"]
end
subgraph "文件系统"
M["Data Files"]
N["Binlog"]
O["Redo Log"]
end
A --> B
B --> F
F --> G --> H --> I
H --> J
H --> K
H --> L
I --> M
I --> N
I --> O
```
### 各层职责
**连接层**负责处理客户端的连接请求,包括身份验证、权限校验、SSL/TLS 协商以及线程分配。MySQL 采用「一连接一线程」模型——每个客户端连接对应一个独立的服务器线程,这意味着万级并发时会产生巨大的线程开销。
> [!NOTE] 为什么高并发场景要考虑线程池?
> MySQL 原生没有内置线程池(Percona Server 提供)。当并发量超过数千时,上下文切换的开销会成为瓶颈。解决方案有三:
> - **ProxySQL / MaxScale**:前端代理层做连接复用
> - **MySQL Thread Pool Plugin**:企业版功能或 Percona 分支
> - **连接池**:在应用层控制并发连接数(如 Go 的 `sql.DB`)
**SQL 层**是整个数据库的核心,完成 SQL 语句的解析、优化和执行。这一层实现了 SQL 标准语法、查询优化策略、函数系统和事务管理。值得注意的是,**很多用户熟悉的 SQL 特性其实都在这一层实现**——比如子查询改写、JOIN 顺序优化等。
**存储引擎层**提供了可插拔的存储方案。不同的引擎对索引、锁、事务的支持各不相同。InnoDB 承担了默认的事务型负载,而 MyISAM、Memory 等引擎则针对特定场景做了优化。由于大多数生产环境只使用 InnoDB,现代运维中这一层其实非常安静。
**文件系统层**将页(Page,默认 16KB)写入磁盘。InnoDB 有自己的 Buffer Pool 来管理数据缓存,同时也依赖操作系统的 Page Cache。**Linux 内核的 I/O 调度器对 MySQL 性能有直接影响**。
## 线程模型详解
MySQL 服务启动时会创建一组后台线程,同时为每个连接分配专用线程:
```mermaid
classDiagram
class MasterThread {
+binlog dump
+purge redo log
+truncate binary log
+DD event cleanup
+change buffer merge
}
class ConnectionThread {
+parse query
+optimize
+execute
+send result
}
class WorkerThreads {
+InnoDB read worker
+InnoDB log writer
+InnoDB page cleaner
}
class ThreadPool {
<<optional>>
+acceptor thread
+worker pool
+idle stack management
}
MasterThread ..> WorkerThreads : "coordinates"
ThreadPool --> ConnectionThread : "dispatches"
```
### 关键后台线程
| 线程 | 职责 |
|------|------|
| **Master Thread** | InnoDB 专属,协调写操作、刷新脏页、合并 Change Buffer |
| **Log Thread** | 将 Redo Log Buffer 刷入 Redo Log File |
| **Page Cleaner** | 主动将脏页从 Buffer Pool 刷盘,避免崩溃恢复时间过长 |
| **Purge Thread** | 删除 Undo Log 中标记为废弃的行记录 |
| **DD Event Cleanup** | 清理数据字典中的过期对象信息 |
### 连接线程生命周期
```mermaid
stateDiagram-v2
[*] --> New: 客户端发起 TCP 连接
New --> Handshake: 握手协议 + 认证
Handshake --> Authenticated: 认证成功
Handshake --> Closed: 认证失败
Authenticated --> Sleeping: 发送命令
Sleeping --> Executing: 收到新命令
Executing --> Sending: 执行完毕
Sending --> Sleeping: 返回结果集
Sleeping --> Closed: 客户端断开 / 超时
Sleeping --> Killed: 管理员 kill 掉
```
> [!QUESTION] 什么是 MySQL 连接超时?
> 如果客户端空闲太久不发消息会发生什么?MySQL 有两个关键超时参数:
> - **`wait_timeout`**(默认 28800 秒 = 8 小时):非交互式连接的闲置超时
> - **`interactive_timeout`**(默认 28800 秒):交互式连接(如 mysql CLI)的闲置超时
>
> 连接池中如果未正确配置这些参数,可能出现大量 ZOMBIE 连接占满 `MaxConnections`。这就是为什么 Go 的 `sql.DB.ConnMaxLifetime` 应该设为比 wait_timeout 更短的值。
## mysqld 启动流程
```mermaid
flowchart TD
A["加载 my.cnf 配置"] --> B["初始化全局变量"]
B --> C["加载插件"]
C --> D["初始化存储引擎<br/>innodb_init()"]
D --> E["打开数据目录"]
E --> F["恢复未提交事务<br/>redo log crash recovery"]
F --> G["启动后台线程"]
G --> H["监听端口"]
H --> I["就绪 ✨"]
```
启动过程中的关键阶段:
1. **配置加载**:按优先级读取配置文件 `/etc/my.cnf` → `/etc/mysql/my.cnf` → `~/.my.cnf`,命令行参数优先级最高
2. **引擎初始化**:InnoDB 会在启动时进行 Crash Recovery——重放 Redo Log 恢复到一致状态。如果数据文件损坏严重,这一步会卡住或报错
3. **Crash Recovery**:innodb_force_recovery 参数可以在恢复期间限制操作级别(1~6),用于紧急导出数据
## 核心架构图
```mermaid
graph TB
subgraph "连接管理"
C1["Accept Thread"] --> C2["Connection Pool"]
C2 --> C3["Read Command"]
end
subgraph "SQL 处理流水线"
C3 --> P1["Syntax Parser"]
P1 --> P2["AST Semantic Check"]
P2 --> P3["Preprocessing"]
P3 --> P4["Optimization"]
P4 --> P5["Execution"]
end
subgraph "InnoDB 执行路径"
P5 --> I1["Buffer Pool Read Page"]
I1 --> I2{"Hit?"}
I2 -->|"Yes"| I3["Return Result"]
I2 -->|"No"| I4["Read from Disk"]
I4 --> I1
I5["Change Buffer"] -.-> I1
end
C1 --> C3
style P4 fill:#FF9F43,color:#000
style I1 fill:#00B6BC,color:#fff
style I3 fill:#00D866,color:#fff
```
## 实战:在线诊断工具
在生产环境中快速定位问题,需要掌握以下几组命令行工具。它们分别作用于不同的架构层级:
### 1. SHOW PROCESSLIST — SQL 层视角
```sql
-- 最基础的线程状态查看
SHOW FULL PROCESSLIST;
-- 等效的 SQL 查询(可结合 WHERE 过滤)
SELECT * FROM information_schema.processlist WHERE COMMAND != 'Sleep';
```
每个线程在 `PROCESSLIST` 中表现为一条记录,关键列:
| 列 | 含义 | 常见值 |
|----|------|--------|
| `ID` | 线程 ID,`KILL id` 使用 | |
| `State` | 当前执行阶段 | `Sending data`, `Creating sort index`, `Locked` |
| `Time` | 当前状态的持续秒数 | > 5s 需警惕 |
| `Info` | 正在执行的 SQL | NULL 表示空闲 |
> [!TIP] State 解读口诀
> - **`Waiting for handler lock`** → 行锁竞争,配合 `Innodb_row_lock_waits` 排查
> - **`Creating sort index`** → 大 ORDER BY/GROUP BY,检查是否缺少索引
> - **`Sending data`** → 不仅是在"发送结果",而是**正在扫描和生成数据**,往往是最耗时的阶段
### 2. SHOW STATUS — 全局计数器
```sql
-- 查看慢查询总数(取决于 slow_query_log 配置)
SHOW GLOBAL STATUS LIKE 'Slow_queries';
-- 当前并发连接数 vs 上限
SHOW GLOBAL STATUS LIKE 'Threads_connected';
SHOW GLOBAL STATUS LIKE 'Max_connections';
-- InnoDB 缓冲池命中率(接近 100% 为佳)
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';
```
> [!QUESTION] 为什么 Buffer Pool 命中率不是越高越好?
> 理论上 Hit Rate 越接近 100% 越好,但当数据集小于 Buffer Pool 时,Hit Rate 会固定在 100% 以上(因为缓存了多次)。生产环境中 **98%~99%** 已经足够优秀,不必盲目增大 `innodb_buffer_pool_size`。
### 3. Performance Schema — MySQL 5.7+ 的深层监控
`performance_schema` 是 MySQL 内置的性能采集框架,比 `SHOW STATUS` 更细粒度:
```sql
-- 当前等待事件(锁、I/O、网络)TOP 10
SELECT EVENT_NAME, COUNT_STAR, AVG_TIMER_WAIT
FROM performance_schema.events_waits_summary_global_by_event_name
ORDER BY COUNT_STAR DESC LIMIT 10;
-- 最近 10 条耗时 > 1s 的语句
SELECT DIGEST_TEXT, COUNT_STAR, AVG_TIMER_WAIT/1e12 AS avg_ms
FROM performance_schema.events_statements_summary_by_digest
WHERE AVG_TIMER_WAIT > 1e12
ORDER BY AVG_TIMER_WAIT DESC LIMIT 10;
```
> [!NOTE] Performance Schema 的性能代价
> 开启 PS 会带来约 **5%~15%** 的 CPU 开销。建议在低峰期采样分析,或仅对目标线程启用 instrumentation。
---
## 关联笔记
- [[hhs/GORM/01-安装与初始化]] — GORM 连接 MySQL 的配置参数
- [[hhs/Redis/01-安装与部署]] — Redis 单线程 vs MySQL 多线程模型对比