目录

用 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_PASSWORDroot 密码(必填,或改用 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.

从日志可以看到镜像的启动脚本在第一次启动(文件夹是空的)时依序:

  1. 创建数据库文件(Initializing database files)。
  2. 启动一个暂时的服务器(Starting temporary server)。注意它的 port: 0:暂时服务器不开放网络连接,只能用 socket 连接。
  3. 创建数据库 app、用户 app 并授权。
  4. 依文件名顺序运行 /docker-entrypoint-initdb.d/ 中的 01-schema.sql、02-seed.sql。
  5. 停止暂时服务器,以正式设置启动,最后一行的 port: 3306 才表示真正可以连接了。

整个过程大约 10 秒。/docker-entrypoint-initdb.d/ 支持 .sql、.sql.gz、.sh 文件,而且只在第一次初始化时运行,之后重启容器不会再运行。

版本选择

tag说明
mysql:8.4长期支持版(LTS),建议用于生产环境
mysql:9创新版,功能较新、支持期较短
mariadb:11MariaDB,与 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.cnfMySQL 会读取 /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日