目录
用 Docker 跑 MySQL:安装、连接、备份与还原
一条命令启动
先准备一个文件夹 mysql-demo,里面的 initdb/ 放两个初始化用的 SQL 文件(后面会说明):
mysql-demo/
└── initdb/
├── 01-schema.sql
└── 02-seed.sql
initdb/01-schema.sql:
CREATE TABLE products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
price INT NOT NULL
);
initdb/02-seed.sql:
INSERT INTO products (name, price) VALUES ('珍珠奶茶', 65), ('鳳梨酥', 45), ('牛肉麵', 180);
在 mysql-demo 文件夹中启动 MySQL:
docker run -d --name mysql \
-e MYSQL_ROOT_PASSWORD=secret \
-e MYSQL_DATABASE=app \
-e MYSQL_USER=app \
-e MYSQL_PASSWORD=apppass \
-p 127.0.0.1:3306:3306 \
-v mysql_data:/var/lib/mysql \
-v "$(pwd)/initdb:/docker-entrypoint-initdb.d:ro" \
--restart unless-stopped \
mysql:8.4
docker run -d --name mysql `
-e MYSQL_ROOT_PASSWORD=secret `
-e MYSQL_DATABASE=app `
-e MYSQL_USER=app `
-e MYSQL_PASSWORD=apppass `
-p 127.0.0.1:3306:3306 `
-v mysql_data:/var/lib/mysql `
-v "${PWD}/initdb:/docker-entrypoint-initdb.d:ro" `
--restart unless-stopped `
mysql:8.4
docker run -d --name mysql ^
-e MYSQL_ROOT_PASSWORD=secret ^
-e MYSQL_DATABASE=app ^
-e MYSQL_USER=app ^
-e MYSQL_PASSWORD=apppass ^
-p 127.0.0.1:3306:3306 ^
-v mysql_data:/var/lib/mysql ^
-v "%cd%/initdb:/docker-entrypoint-initdb.d:ro" ^
--restart unless-stopped ^
mysql:8.4
$ docker run -d --name mysql -e MYSQL_ROOT_PASSWORD=secret -e MYSQL_DATABASE=app -e MYSQL_USER=app -e MYSQL_PASSWORD=apppass -p 127.0.0.1:3306:3306 -v mysql_data:/var/lib/mysql -v "$(pwd)/initdb:/docker-entrypoint-initdb.d:ro" --restart unless-stopped mysql:8.4
Unable to find image 'mysql:8.4' locally
8.4: Pulling from library/mysql
e896da32f635: Pull complete
3527249a8025: Pull complete
6b54d15c954d: Pull complete
15ba404f5528: Pull complete
ad73434ffdad: Pull complete
bf11ec3e7e56: Pull complete
81077ec0e5ac: Pull complete
f51a9a1f0d20: Pull complete
4ba189089539: Pull complete
d4cc7515d7b7: Pull complete
Digest: sha256:24989179f58856db763f418010c1169fecc944e3ec1ea7e921572d83eb070f70
Status: Downloaded newer image for mysql:8.4
d92a7561ad58f958488c606bb07753aeffcc06639878a4c87ed676f6e1f99389
对应 Windows 的写法(点击标签切换):
docker run -d --name mysql -e MYSQL_ROOT_PASSWORD=secret -e MYSQL_DATABASE=app -e MYSQL_USER=app -e MYSQL_PASSWORD=apppass -p 127.0.0.1:3306:3306 -v mysql_data:/var/lib/mysql -v "$(pwd)/initdb:/docker-entrypoint-initdb.d:ro" --restart unless-stopped mysql:8.4
docker run -d --name mysql -e MYSQL_ROOT_PASSWORD=secret -e MYSQL_DATABASE=app -e MYSQL_USER=app -e MYSQL_PASSWORD=apppass -p 127.0.0.1:3306:3306 -v mysql_data:/var/lib/mysql -v "${PWD}/initdb:/docker-entrypoint-initdb.d:ro" --restart unless-stopped mysql:8.4
docker run -d --name mysql -e MYSQL_ROOT_PASSWORD=secret -e MYSQL_DATABASE=app -e MYSQL_USER=app -e MYSQL_PASSWORD=apppass -p 127.0.0.1:3306:3306 -v mysql_data:/var/lib/mysql -v "%cd%/initdb:/docker-entrypoint-initdb.d:ro" --restart unless-stopped mysql:8.4
| 参数 | 说明 |
|---|---|
MYSQL_ROOT_PASSWORD | root 密码(必填,或改用 MYSQL_RANDOM_ROOT_PASSWORD=yes 随机生成) |
MYSQL_DATABASE | 第一次启动时创建的数据库 |
MYSQL_USER / MYSQL_PASSWORD | 创建一般用户,并给予 MYSQL_DATABASE 的完整权限 |
-p 127.0.0.1:3306:3306 | 只开放给本机,外部连不到 |
-v mysql_data:/var/lib/mysql | 数据存在名为 mysql_data 的 Volume |
-v .../initdb:/docker-entrypoint-initdb.d:ro | 初始化时要运行的 SQL |
第一次启动做了什么
$ docker logs mysql 2>&1 | grep -E "Entrypoint|initdb|ready for connections"
2026-10-13 00:46:59+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.4.11-1.el9 started.
2026-10-13 00:46:59+00:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql'
2026-10-13 00:46:59+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.4.11-1.el9 started.
2026-10-13 00:46:59+00:00 [Note] [Entrypoint]: Initializing database files
2026-10-13 00:47:02+00:00 [Note] [Entrypoint]: Database files initialized
2026-10-13 00:47:02+00:00 [Note] [Entrypoint]: Starting temporary server
2026-10-13T00:47:04.184489Z 0 [System] [MY-011323] [Server] X Plugin ready for connections. Socket: /var/run/mysqld/mysqlx.sock
2026-10-13T00:47:04.185012Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.4.11' socket: '/var/run/mysqld/mysqld.sock' port: 0 MySQL Community Server - GPL.
2026-10-13 00:47:04+00:00 [Note] [Entrypoint]: Temporary server started.
2026-10-13 00:47:06+00:00 [Note] [Entrypoint]: Creating database app
2026-10-13 00:47:06+00:00 [Note] [Entrypoint]: Creating user app
2026-10-13 00:47:06+00:00 [Note] [Entrypoint]: Giving user app access to schema app
2026-10-13 00:47:06+00:00 [Note] [Entrypoint]: /usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/01-schema.sql
2026-10-13 00:47:06+00:00 [Note] [Entrypoint]: /usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/02-seed.sql
2026-10-13 00:47:06+00:00 [Note] [Entrypoint]: Stopping temporary server
2026-10-13 00:47:07+00:00 [Note] [Entrypoint]: Temporary server stopped
2026-10-13 00:47:07+00:00 [Note] [Entrypoint]: MySQL init process done. Ready for start up.
2026-10-13T00:47:08.596982Z 0 [System] [MY-011323] [Server] X Plugin ready for connections. Bind-address: '::' port: 33060, socket: /var/run/mysqld/mysqlx.sock
2026-10-13T00:47:08.597330Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.4.11' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server - GPL.
从日志可以看到镜像的启动脚本在第一次启动(文件夹是空的)时依序:
- 创建数据库文件(
Initializing database files)。 - 启动一个暂时的服务器(
Starting temporary server)。注意它的port: 0:暂时服务器不开放网络连接,只能用 socket 连接。 - 创建数据库
app、用户app并授权。 - 依文件名顺序运行
/docker-entrypoint-initdb.d/中的01-schema.sql、02-seed.sql。 - 停止暂时服务器,以正式设置启动,最后一行的
port: 3306才表示真正可以连接了。
整个过程大约 10 秒。/docker-entrypoint-initdb.d/ 支持 .sql、.sql.gz、.sh 文件,而且只在第一次初始化时运行,之后重启容器不会再运行。
版本选择
| tag | 说明 |
|---|---|
mysql:8.4 | 长期支持版(LTS),建议用于生产环境 |
mysql:9 | 创新版,功能较新、支持期较短 |
mariadb:11 | MariaDB,与 MySQL 高度兼容的分支,环境变量改为 MARIADB_ 开头 |
连接
从容器内
容器内就有 mysql 用户端。交互式操作用 docker exec -it mysql mysql -uapp -p,输入密码后进入 mysql> 提示字符。也可以用 -e 直接运行 SQL:
$ docker exec mysql mysql -uapp -papppass app -t -e "SELECT * FROM products;"
mysql: [Warning] Using a password on the command line interface can be insecure.
+----+--------------+-------+
| id | name | price |
+----+--------------+-------+
| 1 | 珍珠奶茶 | 65 |
| 2 | 鳳梨酥 | 45 |
| 3 | 牛肉麵 | 180 |
+----+--------------+-------+
$ docker exec mysql mysql -uroot -psecret -t -e "SELECT VERSION(), @@character_set_server, @@collation_server;"
mysql: [Warning] Using a password on the command line interface can be insecure.
+-----------+------------------------+--------------------+
| VERSION() | @@character_set_server | @@collation_server |
+-----------+------------------------+--------------------+
| 8.4.11 | utf8mb4 | utf8mb4_0900_ai_ci |
+-----------+------------------------+--------------------+
-t以表格格式输出(非交互模式下默认是以 Tab 分隔)。- 中文数据正确访问,服务器字符集是
utf8mb4、排序规则是utf8mb4_0900_ai_ci,这是 MySQL 8 的默认值,不需要另外设置。中文那几列的框线没有对齐,是mysql用户端计算中文宽度的方式造成的,数据本身没有问题。 - 每次都出现的
[Warning] Using a password on the command line interface can be insecure.是提醒密码写在命令中可能被其他人从进程列表看到,示范时可以忽略。
从主机:localhost 与 127.0.0.1 的差别
在主机上使用任何 MySQL 用户端(MySQL Workbench、DBeaver、mysql 命令)连到 127.0.0.1:3306。请写 127.0.0.1 而不是 localhost。用 --network host 的容器仿真主机上的 mysql 用户端来实测:
$ docker run --rm --network host mysql:8.4 mysql -h localhost -uapp -papppass -e "SELECT 1"
mysql: [Warning] Using a password on the command line interface can be insecure.
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)
$ docker run --rm --network host mysql:8.4 mysql -h 127.0.0.1 -uapp -papppass -e "SELECT USER(), CURRENT_USER()"
mysql: [Warning] Using a password on the command line interface can be insecure.
USER() CURRENT_USER()
app@172.18.0.1 app@%
-h localhost:失败,Can't connect to local MySQL server through socket。MySQL 用户端看到localhost时会改用 Unix socket 文件连接,而不是 TCP,但主机上并没有那个 socket 文件(它在容器里)。-h 127.0.0.1:成功,走 TCP 经过-p对应的端口。USER()显示app@172.18.0.1:从 MySQL 的角度,连接来自 Docker 网络的闸道 IP,而不是127.0.0.1。CURRENT_USER()的app@%表示这个账号允许从任何主机连接。
从其他容器
把 MySQL 和应用程序放在同一个自定义网络中,就能用容器名称连接(默认的 bridge 网络不能用名称解析,参考 网络与容器互连):
$ docker network create appnet
595dfaad48e7460c5996b90a62402e50e31fb790a1f04b3cdc67ac12be482db6
$ docker network connect appnet mysql
$ docker run --rm --network appnet mysql:8.4 mysql -h mysql -uapp -papppass app -e "SELECT COUNT(*) FROM products"
mysql: [Warning] Using a password on the command line interface can be insecure.
COUNT(*)
3
应用程序的连接字符串写成:
mysql://app:apppass@mysql:3306/app
主机名称是容器名称 mysql(Compose 中则是服务名称),端口是容器内的 3306。容器之间连接不需要 -p。
用 Compose 管理
实际项目建议用 Compose 管理。在 mysql-demo 中加入 compose.yaml、.env 和自定义设置文件 my.cnf:
mysql-demo/
├── compose.yaml
├── .env
├── my.cnf
└── initdb/
├── 01-schema.sql
└── 02-seed.sql
.env:
MYSQL_ROOT_PASSWORD=change-this-root-password
MYSQL_PASSWORD=change-this-app-password
my.cnf:
[mysqld]
max_connections = 300
innodb_buffer_pool_size = 256M
compose.yaml:
services:
db:
image: mysql:8.4
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: app
MYSQL_USER: app
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
TZ: Asia/Taipei
ports:
- "127.0.0.1:3306:3306"
volumes:
- db_data:/var/lib/mysql
- ./initdb:/docker-entrypoint-initdb.d:ro
- ./my.cnf:/etc/mysql/conf.d/custom.cnf:ro
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -p\"$$MYSQL_ROOT_PASSWORD\" --silent"]
interval: 10s
retries: 10
start_period: 30s
restart: unless-stopped
volumes:
db_data:
| 设置 | 说明 |
|---|---|
TZ: Asia/Taipei | 设置容器时区,MySQL 的 NOW() 会使用这个时区 |
./my.cnf:/etc/mysql/conf.d/custom.cnf | MySQL 会读取 /etc/mysql/conf.d/ 中的 .cnf 文件 |
$$MYSQL_ROOT_PASSWORD | $$ 让 Compose 不要自己替换变量,留给容器内的 shell 读取容器的环境变量 |
-h 127.0.0.1 | 重要,见下方说明 |
$ docker compose up -d --wait
[+] up 3/3
✔ Network mysql-demo_default Created 0.0s
✔ Volume mysql-demo_db_data Created 0.0s
✔ Container mysql-demo-db-1 Healthy 10.9s
$ docker compose exec db sh -c 'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SELECT 1"'
mysql: [Warning] Using a password on the command line interface can be insecure.
1
1
$ docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
mysql-demo-db-1 mysql:8.4 "docker-entrypoint.s…" db 12 seconds ago Up 11 seconds (healthy) 127.0.0.1:3306->3306/tcp, 33060/tcp
$ docker compose exec db sh -c 'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -t -e "SELECT @@max_connections, @@innodb_buffer_pool_size DIV 1048576 AS buffer_pool_mb, @@system_time_zone, NOW();"'
mysql: [Warning] Using a password on the command line interface can be insecure.
+-------------------+----------------+--------------------+---------------------+
| @@max_connections | buffer_pool_mb | @@system_time_zone | NOW() |
+-------------------+----------------+--------------------+---------------------+
| 300 | 256 | CST | 2026-10-13 08:49:41 |
+-------------------+----------------+--------------------+---------------------+
$ docker compose exec db sh -c 'mysql -uapp -p"$MYSQL_PASSWORD" app -t -e "SELECT * FROM products"'
mysql: [Warning] Using a password on the command line interface can be insecure.
+----+--------------+-------+
| id | name | price |
+----+--------------+-------+
| 1 | 珍珠奶茶 | 65 |
| 2 | 鳳梨酥 | 45 |
| 3 | 牛肉麵 | 180 |
+----+--------------+-------+
--wait等到服务变成 healthy 才返回,Healthy花了约 11 秒,与前面看到的初始化时间相符。max_connections是 300、buffer_pool_mb是 256,my.cnf生效了。@@system_time_zone是CST(中原标准时间,UTC+8),NOW()显示的是台北时间。- 初始化 SQL 一样有运行,
products有三笔数据。
健康检查的陷阱:localhost 会太早报告 healthy
网络上常见的 MySQL 健康检查写法是 mysqladmin ping -h localhost。实测把上面的 127.0.0.1 改成 localhost:
$ docker compose up -d --wait
[+] up 3/3
✔ Network mysql-demo_default Created 0.0s
✔ Volume mysql-demo_db_data Created 0.0s
✔ Container mysql-demo-db-1 Healthy 5.8s
$ docker compose exec db sh -c 'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SELECT 1"'
mysql: [Warning] Using a password on the command line interface can be insecure.
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)
$ docker compose logs db 2>&1 | grep -E "Temporary server|init process done|port: 3306"
db-1 | 2026-10-13 08:49:02+08:00 [Note] [Entrypoint]: Temporary server started.
$ docker compose exec db sh -c 'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SELECT 1"'
mysql: [Warning] Using a password on the command line interface can be insecure.
1
1
- 只过了 5.8 秒 就报告
Healthy,但紧接着用 root 连接却失败,日志显示这时还停在Temporary server started。 - 10 秒后再试才成功。
原因是:初始化期间的暂时服务器只能通过 socket 连接,而 -h localhost 正好使用 socket,所以 ping 得到了回应;而且 mysqladmin ping 只要服务器有回应就算成功,即使账号密码还无法登录。结果 Compose 以为数据库已经准备好,就启动了依赖它的服务(depends_on 的 condition: service_healthy),应用程序第一次连接就可能失败。
改成 -h 127.0.0.1 就会走 TCP,暂时服务器不开放网络连接(port: 0),所以要等正式服务器启动后才会报告 healthy。
备份与还原
用 mysqldump 导出 SQL,然后故意删掉数据表,再从备份还原:
$ docker exec mysql sh -c 'mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --single-transaction app' > app.sql
mysqldump: [Warning] Using a password on the command line interface can be insecure.
$ ls -lh app.sql && grep -c "INSERT INTO" app.sql
-rw-r--r-- 1 root root 2.0K Oct 13 00:47 app.sql
1
$ docker exec mysql mysql -uroot -psecret -e "DROP TABLE app.products"
mysql: [Warning] Using a password on the command line interface can be insecure.
$ docker exec mysql mysql -uapp -papppass app -e "SELECT COUNT(*) FROM products"
mysql: [Warning] Using a password on the command line interface can be insecure.
ERROR 1146 (42S02) at line 1: Table 'app.products' doesn't exist
$ docker exec -i mysql sh -c 'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" app' < app.sql
mysql: [Warning] Using a password on the command line interface can be insecure.
$ docker exec mysql mysql -uapp -papppass app -t -e "SELECT * FROM products"
mysql: [Warning] Using a password on the command line interface can be insecure.
+----+--------------+-------+
| id | name | price |
+----+--------------+-------+
| 1 | 珍珠奶茶 | 65 |
| 2 | 鳳梨酥 | 45 |
| 3 | 牛肉麵 | 180 |
+----+--------------+-------+
- 备份命令用
sh -c '...'包起来,$MYSQL_ROOT_PASSWORD在容器内展开,密码不会出现在主机的命令历史中。外层用单引号,避免主机的 shell 先替换变量。 --single-transaction让 InnoDB 数据表在一致的快照中导出,备份期间不会锁住数据表,生产环境一定要加。- 备份文件
app.sql是 2.0K,包含 1 个INSERT INTO命令(三笔数据写在同一个 INSERT 中)。 DROP TABLE之后查找得到Table 'app.products' doesn't exist。- 还原用
docker exec -i,把主机上的app.sql通过标准输入传给容器内的mysql,数据完整恢复。
还原时只能用 -i,不能用 -it。标准输入是文件而不是终端时,-t 会直接出错:
$ docker exec -it mysql sh -c "echo hi" < /dev/null
cannot attach stdin to a TTY-enabled container because stdin is not a terminal
其他常用的备份命令:
# 备份所有数据库(含用户与权限)
docker exec mysql sh -c 'mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --all-databases --single-transaction' > all.sql
# 备份并压缩
docker exec mysql sh -c 'mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --single-transaction app' | gzip > app.sql.gz
# 还原压缩的备份
gunzip < app.sql.gz | docker exec -i mysql sh -c 'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" app'
Windows 用户的备份与还原
PowerShell 不支持 <,而 Windows PowerShell 5.1 的 > 会把文件存成 UTF-16 编码,导入时出错。改成在容器内生成文件、再用 docker cp 拷贝,这个写法在所有系统都能用:
# 备份:在容器内导出成 /tmp/app.sql,再拷贝出来
docker exec mysql sh -c 'mysqldump -uroot -p$MYSQL_ROOT_PASSWORD --single-transaction --result-file=/tmp/app.sql app'
docker cp mysql:/tmp/app.sql app.sql
# 还原:先拷贝进去,再在容器内导入
docker cp app.sql mysql:/tmp/restore.sql
docker exec mysql sh -c 'mysql -uroot -p$MYSQL_ROOT_PASSWORD app < /tmp/restore.sql'
# 备份:在容器内导出成 /tmp/app.sql,再拷贝出来
docker exec mysql sh -c 'mysqldump -uroot -p$MYSQL_ROOT_PASSWORD --single-transaction --result-file=/tmp/app.sql app'
docker cp mysql:/tmp/app.sql app.sql
# 还原:先拷贝进去,再在容器内导入
docker cp app.sql mysql:/tmp/restore.sql
docker exec mysql sh -c 'mysql -uroot -p$MYSQL_ROOT_PASSWORD app < /tmp/restore.sql'
REM 备份:在容器内导出成 /tmp/app.sql,再拷贝出来
docker exec mysql sh -c "mysqldump -uroot -p$MYSQL_ROOT_PASSWORD --single-transaction --result-file=/tmp/app.sql app"
docker cp mysql:/tmp/app.sql app.sql
REM 还原:先拷贝进去,再在容器内导入
docker cp app.sql mysql:/tmp/restore.sql
docker exec mysql sh -c "mysql -uroot -p$MYSQL_ROOT_PASSWORD app < /tmp/restore.sql"
--result-file让 mysqldump 直接写文件,不经过终端的重定向。- 命令中的
<写在sh -c的字符串里,由容器内的 shell 处理,所以 PowerShell 也能运行。 -p$MYSQL_ROOT_PASSWORD不加引号,避免 PowerShell 5.1 处理内嵌引号的问题(密码中不要有空白)。
这组命令在 Docker 29 实测,3 笔数据完整还原:
$ docker exec mysql sh -c 'mysql -uroot -p$MYSQL_ROOT_PASSWORD app -e "CREATE TABLE t (id INT); INSERT INTO t VALUES (1),(2),(3);"'
mysql: [Warning] Using a password on the command line interface can be insecure.
$ docker exec mysql sh -c 'mysqldump -uroot -p$MYSQL_ROOT_PASSWORD --single-transaction --result-file=/tmp/app.sql app'
mysqldump: [Warning] Using a password on the command line interface can be insecure.
$ docker cp mysql:/tmp/app.sql app.sql
$ ls -l app.sql
-rw-rw-rw- 1 root root 1788 Oct 14 09:48 app.sql
$ docker exec mysql sh -c 'mysql -uroot -p$MYSQL_ROOT_PASSWORD app -e "DROP TABLE t"'
mysql: [Warning] Using a password on the command line interface can be insecure.
$ docker cp app.sql mysql:/tmp/restore.sql
$ docker exec mysql sh -c 'mysql -uroot -p$MYSQL_ROOT_PASSWORD app < /tmp/restore.sql'
mysql: [Warning] Using a password on the command line interface can be insecure.
$ docker exec mysql sh -c 'mysql -uroot -p$MYSQL_ROOT_PASSWORD app -e "SELECT COUNT(*) FROM t"'
mysql: [Warning] Using a password on the command line interface can be insecure.
COUNT(*)
3
实测:改了密码变量却没生效
MYSQL_ROOT_PASSWORD 等变量只在第一次初始化(Volume 是空的)时使用。删除容器、保留 Volume,然后用不同的密码重新创建容器:
$ docker rm -f mysql
mysql
$ docker run -d --name mysql -e MYSQL_ROOT_PASSWORD=different -v mysql_data:/var/lib/mysql mysql:8.4
2a0f52d379b574033ae1571ab6873f37e8ab79ccd3830f6feac629696b74ae42
$ docker logs mysql 2>&1 | grep -c "Initializing database"
0
$ docker exec mysql mysql -uroot -pdifferent -e "SELECT 1"
mysql: [Warning] Using a password on the command line interface can be insecure.
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)
$ docker exec mysql mysql -uroot -psecret -e "SELECT 1"
mysql: [Warning] Using a password on the command line interface can be insecure.
1
1
- 日志中
Initializing database出现 0 次:Volume 中已经有数据,启动脚本跳过了初始化。 - 新密码
different无法登录,旧密码secret仍然可以。
也就是说,数据一旦存在,修改 MYSQL_ROOT_PASSWORD 不会有任何效果。要修改密码,请用旧密码登录后运行 SQL:
ALTER USER 'root'@'%' IDENTIFIED BY '新密码';
ALTER USER 'root'@'localhost' IDENTIFIED BY '新密码';
或者在不需要保留数据时,删除 Volume 重新初始化(docker compose down -v,数据会全部消失)。
疑难排解
Access denied for user 'root'@'...'
$ docker exec mysql mysql -uroot -pwrong -e "SELECT 1"
mysql: [Warning] Using a password on the command line interface can be insecure.
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)
- 密码错误,或 Volume 是用旧密码初始化的(见上方实测)。
- root 默认有
root@%和root@localhost两个账号,允许从任何主机连接;自己创建的用户要确认它的 host 是%,否则只能从特定主机连接。
Can't connect to local MySQL server through socket
用户端把 localhost 当成 socket 连接,改用 127.0.0.1(见前面的实测)。
启动后马上退出
看日志:docker logs mysql。常见原因:
- 没有设置
MYSQL_ROOT_PASSWORD(也没有MYSQL_ALLOW_EMPTY_PASSWORD或MYSQL_RANDOM_ROOT_PASSWORD)。 - Volume 中有其他版本(例如 5.7 或 MariaDB)初始化的数据,与目前版本不兼容。
- 主机内存不足。
从 5.7 / 8.0 升级
不要直接把旧的 Volume 挂到新版镜像。建议先用 mysqldump 导出,再导入新版本的空数据库,并先在测试环境验证。
重点回顾
- 初始化 SQL 放在
/docker-entrypoint-initdb.d/,只在第一次初始化时运行。 - 主机上连接写
127.0.0.1,localhost会改用 socket。 - 健康检查写
mysqladmin ping -h 127.0.0.1。 - 备份用
mysqldump --single-transaction,并实际测试还原。
动手练习
练习 1:用一个命令列出 MySQL 容器中所有的数据库。
答案
docker exec mysql sh -c 'mysql -uroot -p$MYSQL_ROOT_PASSWORD -e "SHOW DATABASES"'
练习 2:Compose 的 MySQL 设置了 ports: "127.0.0.1:3306:3306"。同一个 Compose 项目中的应用程序要连接时,主机名称和端口是什么?
答案
主机名称是服务名称 db,端口是容器内的 3306。ports 只影响从主机连接,容器之间不需要它。
常见问题
中文变成乱码?
MySQL 8 默认已经是 utf8mb4。乱码通常来自用户端连接的字符集或导入文件的编码;Windows PowerShell 5.1 用 > 存的备份是 UTF-16,请用本章的 docker cp 写法。
要用 MySQL 还是 MariaDB?
两者高度兼容。WordPress 等常见程序都支持;MariaDB 的环境变量改为 MARIADB_ 开头。
改了 MYSQL_ROOT_PASSWORD 没有生效?
密码变量只在 Volume 是空的时候使用,之后要用 SQL 修改,实测见本章。
更新于 2026年10月14日