MySQL 高并发场景下的性能优化

MySQL 的性能优化并不是简单地给表增加几个索引。

在真实的高并发系统中,数据库性能通常受到多个因素共同影响:

应用请求
   ↓
数据库连接
   ↓
SQL 执行
   ↓
索引
   ↓
Buffer Pool
   ↓
锁 / 事务
   ↓
磁盘 I/O

当并发量继续增加以后,还会遇到:

连接数过高
SQL 执行时间增加
锁竞争
事务堆积
Buffer Pool 压力
磁盘 I/O
CPU 使用率
Redo Log 压力

因此,高并发 MySQL 优化应该从整个访问链路出发,而不是只盯着 SQL。


1. 高并发下 MySQL 为什么容易成为瓶颈

假设一个系统每秒有:

5000 QPS

其中:

80% 查询
20% 写入

那么数据库每秒需要处理:

4000 次查询
1000 次写入

如果单条 SQL 平均执行:

10 ms

看起来并不算慢。

但当大量请求同时进入数据库后,数据库真正面对的是:

大量连接
    ↓
大量 SQL
    ↓
大量索引访问
    ↓
大量数据页
    ↓
大量事务
    ↓
锁竞争
    ↓
磁盘 / CPU / 内存压力

因此:

高并发问题本质上不是某一个 SQL 慢,而是数据库有限资源被大量请求同时竞争。

MySQL 常见瓶颈主要包括:

  • CPU;

  • 内存;

  • Buffer Pool;

  • 磁盘 I/O;

  • 数据库连接;

  • 锁;

  • Redo Log;

  • 临时表;

  • 排序;

  • 网络。

优化时应该先找到真正的瓶颈,再针对性处理。


2. 第一层优化:减少数据库请求

高并发系统最有效的优化之一往往不是:

“让 SQL 更快。”

而是:

让数据库少执行一些 SQL。

例如一个接口:

GET /user/profile

如果每次请求都查询:

用户基本信息
用户配置
用户权限
用户统计
用户等级

一次请求可能产生:

5 次 SQL

1000 个请求就可能产生:

5000 次 SQL

如果其中一部分数据变化不频繁,就可以使用 Redis 等缓存:

请求
 ↓
Redis
 ↓
命中 → 返回
 ↓
未命中
 ↓
MySQL

例如:

用户配置
系统配置
地区信息
商品基础信息
热点文章
权限信息

都可以根据业务特征考虑缓存。

但缓存不是数据库优化的万能答案

缓存会引入:

缓存一致性
缓存失效
缓存穿透
缓存击穿
缓存雪崩

因此应该根据数据特点选择:

强一致数据
→ 数据库为主

读多写少
→ 可以考虑缓存

热点数据
→ 优先考虑缓存

一个重要原则是:

缓存的核心价值是减少数据库访问,而不是简单地把数据库换成 Redis。


3. SQL 与索引仍然是基础

高并发环境下,一条低效 SQL 的影响会被放大。

例如:

SELECT *
FROM orders
WHERE user_id = 100
ORDER BY created_at DESC;

如果:

user_id
created_at

没有合理索引,大量并发请求可能不断产生:

扫描
排序
回表
磁盘访问

优化应该先使用:

EXPLAIN

查看执行计划。

例如考虑:

CREATE INDEX idx_user_created
ON orders(user_id, created_at);

再重新执行:

EXPLAIN
SELECT *
FROM orders
WHERE user_id = 100
ORDER BY created_at DESC;

观察:

key
rows
Extra

等信息。

前面的文章已经详细介绍过 EXPLAIN、JOIN、深分页和索引设计,这些基础优化在高并发环境中仍然成立。

尤其要注意:

不要因为系统进入高并发,就开始无脑增加索引。

索引虽然可以加快查询,但也会增加:

INSERT
UPDATE
DELETE

时的索引维护成本。


4. 控制数据库连接数

数据库连接也是一种有限资源。

例如 MySQL:

max_connections = 500

并不意味着:

500 个连接越多越好

如果大量连接同时执行 SQL:

500 Connections
      ↓
500 SQL
      ↓
CPU
Memory
Disk I/O
Lock

数据库很容易进入高负载状态。

更合理的思路是:

应用
 ↓
连接池
 ↓
有限数据库连接
 ↓
MySQL

应用侧使用连接池,可以避免每一个请求都不断创建和销毁数据库连接。

但是连接池也不能无限扩大。

例如:

Web Server
1000 并发请求
        ↓
连接池最大 50
        ↓
MySQL

这意味着最多只有一定数量的请求同时访问数据库,其余请求在应用层等待连接。

这实际上是一种并发控制。

因此:

数据库连接池不是越大越好,而应该根据 MySQL 的 CPU、I/O、SQL 平均执行时间和业务并发进行压测确定。


5. 减少事务和锁竞争

高并发系统中非常容易出现:

事务等待
↓
锁等待
↓
请求堆积
↓
连接占满
↓
数据库负载继续增加

例如:

START TRANSACTION;

SELECT ...
FOR UPDATE;

-- 大量业务逻辑

UPDATE ...;

COMMIT;

如果事务持续时间很长,就意味着锁可能长期持有。

更合理的是:

准备数据
↓
BEGIN
↓
执行必要 SQL
↓
COMMIT

尽量避免在事务内部执行:

HTTP 请求
RPC
文件操作
复杂计算
Sleep
用户交互

这些操作都会扩大事务持续时间。

一个重要原则

事务应该尽量做到:

短、小、明确。

例如库存扣减:

UPDATE products
SET stock = stock - 1
WHERE id = 100
  AND stock > 0;

然后通过:

affected_rows

判断扣减是否成功。

相比:

SELECT stock
↓
PHP 判断
↓
UPDATE stock

这种方式可以减少应用层和数据库之间的交互,同时降低并发窗口。


6. 合理利用 Buffer Pool

InnoDB 的 Buffer Pool 是高并发性能的重要基础。

如果大量热点数据能够留在内存:

SQL
 ↓
Buffer Pool
 ↓
数据页

就可以减少磁盘 I/O。

反之,如果 Buffer Pool 不足:

SQL
 ↓
Buffer Pool Miss
 ↓
磁盘 I/O
 ↓
读取 Page
 ↓
返回数据

大量并发下可能造成明显的 I/O 压力。

因此需要关注:

innodb_buffer_pool_size

以及 Buffer Pool 的实际使用情况。

对于专用数据库服务器,Buffer Pool 通常应该占据较大的内存比例,但不能机械套用一个固定百分比。

因为还需要给:

MySQL 其他内存
操作系统
连接
排序
临时表

预留空间。

Buffer Pool 不是越大越好

如果服务器内存:

32 GB

不能简单认为:

innodb_buffer_pool_size = 32G

还需要考虑整个 MySQL 进程以及操作系统的内存需求。

更合理的方式是:

确定服务器内存
↓
评估数据集大小
↓
观察 Buffer Pool 命中情况
↓
结合实际负载调整
↓
压测验证

7. 写入压力:Redo Log 与磁盘 I/O

高并发写入场景下,数据库瓶颈可能从 SQL 转向磁盘和日志。

例如:

大量 INSERT
大量 UPDATE
大量 DELETE
        ↓
修改 Buffer Pool
        ↓
Redo Log
        ↓
磁盘 I/O

如果写入速度非常高,就需要关注:

Redo Log
Dirty Page
Checkpoint
磁盘 IOPS
磁盘延迟

尤其是事务提交时的日志持久化策略。

例如:

innodb_flush_log_at_trx_commit

它会影响事务提交时 Redo Log 的刷盘行为。

在对数据持久性要求较高的 OLTP 系统中,不能为了性能简单关闭严格的持久化策略。

因为:

数据库性能优化不能以牺牲业务要求的数据安全为代价。

如果写入压力已经超过单机能力,就应该考虑从架构层面解决:

批量写入
 ↓
异步化
 ↓
削峰
 ↓
读写分离
 ↓
分库分表

而不是无限调数据库参数。


8. 读写分离解决读压力

当系统具有明显的:

读多写少

特点时,可以考虑 MySQL 主从复制 + 读写分离。

架构可以设计为:

                Application
                     │
             ┌───────┴───────┐
             ↓               ↓
           Write            Read
             ↓               ↓
           Master         Replica
                              │
                        ┌─────┴─────┐
                        ↓           ↓
                     Replica 1   Replica 2

写请求:

INSERT
UPDATE
DELETE

进入主库。

查询请求:

SELECT

根据业务情况进入 Replica。

这样可以把读压力分散到多个数据库实例。

但是读写分离有一个非常重要的问题:

主从复制存在延迟。

例如:

写入主库
 ↓
主库成功
 ↓
立即查询从库
 ↓
从库可能还没有同步

这时候用户可能看到旧数据。

因此:

强一致查询
→ 主库

允许短暂延迟
→ 从库

必须根据业务场景决定。

例如:

支付结果
账户余额
订单状态刚刚变更

通常不能简单地认为所有查询都可以发送到从库。


9. 高并发架构下的综合优化

当单条 SQL 已经优化、索引合理、服务器资源也比较充足后,如果系统继续增长,就应该从架构层面优化。

可以逐渐形成:

                 Client
                    ↓
               Load Balancer
                    ↓
               Application
                    │
        ┌───────────┼───────────┐
        ↓           ↓           ↓
      Cache       Queue       MySQL
        │                       │
        │               ┌───────┴───────┐
        │               ↓               ↓
        │             Master         Replica
        │               │               │
        └───────────────┴───────────────┘

各组件负责不同的问题:

Redis

减少热点数据对 MySQL 的访问:

Cache

消息队列

削峰和异步化:

Queue

例如:

用户请求
 ↓
写入消息队列
 ↓
立即返回
 ↓
Worker
 ↓
MySQL

但这种方案会改变业务一致性模型,不能简单应用于所有写操作。

Replica

分担查询压力:

Read Scaling

分库分表

当单库、单表达到架构边界后:

Database
 ↓
Sharding

但分库分表会显著增加系统复杂度,因此不应该在数据量还很小时提前引入。


10. 总结

MySQL 高并发优化应该按照从简单到复杂的顺序进行。

第一步:

减少不必要的数据库请求

第二步:

优化 SQL
优化索引
减少扫描

第三步:

控制连接
优化连接池

第四步:

缩短事务
减少锁竞争
避免死锁

第五步:

合理配置 Buffer Pool
减少磁盘 I/O

第六步:

缓存热点数据

第七步:

读写分离

最后才考虑:

分库分表

可以把整个优化思路总结成:

                    MySQL 高并发优化
                           │
        ┌──────────────────┼──────────────────┐
        ↓                  ↓                  ↓
      请求量              SQL                资源
        │                  │                  │
      缓存              索引              Buffer Pool
      MQ                 JOIN              CPU
      批处理             分页              Disk I/O
                         EXPLAIN
                            │
                            ↓
                       事务与锁
                            │
                     ┌──────┴──────┐
                     ↓             ↓
                  短事务         少锁竞争
                     │             │
                     └──────┬──────┘
                            ↓
                       架构扩展
                            │
                   ┌────────┼────────┐
                   ↓        ↓        ↓
                 Replica   Cache   Sharding

最重要的不是记住某个参数应该设置成多少,而是建立正确的优化顺序:

先减少请求,再优化 SQL;先解决明显瓶颈,再考虑架构扩展。

同时,高并发优化一定要建立在监控、慢查询日志、执行计划和压力测试的基础上。

没有数据支撑的“优化”,很容易变成单纯修改参数。

当单机 MySQL 已经通过 SQL、索引、事务、缓存等方式优化到一定程度后,下一阶段就是解决数据库实例之间的数据复制与读扩展问题。

MySQL 主从复制原理与实战