MySQL架构详解:Client/Server通信流程
大家好,我是一名拥有10年以上经验的DBA老兵。
做这个系列,源于一个朴素的愿望:把踩过的坑、总结的经验系统化输出,希望能帮到刚入行或想进阶的兄弟们。
让我们开始今天的第1天内容。
背景引入
💡 说白了:很多同学CRUD写得很溜,但被问到"MySQL是怎么处理你的SQL的"时,一问三不知。
你有没有想过这个问题:
当你执行
SELECT * FROM user WHERE id = 1这条SQL时,MySQL 内部到底发生了什么?
不了解 MySQL 的架构,就像开车不知道发动机原理——也能开,但出了问题就抓瞎。
今天的目标:搞清楚 MySQL 的"流水线"是怎么工作的,面试必问!
核心概念
MySQL 整体架构
MySQL 是典型的 Client/Server 架构。说白了,就是客户端发请求,服务端处理返回结果。
整体流程就像工厂流水线:
客户端 ──SQL语句──▶ MySQL服务器 ──处理──▶ 返回结果
│
▼
┌─────────────┐
│ 连接层 │ 👉 TCP连接、线程管理
└─────────────┘
│
▼
┌─────────────┐
│ SQL层 │ 👉 解析→优化→执行
└─────────────┘
│
▼
┌─────────────┐
│ 存储引擎层 │ 👉 数据读写、事务支持
└─────────────┘
第一关:连接层(Connection Layer)
客户端怎么和 MySQL 建立联系?三次握手 + 创建线程。
-- 查看当前连接数及执行结果
> SHOW GLOBAL STATUS LIKE 'Threads_connected';
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| Threads_connected | 15 |
+-------------------+-------+
-- 查看最大连接数及执行结果
> SHOW GLOBAL VARIABLES LIKE 'max_connections';
+-----------------+--------+
| Variable_name | Value |
+-----------------+--------+
| max_connections | 1000 |
+-----------------+--------+
面试必问:
max_connections最大连接数,默认151,不够用怎么办?wait_timeout连接空闲超时,默认8小时,太长有啥问题?- 为什么 MySQL 早期是每个连接一个线程?现在有什么变化?
💡 MySQL 像餐厅,每个客人(连接)来都分配一个服务员(线程),服务员要一直等着,吃完饭才走。客人太多,服务员忙不过来,就崩了。
📝 面试解答:
Q1: max_connections 不够用怎么办?
方案一:适当调大
max_connections(但每个连接都占内存,调太大可能 OOM)
方案二(推荐):使用连接池(HikariCP、Druid),复用连接,别频繁创建新连接
方案三:排查连接泄漏(某些连接没及时 close,一直处于 Sleep 状态)
Q2: wait_timeout 太长有啥问题?
空闲连接占着茅坑不拉屎,浪费资源
攻击者利用这点建立大量空闲连接搞垮 MySQL
推荐设置:30分钟(1800秒)以内
Q3: 为什么 MySQL 早期是每个连接一个线程?现在有什么变化?
早期 MySQL 采用 one-thread-per-connection 模型,实现简单、隔离性好。但在高并发下,大量线程会导致上下文切换开销大、内存消耗高(每个线程默认栈约 256KB,外加会话缓冲区),容易 OOM。
现在的变化:
- 社区版 MySQL(8.0 及以前)依然使用 one-thread-per-connection,没有变化。
- 企业版从 5.5 开始提供 Thread Pool 插件,让多个连接复用少量工作线程,减少切换开销和内存占用。
- MariaDB 和 Percona Server 原生支持线程池。
需注意:线程池是服务端线程复用,不是连接复用。客户端仍然推荐使用连接池(如 HikariCP)来减少连接建立成本。
第二关:SQL层(SQL Layer)
SQL 语句进来后,要经过"三道工序":
| 工序 | 名字 | 干啥的 |
|---|---|---|
| 第一道 | 解析器(Parser) | 词法分析、语法检查,确认SQL有没有拼错 |
| 第二道 | 预处理器(Preprocessor) | 语义检查,表/列存不存在,有没有权限 |
| 第三道 | 优化器(Optimizer) | 生成执行计划,选定用哪个索引 |
-- EXPLAIN 查看执行计划及执行结果
EXPLAIN SELECT * FROM user WHERE id = 1;
面试考点:优化器怎么选索引?为什么有时候明明有索引却不走?
📝 面试解答:
Q: 优化器怎么选索引?为什么有时候明明有索引却不走?
优化器靠统计信息(cardinality)来估算哪个索引划算
不走索引的常见原因:
- 统计信息过期——
ANALYZE TABLE刷新一下统计信息- 索引选择性低——比如性别字段加索引,全表扫描更快
- 隐式类型转换——
WHERE name = 123,name 是 varchar 但给了 int- 回表成本高——优化器估算回表代价太大,不如直接全表扫描划算
后续会详细讲 EXPLAIN 各个字段的含义
第三关:存储引擎层(Storage Engine Layer)
MySQL 的特点是插件式存储引擎,想用啥就用啥。
-- 查看支持的存储引擎及执行结果
> SHOW ENGINES;
+------------------+---------+-------------------------------------------------------------------+--------------+------+------------+
| Engine | Support | Comment | Transactions | XA | Savepoints |
+------------------+---------+-------------------------------------------------------------------+--------------+------+------------+
| MEMORY | YES | Hash based, stored in memory, useful for temporary tables | NO | NO | NO |
| InnoDB | DEFAULT | Supports transactions, row-level locking, and foreign keys | YES | YES | YES |
| PERFORMANCE_SCHEMA | YES | Performance Schema | NO | NO | NO |
| MyISAM | YES | MyISAM storage engine | NO | NO | NO |
| MRG_MYISAM | YES | Collection of identical MyISAM tables | NO | NO | NO |
| BLACKHOLE | YES | /dev/null storage engine (anything you write to it disappears) | NO | NO | NO |
| CSV | YES | CSV storage engine | NO | NO | NO |
| ARCHIVE | YES | Archive storage engine | NO | NO | NO |
+------------------+---------+-------------------------------------------------------------------+--------------+------+------------+
-- 查看表使用的存储引擎及执行结果
> SHOW TABLE STATUS FROM schema_name LIKE 't1'\G
LIKE 't1'\G
*************************** 1. row ***************************
Name: t1
Engine: InnoDB
Version: 10
Row_format: Dynamic
Rows: 1841
Avg_row_length: 863
Data_length: 1589248
Max_data_length: 0
Index_length: 81920
Data_free: 6291456
Auto_increment: 1866
Create_time: 2026-01-13 16:53:50
Update_time: NULL
Check_time: NULL
Collation: utf8mb4_general_ci
Checksum: NULL
Create_options: row_format=DYNAMIC
Comment:
💡 说白了:存储引擎就是"仓库管理员",InnoDB 是现在的标配,很久以前的 MyISAM 是老江湖。
实战案例
场景一:连接数打满了怎么办?
-- 查看连接详情及执行结果
SHOW FULL PROCESSLIST;
常见问题:
- 连接数打满:
Too many connections - 解决思路:调大
max_connections或在中间件层使用连接池
场景二:查看 SQL 的执行计划
-- 查看SQL执行计划及执行结果
> EXPLAIN SELECT * FROM user WHERE user_name = 'Tom';
+----+-------------+----------+------------+-------+---------------+-----------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+-------+---------------+-----------+---------+-------+------+----------+-------+
| 1 | SIMPLE | user | NULL | const | user_name | user_name | 1023 | const | 1 | 100.00 | NULL |
+----+-------------+----------+------------+-------+---------------+-----------+---------+-------+------+----------+-------+
这是优化器生成的"作业指导书",面试必考,后续单独写一篇关于执行计划的文章,详细讲。
避坑指南
⚠️ 真实踩过的坑:
连接不是越多越好
- 每个连接占用内存,过多连接会导致 OOM
- 生产环境务必使用连接池(HikariCP、Druid)
短连接 vs 长连接
- 高并发场景用连接池,避免频繁建立/断开连接
- 短连接适合低频场景
wait_timeout 别太长
- 默认8小时,连接占用不释放
- 推荐设置 30分钟以内,配置的时候是整数,单位为秒
思考题
🤔 互动时间:
- 如果你的 SQL 没有走索引,通过什么参数可以看到?
max_connections设置 2000 行不行?有什么风险?
总结
🎯 面试考点
- MySQL 三层架构:连接层 → SQL层 → 存储引擎层
- 每个连接对应一个线程,线程模型
max_connections、wait_timeout参数调优- 优化器基于统计信息选索引,索引失效常见原因
- EXPLAIN 执行计划是面试必考
下期预告:存储引擎的选择:InnoDB vs MyISAM —— 面试必问!
【合集】:每天一个MySQL知识点,百日打怪升级