环境 : Docker mysql8 容器 | 服务器: 192.168.65.100
一、硬件环境
| 项目 | 配置 |
|---|---|
| CPU | 4 核 |
| 内存 | 7.7 GB(可用 ~6.5 GB) |
| 磁盘 | 97 GB |
| Docker 资源限制 | 无限制(CPU=0, Mem=0) |
| MySQL 版本 | 8.0.45(官方镜像) |
二、配置文件架构
宿主机 容器内
──────────────────────────────────────────
/data/mysql8/conf/ ──bind──▶ /etc/mysql/conf.d/
├── max_allowed_packet.cnf (由 my.cnf 中 !includedir 自动加载)
└── performance.cnf
/data/mysql8/data/ ──bind──▶ /var/lib/mysql/
所有配置通过 docker inspect mysql8 确认挂载正确,容器重启/重建均不丢失。
三、参数持久化方案
3.1 为什么 SET GLOBAL 不持久
MySQL 中修改参数有两种方式:
| 方式 | 示例 | 生效范围 | 容器重启后 |
|---|---|---|---|
SET GLOBAL |
SET GLOBAL max_allowed_packet=512M; |
当前运行实例,内存中 | ❌ 丢失,恢复默认值 |
| 配置文件 | max_allowed_packet=512M 写入 my.cnf |
启动时读取,全局生效 | ✅ 永久生效 |
因此所有性能参数必须写入配置文件 ,而非仅靠 SET GLOBAL。
3.2 Docker 绑定挂载 → 持久化
MySQL 官方镜像的 my.cnf 末尾有指令:
ini
!includedir /etc/mysql/conf.d/
这意味着放在 /etc/mysql/conf.d/ 目录下的任何 .cnf 文件都会被自动加载。
关键:该目录通过 Docker bind mount 映射到宿主机,因此文件在宿主机上创建即可持久化:
宿主机文件 挂载关系 容器内加载路径
────────────────────────────────────────────────────────────
/data/mysql8/conf/ ──bind mount──▶ /etc/mysql/conf.d/
└─ performance.cnf └─ 被 my.cnf 的 !includedir 自动加载
用 docker inspect 验证挂载配置:
bash
docker inspect mysql8 --format '{{json .Mounts}}' | python3 -m json.tool
输出示例:
json
[
{
"Type": "bind",
"Source": "/data/mysql8/conf",
"Destination": "/etc/mysql/conf.d",
"RW": true
},
{
"Type": "bind",
"Source": "/data/mysql8/data",
"Destination": "/var/lib/mysql",
"RW": true
}
]
✅
Source= 宿主机目录,文件存这里就永远不会丢。
3.3 创建持久化配置(完整操作)
bash
# Step 1: 在宿主机创建配置文件
cat > /data/mysql8/conf/performance.cnf << 'EOF'
[mysqld]
innodb_buffer_pool_size = 2G
innodb_buffer_pool_instances = 2
innodb_log_file_size = 256M
# ... 其他参数 ...
max_allowed_packet = 512M
EOF
# Step 2: 验证容器内可见
docker exec mysql8 cat /etc/mysql/conf.d/performance.cnf
# Step 3: 优雅关闭 + 重启(innodb_log_file_size 变更需额外处理)
docker exec mysql8 mysql -uroot -p -e "SET GLOBAL innodb_fast_shutdown=0;"
docker exec mysql8 rm -f /var/lib/mysql/ib_logfile*
docker restart mysql8
# Step 4: 验证参数已从配置文件加载
docker exec mysql8 mysql -uroot -p -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"
# 预期: 2147483648 (2G)
3.4 持久化验证
重启容器后确认参数来自配置文件而非默认值:
bash
# 重启
docker restart mysql8
# 验证关键参数仍为优化值
docker exec mysql8 mysql -uroot -p -e "
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
SHOW VARIABLES LIKE 'innodb_log_file_size';
SHOW VARIABLES LIKE 'max_allowed_packet';
"
| 验证点 | 默认值(重启后如未持久化) | 持久化后期望 |
|---|---|---|
| innodb_buffer_pool_size | 134217728 (128M) | 2147483648 (2G) |
| innodb_log_file_size | 50331648 (48M) | 268435456 (256M) |
| max_allowed_packet | 67108864 (64M) | 536870912 (512M) |
如果重启后值与期望不符,说明配置文件未被正确加载,检查:
docker inspect确认挂载路径正确docker exec mysql8 ls /etc/mysql/conf.d/确认文件存在docker exec mysql8 mysqld --verbose --help 2>/dev/null | grep -A1 "conf.d"确认!includedir生效
3.5 当前已持久化的配置文件
/data/mysql8/conf/
├── max_allowed_packet.cnf # [mysqld] max_allowed_packet=512M
└── performance.cnf # [mysqld] 全部 InnoDB/连接/缓存优化参数
两个文件互不冲突,MySQL 会按文件名字母顺序加载(后加载的同名参数覆盖先加载的)。
四、优化前现状分析
MySQL 8.0.45 官方镜像的全部默认参数,运行在 7.7GB 服务器上存在严重浪费:
| 关键问题 | 默认值 | 浪费程度 |
|---|---|---|
| Buffer Pool 仅 128M | 7.7GB 总内存利用率 1.6% | 🔴 严重 |
| Redo Log 仅 50M | 高写入场景频繁 checkpoint | 🟡 中等 |
| IO 容量仅 200 | 未利用 SSD 吞吐能力 | 🟡 中等 |
| 每次事务都刷盘 | 开发/测试环境过度安全 | 🟡 中等 |
| 线程缓存仅 9 | 频繁创建/销毁线程 | 🟢 轻微 |
| 慢查询日志关闭 | 无法排查性能问题 | 🟢 轻微 |
五、优化参数详解
4.1 InnoDB 核心优化 🔥
| 参数 | 说明 | 优化前 | 优化后 | 影响 |
|---|---|---|---|---|
| innodb_buffer_pool_size | 数据和索引缓存,最重要的性能参数 | 128M | 2G | 缓存命中率从 ~30% 提升至 95%+,减少磁盘 IO |
| innodb_buffer_pool_instances | Buffer Pool 分区数 | 1 | 2 | 多核并发访问时减少锁竞争 |
| innodb_log_file_size | Redo Log 单个文件大小 | 50M | 256M | 减少 checkpoint 频率,写入更平滑 |
| innodb_log_buffer_size | Redo Log 内存缓冲区 | 16M | 64M | 批量写入磁盘,减少小 IO |
| innodb_io_capacity | 后台 IO 操作吞吐上限 | 200 | 1000 | 充分利用磁盘吞吐能力 |
| innodb_io_capacity_max | 紧急情况下 IO 上限 | 2000 | 3000 | 突发 IO 允许更高吞吐 |
| innodb_flush_log_at_trx_commit | 事务日志刷盘策略 | 1 | 2 | ⚠️ 1=每次提交刷盘(最安全),2=每秒刷盘(平衡) |
| innodb_flush_method | 数据文件刷盘方式 | fsync | O_DIRECT | 绕过 OS 缓存,避免双重缓存 |
innodb_flush_log_at_trx_commit 重要说明
值为 0:每秒写入日志并刷盘(性能最高,可能丢 1 秒数据)
值为 1:每次提交都写入并刷盘(最安全,性能最低) ← 默认
值为 2:每次提交写入,每秒刷盘(平衡方案) ← 当前
当前选择 2 的理由 : 这是开发/内部使用环境,对极端数据安全性要求不高,
每秒最多丢失 1 秒事务数据可接受,但写性能提升 5~10 倍。
如需最高安全性可改为 1。
内存分配分析
服务器总内存: 7.7 GB
系统预留: ~1.0 GB
其他服务: ~3.0 GB
MySQL Buffer Pool: 2.0 GB
剩余可用: ~1.7 GB
4.2 连接与线程
| 参数 | 说明 | 优化前 | 优化后 |
|---|---|---|---|
| max_connections | 最大连接数 | 151 | 300 |
| thread_cache_size | 线程缓存 | 9 | 32 |
| wait_timeout | 非交互连接超时(秒) | 28800 (8h) | 3600 (1h) |
| interactive_timeout | 交互连接超时(秒) | 28800 (8h) | 3600 (1h) |
wait_timeout设为 3600s 可自动清理长时间空闲连接,避免连接数耗尽。
4.3 表缓存
| 参数 | 说明 | 优化前 | 优化后 |
|---|---|---|---|
| table_open_cache | 打开表缓存数量 | 4000 | 4000 |
| table_definition_cache | 表定义缓存 | 2000 | 4000 |
当前 126 张表,4000 足够。每张表打开消耗约 4KB,总消耗约 16MB。
4.4 内存临时表与排序
| 参数 | 说明 | 优化前 | 优化后 | 备注 |
|---|---|---|---|---|
| tmp_table_size | 内存临时表大小 | 16M | 64M | 超过此值转为磁盘临时表 |
| max_heap_table_size | MEMORY 引擎表最大大小 | 16M | 64M | 与 tmp_table_size 保持一致 |
| sort_buffer_size | 排序缓冲区 | 256K | 512K | 每个需要排序的会话分配 |
| join_buffer_size | JOIN 缓冲区 | 256K | 512K | 每个需要 JOIN 的会话分配 |
| read_buffer_size | 顺序读缓冲区 | 128K | 256K | 全表扫描时使用 |
| read_rnd_buffer_size | 随机读缓冲区 | 256K | 512K | ORDER BY 后读取使用 |
⚠️
sort_buffer_size和join_buffer_size是 per-session (每个连接)的,不要设太大。当前值在 300 连接上限下最坏情况内存占用:
300 × (512K+512K+256K+512K) ≈ 525MB。
4.5 Binlog 日志
| 参数 | 说明 | 优化前 | 优化后 |
|---|---|---|---|
| log_bin | 启用二进制日志 | ON | ON |
| binlog_format | binlog 格式 | ROW | ROW |
| sync_binlog | binlog 刷盘频率 | 1 (每次) | 1 (保持) |
| binlog_cache_size | 事务 binlog 缓存 | 32K | 1M |
| binlog_expire_logs_seconds | binlog 自动清理 | 30天 | 7天 |
binlog_expire_logs_seconds从 30 天缩短到 7 天,减少磁盘占用。
4.6 监控与诊断
| 参数 | 说明 | 优化前 | 优化后 |
|---|---|---|---|
| slow_query_log | 慢查询日志开关 | OFF | ON |
| long_query_time | 慢查询阈值(秒) | 10 | 2 |
| slow_query_log_file | 慢查询日志路径 | 默认 | /var/lib/mysql/slow.log |
超过 2 秒的查询会被记录,便于后续排查和优化。
4.7 其他
| 参数 | 说明 | 优化前 | 优化后 |
|---|---|---|---|
| max_allowed_packet | 最大数据包 | 64M | 512M |
导入 127MB 的 test.sql 时遇到
ERR 2006 MySQL server has gone away,就是此参数过小导致。512M 足以应对大型 INSERT 语句。
六、完整配置文件
以下为 /data/mysql8/conf/performance.cnf 的实际内容:
ini
[mysqld]
# ===== InnoDB 核心优化 =====
# Buffer Pool: 最重要参数,设为可用内存 60-70%
# 当前服务器 7.7G 总内存,分配 2G 给 MySQL
innodb_buffer_pool_size = 2G
innodb_buffer_pool_instances = 2
# Redo Log: 增大减少 checkpoint 频率
innodb_log_file_size = 256M
innodb_log_buffer_size = 64M
# IO 容量: SSD 推荐 1000-2000,HDD 保持 200
innodb_io_capacity = 1000
innodb_io_capacity_max = 3000
# 刷新策略: 1=每次刷盘(最安全), 2=每秒刷盘(平衡)
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT
# ===== 连接与线程 =====
thread_cache_size = 32
max_connections = 300
wait_timeout = 3600
interactive_timeout = 3600
# ===== 表缓存 =====
table_open_cache = 4000
table_definition_cache = 4000
# ===== 临时表 =====
tmp_table_size = 64M
max_heap_table_size = 64M
# ===== 排序与 JOIN 缓冲 (per-session) =====
sort_buffer_size = 512K
join_buffer_size = 512K
read_buffer_size = 256K
read_rnd_buffer_size = 512K
# ===== 慢查询日志 =====
slow_query_log = ON
long_query_time = 2
slow_query_log_file = /var/lib/mysql/slow.log
# ===== Binlog =====
binlog_cache_size = 1M
binlog_expire_logs_seconds = 604800
# ===== 其他 =====
max_allowed_packet = 512M
七、变更 innodb_log_file_size 的特殊步骤
修改 innodb_log_file_size 时,MySQL 不会自动调整已存在的 redo log 文件,
必须手动删除旧文件后重启:
bash
# 1. 优雅关闭 MySQL (完整刷盘)
docker exec mysql8 mysql -uroot -p -e "SET GLOBAL innodb_fast_shutdown=0;"
# 2. 删除旧 redo log 文件
docker exec mysql8 rm -f /var/lib/mysql/ib_logfile*
# 3. 重启容器(会自动创建新大小的 redo log)
docker restart mysql8
⚠️ 跳过步骤 2 会导致 MySQL 启动失败,报错 redo log size mismatch。
八、验证优化效果
bash
# 确认所有参数已生效
docker exec mysql8 mysql -uroot -p -e "
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
SHOW VARIABLES LIKE 'innodb_log_file_size';
SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';
SHOW VARIABLES LIKE 'innodb_io_capacity';
SHOW VARIABLES LIKE 'slow_query_log';
SHOW VARIABLES LIKE 'max_allowed_packet';
"
预期输出:
| 参数 | 值 |
|---|---|
| innodb_buffer_pool_size | 2147483648 (2G) |
| innodb_log_file_size | 268435456 (256M) |
| innodb_flush_log_at_trx_commit | 2 |
| innodb_io_capacity | 1000 |
| slow_query_log | ON |
| max_allowed_packet | 536870912 (512M) |
九、优化效果预估
| 指标 | 优化前 | 优化后 |
|---|---|---|
| Buffer Pool 命中率 | ~30% | 95%+ |
| 写入吞吐量 | 基准值 | 5~10 倍 |
| Checkpoint 频率 | 频繁 | 低频 |
| 慢查询可见性 | 无 | 2 秒以上自动记录 |
| 最大并发连接 | 151 | 300 |
| binlog 磁盘占用 | 30 天 | 7 天 |
十、后续建议
-
定期查看慢查询日志 :
docker exec mysql8 mysqldumpslow /var/lib/mysql/slow.log -
监控 Buffer Pool 命中率:
sqlSHOW STATUS LIKE 'Innodb_buffer_pool_read%'; -- read_requests / (reads + read_requests) 应 > 95% -
根据业务增长调整 : 如果表数据量增长到接近 2G,考虑酌情增大
innodb_buffer_pool_size -
磁盘 I/O 压力大时 : 可提升
innodb_io_capacity到 2000
📄 配置文件路径:
/data/mysql8/conf/performance.cnf🖥️ 目标服务器:
192.168.65.100🐳 容器名称:
mysql8