切换语言
切换主题

Docker部署MySQL完全指南:数据持久化到主从复制实战

Docker MySQL deployment guide from standalone to master-slave replication

凌晨三点,我盯着终端上那行刺眼的报错,手心直冒汗。半个月的测试数据,全没了。只因为下午重启了一次MySQL容器。

说实话,我当时真的慌了。用Docker部署MySQL不是挺简单的吗?网上随便一搜,docker run -e MYSQL_ROOT_PASSWORD=123456 mysql 就能跑起来。我天真地以为这样就行了,直到那个深夜——容器重启,数据消失,世界崩塌。

你有没有遇到过类似的情况?或者是这些场景:想改个MySQL字符集,不知道怎么挂载配置文件;本地应用连接容器死活连不上,报错信息看得头大;生产环境要搞主从复制,Docker环境下完全不知道从哪下手。

老实讲,Docker MySQL的坑,我几乎全踩过一遍。这篇文章就是用来填坑的——从最基础的数据持久化,到配置文件挂载,再到生产级的主从复制,每个配置都是我验证过可以直接用的。

话不多说,咱们直接开干。

Docker MySQL单机部署基础

最简单的启动方式(以及为什么不推荐)

先说个最常见的误区。很多人第一次用Docker装MySQL,会直接这样:

docker run --name mysql-test -e MYSQL_ROOT_PASSWORD=123456 -d mysql:8.0

跑起来了,容器状态正常,进去能操作数据库,一切看起来完美。但这是个定时炸弹。

为什么?容器的本质是临时的。你删掉容器,或者哪天手滑重启了一下,数据就全没了。MySQL默认把数据存在容器内的 /var/lib/mysql 目录,容器一删,这个目录也跟着消失。

我第一次踩这个坑时,还以为是MySQL出了bug。后来才明白,不是MySQL的问题,是我压根没做数据持久化。

数据持久化:挂载volume的正确姿势

说白了,数据持久化就是把MySQL的数据目录映射到宿主机,容器挂了数据还在。

Docker提供三种挂载方式:

  1. bind mount:直接映射宿主机的某个目录,比如 /home/mysql/data
  2. named volume:由Docker管理的卷,不用关心实际存储位置
  3. tmpfs:用内存存储,重启就没,基本不用

2024年Docker官方推荐用named volume,性能已经和bind mount没啥区别了。我个人两种都用,看场景:开发环境用named volume省心,生产环境用bind mount方便备份。

直接上完整命令:

docker run --name mysql-persistent \
  -e MYSQL_ROOT_PASSWORD=rootpwd123 \
  -p 3306:3306 \
  -v mysql-data:/var/lib/mysql \
  -d mysql:8.0

这里 -v mysql-data:/var/lib/mysql 就是关键。mysql-data 是volume名字(Docker会自动创建),/var/lib/mysql 是MySQL容器内的数据目录。

验证一下是不是真的持久化了:

# 进容器创建个数据库
docker exec -it mysql-persistent mysql -uroot -prootpwd123 -e "CREATE DATABASE testdb;"

# 停掉容器并删除
docker stop mysql-persistent
docker rm mysql-persistent

# 用同样的volume重新启动容器
docker run --name mysql-persistent \
  -e MYSQL_ROOT_PASSWORD=rootpwd123 \
  -p 3306:3306 \
  -v mysql-data:/var/lib/mysql \
  -d mysql:8.0

# 进去查看,testdb还在
docker exec -it mysql-persistent mysql -uroot -prootpwd123 -e "SHOW DATABASES;"

看到 testdb 还在,就说明数据真的保住了。这种感觉,踏实。

配置文件挂载:自定义MySQL参数

数据持久化搞定了,下一个问题:怎么改MySQL配置?

比如你想把字符集改成 utf8mb4,或者调整最大连接数。如果不挂载配置文件,每次都得进容器手动改,重启容器又得改一次,烦死了。

MySQL的配置文件读取路径是 /etc/mysql/conf.d/,我们把自定义的 my.cnf 挂到这个目录就行。

先在宿主机创建配置文件:

mkdir -p /home/mysql/conf
cat > /home/mysql/conf/my.cnf << 'EOF'
[mysqld]
# 字符集设置
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci

# 连接数设置
max_connections=1000

# 认证插件(解决部分客户端连接问题)
default_authentication_plugin=mysql_native_password

[client]
default-character-set=utf8mb4
EOF

然后启动容器时挂载这个配置文件:

docker run --name mysql-custom \
  -e MYSQL_ROOT_PASSWORD=rootpwd123 \
  -p 3306:3306 \
  -v /home/mysql/conf:/etc/mysql/conf.d \
  -v /home/mysql/data:/var/lib/mysql \
  -d mysql:8.0

注意这里同时挂载了两个目录:配置文件和数据目录。

验证配置是否生效:

docker exec -it mysql-custom mysql -uroot -prootpwd123 -e "SHOW VARIABLES LIKE 'character%';"

如果看到 character_set_serverutf8mb4,说明配置已经生效了。

解决外部连接问题

容器起来了,数据也持久化了,配置也挂上了。现在你想用本地的应用连接这个MySQL,结果——

ERROR 2003 (HY000): Can't connect to MySQL server on 'localhost' (Connection refused)

或者

ERROR 1045 (28000): Access denied for user 'root'@'172.17.0.1'

这两个错误,我当初也遇到过。排查了好久才搞明白。

问题1:Connection refused

这个多半是端口没映射对。启动容器时一定要加 -p 3306:3306,把容器的3306端口映射到宿主机的3306端口。

如果你宿主机的3306端口已经被占用了(比如本地已经装了MySQL),可以映射到其他端口:

-p 3307:3306  # 宿主机用3307访问,容器内还是3306

还有一种情况是防火墙拦截了。如果是Linux服务器,检查一下防火墙:

# CentOS/RHEL
sudo firewall-cmd --zone=public --add-port=3306/tcp --permanent
sudo firewall-cmd --reload

# Ubuntu
sudo ufw allow 3306/tcp

问题2:Access denied

这个是权限问题。MySQL默认创建的root用户只允许从 localhost 连接,外部IP连不上。

解决办法是修改root用户的host为 %(允许任何IP连接):

# 进入容器
docker exec -it mysql-custom mysql -uroot -prootpwd123

# 执行以下SQL
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'rootpwd123';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;
FLUSH PRIVILEGES;

这里要注意两点:

  1. mysql_native_password 这个认证插件兼容性更好,一些老版本的MySQL客户端需要这个
  2. 生产环境千万别这么干,不要让root用户从任何IP都能连,要创建专门的用户并限制IP

改完后,外部应用应该就能连上了。

第一部分到这就差不多了。这几个问题搞定,基本的Docker MySQL单机部署就没啥大坑了。

使用Docker Compose优雅管理

为什么推荐用Docker Compose

如果你只是本地开发测试,前面那些 docker run 命令也够用了。但实际工作中,你会发现几个问题:

  1. 命令太长,每次都要翻记录找之前的命令
  2. 参数容易搞错,比如volume路径写错了
  3. 团队协作时,大家的启动命令不一样,配置混乱
  4. 需要多个容器协同工作时(比如MySQL + Redis + Nginx),一个个启动太麻烦

这时候Docker Compose就派上用场了。

说白了,Docker Compose就是把你那一长串 docker run 命令,写到一个YAML配置文件里。以后启动只需要 docker-compose up -d,停掉只需要 docker-compose down,配置文件还能放到Git里版本管理。

对了,团队里新来的同事问你”怎么启动MySQL”,你直接甩给他一个 docker-compose.yml,他 git clone 下来一键启动,不用问你半句。这感觉,真爽。

Docker Compose单机MySQL配置

直接上完整配置,我逐行解释:

version: '3.8'

services:
  mysql:
    image: mysql:8.0
    container_name: mysql-standalone
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: rootpwd123
      MYSQL_DATABASE: myapp
      MYSQL_USER: appuser
      MYSQL_PASSWORD: apppwd123
    ports:
      - "3306:3306"
    volumes:
      - mysql-data:/var/lib/mysql
      - ./conf/my.cnf:/etc/mysql/conf.d/my.cnf
      - ./logs:/var/log/mysql
    networks:
      - mysql-network
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-prootpwd123"]
      interval: 10s
      timeout: 5s
      retries: 3

volumes:
  mysql-data:

networks:
  mysql-network:
    driver: bridge

逐项解释一下关键配置:

environment部分

  • MYSQL_ROOT_PASSWORD:root密码,必填
  • MYSQL_DATABASE:容器启动时自动创建的数据库
  • MYSQL_USERMYSQL_PASSWORD:自动创建的普通用户(比root更安全)

volumes部分

  • mysql-data:/var/lib/mysql:数据持久化,named volume方式
  • ./conf/my.cnf:/etc/mysql/conf.d/my.cnf:挂载配置文件,用相对路径
  • ./logs:/var/log/mysql:日志目录挂载,方便排查问题

restart: always:容器挂了自动重启,服务器重启后也会自动启动容器

healthcheck:健康检查,定期ping MySQL,如果挂了会自动重启

networks:自定义网络,如果你有多个容器需要互相通信,都加入这个网络就行

使用步骤:

  1. 创建目录结构:
mkdir -p mysql-docker/{conf,logs}
cd mysql-docker
  1. 创建配置文件 conf/my.cnf
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
max_connections=1000
default_authentication_plugin=mysql_native_password

[client]
default-character-set=utf8mb4
  1. 创建 docker-compose.yml(就是上面那个完整配置)

  2. 启动:

docker-compose up -d
  1. 查看状态:
docker-compose ps

你会看到类似这样的输出:

       Name                     Command                  State                 Ports
------------------------------------------------------------------------------------------------
mysql-standalone      docker-entrypoint.sh mysqld      Up (healthy)    0.0.0.0:3306->3306/tcp

注意那个 (healthy) 状态,说明healthcheck通过了。

  1. 查看日志:
docker-compose logs -f mysql

-f 参数是实时跟踪日志,类似 tail -f。如果启动有问题,日志里基本都能找到原因。

  1. 停止并删除容器:
docker-compose down

注意:这只会删除容器,不会删除volume(也就是数据不会丢)。如果你想连数据一起删,用:

docker-compose down -v  # 慎用!会删除所有数据

说实话,我现在本地开发基本都用Docker Compose了。配置写一次,以后启停都是一条命令,省心。

生产级主从复制配置

MySQL主从复制原理简述

先说说为什么要搞主从复制。

单机MySQL在小项目里够用,但业务量一大就扛不住了。主从复制主要解决两个问题:

  1. 读写分离:主库负责写(INSERT、UPDATE、DELETE),从库负责读(SELECT)。大部分应用都是读多写少,把读请求分散到多个从库,性能立马上去
  2. 数据备份和高可用:主库挂了,从库还能顶上,至少读服务不受影响

原理不复杂:

  • 主库(Master)开启binlog日志,记录所有数据变更操作
  • 从库(Slave)连接到主库,读取binlog内容
  • 从库有两个线程工作:IO线程负责从主库拉取binlog并保存到relay log,SQL线程负责执行relay log里的SQL语句
  • 这样主库的数据变更就同步到从库了

Docker环境下配置主从,核心就是:

  1. 两个容器各自配置不同的server-id
  2. 主库开启binlog
  3. 从库连接主库并开启复制

听起来挺复杂,实际上用Docker Compose配置完,启动就行,没想象中那么难。

Master节点配置

先配置主库。主库需要做三件事:开启binlog、设置server-id、创建复制用户。

  1. 创建主库配置文件 conf/master.cnf
[mysqld]
# 唯一服务器ID,主从不能重复
server-id=1

# 开启二进制日志
log-bin=mysql-bin

# binlog格式,ROW模式记录每一行的变化,更安全
binlog-format=ROW

# 字符集
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci

# 可选:设置要同步的数据库(不设置则同步所有)
# binlog-do-db=myapp

# 可选:设置不同步的数据库
# binlog-ignore-db=mysql
# binlog-ignore-db=information_schema
  1. 主库的docker-compose配置:
version: '3.8'

services:
  mysql-master:
    image: mysql:8.0
    container_name: mysql-master
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: rootpwd123
      MYSQL_DATABASE: myapp
    ports:
      - "3306:3306"
    volumes:
      - master-data:/var/lib/mysql
      - ./conf/master.cnf:/etc/mysql/conf.d/master.cnf
      - ./logs/master:/var/log/mysql
    networks:
      - mysql-replication

volumes:
  master-data:

networks:
  mysql-replication:
    driver: bridge
  1. 启动主库:
docker-compose up -d mysql-master
  1. 创建复制用户:

进入主库容器,创建一个专门用于复制的用户:

docker exec -it mysql-master mysql -uroot -prootpwd123

执行以下SQL:

-- 创建复制用户
CREATE USER 'repl'@'%' IDENTIFIED WITH mysql_native_password BY 'replpwd123';

-- 授予复制权限
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';

-- 刷新权限
FLUSH PRIVILEGES;

-- 查看主库状态,记录File和Position
SHOW MASTER STATUS;

SHOW MASTER STATUS 会输出类似这样的结果:

+------------------+----------+--------------+------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000003 |      156 |              |                  |
+------------------+----------+--------------+------------------+

重点:把 FilePosition 这两个值记下来,配置从库时要用。

Slave节点配置

从库配置相对简单一些。

  1. 创建从库配置文件 conf/slave.cnf
[mysqld]
# 唯一服务器ID,必须和主库不同
server-id=2

# 中继日志
relay-log=relay-bin

# 从库只读(防止误操作写入)
read-only=1

# 字符集
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
  1. 更新docker-compose.yml,添加从库配置:
version: '3.8'

services:
  mysql-master:
    image: mysql:8.0
    container_name: mysql-master
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: rootpwd123
      MYSQL_DATABASE: myapp
    ports:
      - "3306:3306"
    volumes:
      - master-data:/var/lib/mysql
      - ./conf/master.cnf:/etc/mysql/conf.d/master.cnf
      - ./logs/master:/var/log/mysql
    networks:
      - mysql-replication

  mysql-slave:
    image: mysql:8.0
    container_name: mysql-slave
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: rootpwd123
    ports:
      - "3307:3306"  # 注意:宿主机用3307访问从库
    volumes:
      - slave-data:/var/lib/mysql
      - ./conf/slave.cnf:/etc/mysql/conf.d/slave.cnf
      - ./logs/slave:/var/log/mysql
    networks:
      - mysql-replication
    depends_on:
      - mysql-master

volumes:
  master-data:
  slave-data:

networks:
  mysql-replication:
    driver: bridge
  1. 启动从库:
docker-compose up -d mysql-slave
  1. 配置从库连接主库:

进入从库容器:

docker exec -it mysql-slave mysql -uroot -prootpwd123

执行以下SQL(注意替换File和Position为你刚才记录的值):

-- 配置主库连接信息
CHANGE MASTER TO
  MASTER_HOST='mysql-master',           -- 主库容器名(Docker网络内可直接用容器名)
  MASTER_PORT=3306,                     -- 主库端口
  MASTER_USER='repl',                   -- 复制用户
  MASTER_PASSWORD='replpwd123',         -- 复制用户密码
  MASTER_LOG_FILE='mysql-bin.000003',   -- 主库binlog文件(从SHOW MASTER STATUS获取)
  MASTER_LOG_POS=156;                   -- 主库binlog位置(从SHOW MASTER STATUS获取)

-- 启动从库复制
START SLAVE;

-- 查看从库状态
SHOW SLAVE STATUS\G

验证主从同步

SHOW SLAVE STATUS\G 会输出一大堆信息,重点关注这几个字段:

Slave_IO_Running: Yes       # IO线程运行状态,必须是Yes
Slave_SQL_Running: Yes      # SQL线程运行状态,必须是Yes
Seconds_Behind_Master: 0    # 从库延迟秒数,0表示实时同步
Last_IO_Error:              # IO错误信息,为空表示正常
Last_SQL_Error:             # SQL错误信息,为空表示正常

如果 Slave_IO_RunningSlave_SQL_Running 都是 Yes,恭喜你,主从复制已经成功了!

测试一下是不是真的同步:

  1. 在主库创建测试数据:
docker exec -it mysql-master mysql -uroot -prootpwd123 -e "
USE myapp;
CREATE TABLE test_table (id INT PRIMARY KEY, name VARCHAR(50));
INSERT INTO test_table VALUES (1, 'test data');
"
  1. 在从库查询:
docker exec -it mysql-slave mysql -uroot -prootpwd123 -e "
USE myapp;
SELECT * FROM test_table;
"

如果能查到刚才插入的数据,说明主从同步正常工作了。

这种感觉,就像看着自己的代码在生产环境跑起来一样——爽!

常见问题排查

主从复制虽然不难,但第一次配置时还是容易遇到问题。我把常见的坑列一下:

问题1:Slave_IO_Running是No

可能原因:

  • 网络不通:检查容器是否在同一网络,用 docker exec -it mysql-slave ping mysql-master 测试
  • 用户权限不对:检查主库的repl用户是否创建成功,权限是否正确
  • binlog文件名或位置错误:重新执行 SHOW MASTER STATUS 确认

问题2:Slave_SQL_Running是No

可能原因:

  • SQL执行出错:查看 Last_SQL_Error 字段,根据错误信息排查
  • 主从数据不一致:如果主库在配置前就有数据,需要先导出主库数据导入从库,再配置主从

问题3:Seconds_Behind_Master一直很大

可能原因:

  • 从库性能不足:检查从库CPU、内存、磁盘IO
  • 主库写入太频繁:考虑增加从库数量分散压力
  • 网络带宽不足:检查网络延迟

如果遇到问题解决不了,可以重置从库重新配置:

-- 在从库执行
STOP SLAVE;
RESET SLAVE;

-- 然后重新执行CHANGE MASTER TO和START SLAVE

性能优化与最佳实践

Docker MySQL性能优化建议

很多人担心Docker会拖慢MySQL性能。说实话,早几年确实有这个问题,但2024年的数据显示,Docker对MySQL的性能影响已经降到5%以内了,I/O密集型场景下几乎感觉不到差异。

不过话说回来,该优化还是要优化。几个实用建议:

1. Volume类型选择

前面提到过named volume和bind mount。性能上已经没啥区别了,但有个小细节:

  • named volume:Docker管理,自动选择最优存储驱动,开发环境推荐
  • bind mount:直接映射宿主机目录,生产环境推荐(方便备份、监控)
# named volume方式
volumes:
  - mysql-data:/var/lib/mysql

# bind mount方式
volumes:
  - /data/mysql:/var/lib/mysql

2. 网络模式选择

默认的bridge网络已经够用,但如果你对性能要求极致,可以试试host网络模式:

services:
  mysql:
    network_mode: "host"  # 直接使用宿主机网络,性能更好

注意:用host模式后,ports 映射就不需要了,容器直接监听宿主机的3306端口。

3. 资源限制配置

生产环境一定要限制资源,防止MySQL容器吃光服务器资源:

services:
  mysql:
    image: mysql:8.0
    deploy:
      resources:
        limits:
          cpus: '2'        # 最多用2个CPU核心
          memory: 2G       # 最多用2G内存
        reservations:
          memory: 1G       # 保证至少1G内存

这个配置需要用 docker-compose --compatibility up 启动(或者用Docker Swarm模式)。

4. 日志管理

MySQL容器的日志如果不限制,时间长了会占满磁盘。加个日志限制:

services:
  mysql:
    logging:
      driver: "json-file"
      options:
        max-size: "100m"    # 单个日志文件最大100M
        max-file: "3"       # 最多保留3个日志文件

生产环境最佳实践清单

这部分是我踩坑总结出来的,强烈建议照着做。

安全建议

  1. 不要使用默认或简单密码
# ❌ 别这么干
MYSQL_ROOT_PASSWORD: 123456

# ✅ 用强密码,或者用Docker secrets
MYSQL_ROOT_PASSWORD: "Mx8#kL9$pQ2@vN4!"
  1. 使用Docker secrets管理敏感信息
services:
  mysql:
    image: mysql:8.0
    secrets:
      - mysql_root_password
    environment:
      MYSQL_ROOT_PASSWORD_FILE: /run/secrets/mysql_root_password

secrets:
  mysql_root_password:
    file: ./secrets/mysql_root_password.txt
  1. 限制容器网络访问
networks:
  mysql-network:
    driver: bridge
    internal: true  # 仅容器间通信,禁止外部访问

如果需要外部访问,用专门的应用容器做代理,不要直接暴露MySQL容器。

运维建议

  1. 定期备份数据目录
# 简单粗暴的备份脚本
#!/bin/bash
DATE=$(date +%Y%m%d_%H%M%S)
docker exec mysql-master mysqldump -uroot -prootpwd123 --all-databases > backup_$DATE.sql

# 或者直接备份数据目录
tar -czf mysql-data-backup_$DATE.tar.gz /data/mysql/

生产环境建议每天自动备份,至少保留7天的备份。

  1. 配置healthcheck监控容器状态
healthcheck:
  test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p$$MYSQL_ROOT_PASSWORD"]
  interval: 10s
  timeout: 5s
  retries: 3
  start_period: 30s

容器挂了会自动重启,配合监控告警更完美。

  1. 日志volume单独挂载
volumes:
  - ./logs:/var/log/mysql

日志单独挂出来,出问题时方便排查,也不会占用数据volume的空间。

高可用建议

  1. 至少一主一从,建议一主多从

读多写少的场景,一个主库配3-5个从库,读请求分散到从库。

  1. 配合负载均衡实现读写分离

可以用MySQL Router、ProxySQL、或者应用层实现:

  • 写操作(INSERT/UPDATE/DELETE)→ 主库
  • 读操作(SELECT)→ 从库(负载均衡)
  1. 定期测试主从切换流程

不要等主库真挂了才发现从库顶不上。定期演练主从切换:

-- 从库执行
STOP SLAVE;
RESET SLAVE ALL;
SET GLOBAL read_only=0;  -- 取消只读,提升为主库

生产环境建议用成熟的高可用方案(如MHA、Orchestrator)自动切换。

结论

说了这么多,咱们回顾一下这篇文章的核心内容。

从最基础的Docker MySQL单机部署,到数据持久化、配置文件挂载、外部连接问题解决,再到Docker Compose优雅管理,最后到生产级的主从复制配置——这一整套流程,基本覆盖了Docker MySQL部署的所有常见场景。

几个关键要点再强调一下:

  1. 数据持久化是必须的,别再用裸容器跑MySQL了,血的教训
  2. 配置文件挂载很重要,字符集、连接数这些参数得提前配好
  3. Docker Compose是趋势,配置集中管理,团队协作友好
  4. 主从复制并不难,关键是配置文件和步骤要对
  5. 生产环境要注意安全和备份,密码别用弱口令,数据定期备份

文章里的所有配置代码都是我验证过可以直接用的。你可以直接复制到你的项目里,改改密码和端口就能跑。

如果你是第一次用Docker部署MySQL,建议从单机部署开始,先把数据持久化和配置挂载搞明白,再去折腾主从复制。一步步来,别着急。

遇到问题了也别慌,大部分问题都能在日志里找到原因。搞不定的话,欢迎评论区交流,我看到会回复。

最后说一句,Docker部署MySQL真的比传统方式省心太多了。配置一次,到处运行,这种感觉——真香。

13 分钟阅读 · 发布于: 2025年12月18日 · 修改于: 2025年12月26日

评论

使用 GitHub 账号登录后即可评论

相关文章