用 Compose 部署一个可上生产基线的 MySQL:数据/配置/日志/备份目录分离并持久化,端口只绑回环,root 密码走 .env(chmod 600),带健康检查、资源限额、utf8mb4、ROW 格式 binlog、慢查询日志。
示例锁定
mysql:8.0(新项目首选)。文末附 5.7 的差异说明。
一、目录结构
/opt/mysql/
├── docker-compose.yml
├── .env # 600,root/业务密码
└── mysql/
├── conf/my.cnf
├── data/ # 数据目录,chown 999:999
├── logs/ # chown 999:999
└── backup/ # 备份输出
sudo mkdir -p /opt/mysql/mysql/{conf,data,logs,backup}
sudo chown -R 999:999 /opt/mysql/mysql/{data,logs}
MySQL 官方镜像内 mysql 用户 uid 固定为 999,不预 chown 会因权限不足启动失败。
二、.env
umask 077
cat > /opt/mysql/.env <<'EOF'
MYSQL_ROOT_PASSWORD=替换为强密码
MYSQL_DATABASE=appdb
MYSQL_USER=app
MYSQL_PASSWORD=替换为强密码
TZ=Asia/Shanghai
EOF
chmod 600 /opt/mysql/.env
强密码生成:openssl rand -base64 24 | tr -d '/+='。
三、docker-compose.yml
name: mysql-stack
x-logging: &default-logging
driver: json-file
options:
max-size: "50m"
max-file: "5"
services:
mysql:
image: mysql:8.0.40
container_name: mysql8
restart: unless-stopped
logging: *default-logging
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
TZ: ${TZ}
ports:
# 只绑回环;远程访问走 SSH 隧道,不对公网暴露 3306
- "127.0.0.1:3306:3306"
volumes:
- ./mysql/data:/var/lib/mysql
- ./mysql/conf/my.cnf:/etc/mysql/conf.d/my.cnf:ro
- ./mysql/logs:/var/log/mysql
- ./mysql/backup:/backup
security_opt:
- no-new-privileges:true
mem_limit: 2g
mem_reservation: 1g
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -p\"$$MYSQL_ROOT_PASSWORD\" --silent"]
interval: 10s
timeout: 5s
retries: 6
start_period: 30s
注意:compose 里引用容器自身的环境变量要写 $$MYSQL_ROOT_PASSWORD(双美元符号转义)。
四、my.cnf
mysql/conf/my.cnf(按 2G 容器限额的 4C 主机示例):
[mysqld]
# ---------- 基础 ----------
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
default-storage-engine = InnoDB
default-time-zone = '+08:00'
skip-name-resolve = 1
# lower_case_table_names 必须在首次初始化前设定;8.0 初始化后不可改,默认 0
# ---------- 连接 ----------
max_connections = 500
max_allowed_packet = 64M
wait_timeout = 28800
interactive_timeout = 28800
table_open_cache = 2000
open_files_limit = 65535
# ---------- InnoDB(buffer pool 约为容器内存的 50%)----------
innodb_buffer_pool_size = 1G
innodb_buffer_pool_instances= 2
innodb_redo_log_capacity = 256M
innodb_flush_log_at_trx_commit = 1
innodb_flush_method = O_DIRECT
innodb_file_per_table = 1
innodb_io_capacity = 1000
innodb_io_capacity_max = 2000
# ---------- 日志 ----------
log_error = /var/log/mysql/error.log
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
log_queries_not_using_indexes = 1
# ---------- Binlog(备份恢复/主从基础)----------
server_id = 1
log_bin = mysql-bin
binlog_format = ROW
binlog_expire_logs_seconds = 604800 # 7 天(5.7 用 expire_logs_days=7)
# ---------- 严格模式 ----------
sql_mode = STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
[client]
default-character-set = utf8mb4
[mysql]
default-character-set = utf8mb4
五、启动与验证
cd /opt/mysql
docker compose config -q
docker compose up -d
# 首次初始化要 20~40 秒,等待 healthy
docker compose ps
docker compose logs -f mysql
验证字符集、端口、连通性:
docker exec mysql8 mysql -uroot -p"$(grep MYSQL_ROOT_PASSWORD .env | cut -d= -f2)" \
-e "show variables like 'character_set_server'; select version();"
ss -ltnp | grep 3306 # 应为 127.0.0.1:3306,而不是 0.0.0.0
远程连接一律走 SSH 隧道(在你自己的电脑执行):
ssh -L 3306:127.0.0.1:3306 user@203.0.113.10
# 然后本地客户端连 127.0.0.1:3306
六、日常运维与备份
docker compose ps
docker compose restart mysql
docker compose pull && docker compose up -d # 小版本升级(先备份!)
# 手动全量备份(一致性快照,不锁表)
docker exec mysql8 sh -c \
'mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --single-transaction --routines --events \
--all-databases | gzip > /backup/all-$(date +%F).sql.gz'
# 恢复
gunzip < /backup/all-2026-09-20.sql.gz | docker exec -i mysql8 mysql -uroot -p"密码"
加一条宿主机 crontab,每天 2:30 备份并保留 14 天:
30 2 * * * cd /opt/mysql && docker exec mysql8 sh -c 'mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --single-transaction --routines --events --all-databases | gzip > /backup/all-$(date +\%F).sql.gz' && find /opt/mysql/mysql/backup -name 'all-*.sql.gz' -mtime +14 -delete
备份文件应再同步到对象存储或另一台机器,数据目录和备份放在同一块盘不等于有备份。
七、MySQL 5.7 的差异
如果历史系统必须用 5.7(5.7 官方已 EOL,新项目不要选):
- 镜像换
mysql:5.7.44,容器名按需改; - binlog 过期用
expire_logs_days = 7,没有innodb_redo_log_capacity,用innodb_log_file_size = 256M; - 如需保持旧客户端兼容,启动命令加
--default-authentication-plugin=mysql_native_password。
八、常见问题
| 现象 | 原因与处理 |
|---|---|
容器反复重启,日志 Permission denied / Can't create/write to file |
data 或 logs 目录属主不对,chown -R 999:999 |
Expression #1 of ORDER BY is not in GROUP BY |
严格 sql_mode=ONLY_FULL_GROUP_BY 生效;正确做法是修 SQL,不建议全局关严格模式 |
| 健康检查一直 starting | 首次初始化较慢,start_period 内属正常;超期看 error.log,常见于内存不足或 data 目录是从其它大版本直接挂来 |
| 外部连不上 3306 | 设计如此(只绑回环)。确需开放请改用云安全组限来源 IP,并给业务账号限定 host,不要对 0.0.0.0 开放 |
| 改了 my.cnf 不生效 | docker compose restart mysql;启动参数(command)才需要 up -d 重建 |
| 升级大版本(5.7→8.0)启动失败 | 大版本不能直接换镜像挂旧数据目录,应先用 mysqldump 逻辑迁移到全新初始化的 8.0 |
| 磁盘告警 | binlog 已设 7 天自动清理;另确认 json-file 日志轮转生效,慢日志定期归档 |
此处评论已关闭