Docker从零部署MySQL:容器化、数据持久化与SQL操作指南
说实话从零开始装一套MySQL听起来很简单但你一定遇到过这些场景宿主机上装到一半报依赖错误系统自带的版本太老项目要用新特性还得折腾卸载重装更别提同一台机器上想同时跑多个版本的MySQL简直是在给自己挖坑。我在这些事上浪费过不少时间后来彻底转向用Docker来跑MySQL——安装、启动、配置、进入容器执行SQL整套流程走下来非常干净环境想删就删版本想换就换。这篇文章就按从头开始的思路把Docker下MySQL的完整流程拆开讲从拉镜像、起容器、挂数据、写配置再到用docker exec进容器执行查询SQL每一步都告诉你“为什么这么做”。不管你是刚入门的开发者还是想快速搭一套本地开发环境的人都能照着操作复现。1. 为什么要用Docker跑MySQL先弄明白这件事值不值得做1.1 Docker跑MySQL解决了哪些实际问题很多人刚接触Docker时会有个疑问我直接在服务器上装个MySQL不就行了为什么要多包一层容器这个答案得从实际踩坑经历说起。直接在宿主机上安装MySQL你会面临几个很现实的问题第一系统依赖环境是全局的一个软件库升级可能影响其他服务第二版本切换非常麻烦比如老项目必须用5.7新项目又要8.0两个同时装端口、目录、启动脚本全都得手动隔离第三卸载不干净经常是配置文件、数据目录、系统服务残留一大堆重装时总能遇到莫名其妙的问题。Docker的出现本质上就是把“环境”也当成软件的一部分。MySQL跑在容器里镜像自带整套运行环境装到任何机器上行为一致。这就像把家电连同电线插头、说明书一起装进一个标准纸箱换个房子拆箱即用。开发环境里你甚至可以同时跑两个MySQL容器一个映射3306端口一个映射3307端口互不干扰。这对多项目并行开发的场景尤其有用。1.2 动手前需要准备什么开始操作之前先确保Docker环境安装好、能正常运行。Linux服务器装Docker引擎macOS和Windows一般装Docker Desktop装完在终端里先验证两条命令docker --version docker psdocker --version能正常输出版本号docker ps能列出容器列表且不报权限错误说明Docker引擎在正常运行。如果你在Linux上第一次执行docker ps遇到permission denied大概率是当前用户不在docker用户组里先执行sudo usermod -aG docker $USER然后退出终端重新登录再试。这里还想提醒一句别急着敲下面的命令先花两分钟确认自己的目标是“临时玩玩”还是“长期开发用”。如果只是体验一下可以不挂数据目录但既然标题写的是从头开始我建议直接按生产习惯来数据目录从一开始就挂载到宿主机后面无论容器怎么重建数据都还在。这个习惯能帮你少走很多弯路。2. 安装MySQL镜像版本选择与拉取的那些细节2.1 镜像版本选哪个比较好官方镜像仓库里的MySQL镜像主要分几个系列5.7系列、8.0系列还有最近的8.4和9.x创新版。如果你只是搭开发环境我建议优先用8.0系列这是目前兼容性最好、资料最多的分支。8.4和9.x虽然新但对很多第三方组件和迁移工具来说还没完全跟上没必要在入门阶段给自己增加变数。版本选择还有一个关键点不要用latest标签。latest指向最新发布版本今天拉的和三个月后拉的可能不是同一个版本项目一旦依赖某个具体版本的行为就很容易被“悄悄升级”坑到。正确做法是把镜像标签锁到具体的小版本比如mysql:8.0.36这样在任何人、任何机器上拉下来的东西都是一致的。MySQL 5.7和8.0还有一个非常重要的差异默认认证插件。8.0默认使用caching_sha2_password5.7默认是mysql_native_password。老版本的客户端、一些早期版本的开发库连接8.0时如果不做适配会直接报认证失败。如果你要连一个老系统镜像版本别拍脑袋选一定先确认客户端兼容性。这个知识点在后面的认证插件配置部分还会用到。2.2 拉取镜像的实操过程选定版本号之后直接在终端执行docker pull mysql:8.0.36执行后Docker会从官方仓库拉取镜像整个过程显示一层层的下载进度。拉取完成后用docker images确认本地镜像列表docker images能看到本地已有的镜像和各自大小确认mysql:8.0.36已经出现在列表里。拉取过程中如果速度很慢可以先检查网络环境或者确认是否配置了可用的镜像仓库地址。正常网络环境下官方镜像仓库的拉取速度通常可以接受如果长时间停在某一步不动按两次CtrlC取消后重试多半是网络不稳定导致底部分层中断。这里补充一个很多教程不会说的细节锁版本号之后后续升级需要显式地重新docker pull指定新版本不会出现“系统自动偷偷升级”的意外。对于团队协作来说把版本号写进运维脚本或编排文件里比写latest严谨得多。3. 启动MySQL容器参数逐个拆明白3.1 一条run命令背后的设计逻辑镜像拉下来之后启动容器的核心命令长这样docker run -d \ --name mysql-dev \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDyourpassword \ -v mysql-data:/var/lib/mysql \ mysql:8.0.36这条命令的每个参数都不是随便加的。-d表示让容器在后台运行不会因为终端关闭就退出。--name mysql-dev给容器起个固定名字后面进入容器、查看日志、停止容器都靠这个名字比记一长串容器ID方便得多。-p 3306:3306是端口映射冒号左边是宿主机端口冒号右边是容器内部端口宿主机上的应用通过3306端口连接时流量会被转发到容器内的MySQL服务。-e MYSQL_ROOT_PASSWORDyourpassword设置MySQL根用户密码这是官方镜像初始化时必须指定的环境变量之一。-v mysql-data:/var/lib/mysql把容器中的MySQL数据目录挂载到Docker管理的具名卷中这一行参数直接决定了删掉容器之后数据是否还在。启动命令执行完后用docker ps查看容器状态。如果容器没有出现在UP状态说明启动过程出问题了先用docker logs mysql-dev查看日志再针对性排查。3.2 数据持久化为什么必须挂载数据目录很多新手刚接触Docker时脑子里只有“启动容器”这个概念经常漏掉数据挂载结果一次docker rm把整个数据库删了这才反应过来容器是临时的数据必须放到容器外面。MySQL官方镜像内部的数据目录是/var/lib/mysql。如果不挂载任何卷数据就写在容器的可写层里容器一删除数据连同可写层一起被清理相当于把整个数据库写在白板上擦掉就没了。所以挂载数据目录是必须做的推荐使用Docker具名卷docker volume create mysql-data docker run -d --name mysql-dev -v mysql-data:/var/lib/mysql mysql:8.0.36docker volume创建的卷由Docker管理放在宿主机的特定路径下好处是你不用关心它在磁盘哪个位置也不用管宿主机目录权限问题删除容器后卷还在重新创建容器时继续挂载同一个卷数据就能无缝恢复。挂载时有一个实践中常踩的坑MySQL官方镜像里的MySQL进程以mysql用户运行这个用户的UID是999。如果你用bind mount方式把宿主机某个目录直接挂进去比如-v /data/mysql:/var/lib/mysql宿主机目录的属主和权限可能和容器里的mysql用户不匹配导致MySQL启动时报Permission denied。解决办法是先把宿主机目录的属主改成UID 999sudo chown -R 999:999 /data/mysql或者干脆用Docker具名卷省去这个麻烦。新手建议直接用具名卷。3.3 端口映射与多实例同时运行端口映射用来让宿主机及其他机器连接到容器内的MySQL。默认情况下MySQL在容器内监听3306端口宿主机如果不做映射外面的程序就访问不到。-p 3306:3306是最常见的做法。但如果宿主机上已经有另一个服务占据了3306端口比如本机原先装过MySQLdocker run就会启动失败日志提示端口被占用。这时候可以把宿主机端口改掉docker run -d --name mysql-dev -p 3307:3306 -e MYSQL_ROOT_PASSWORDyourpassword mysql:8.0.36这样宿主机上的应用用3307端口连接就能访问容器内的MySQL。同理如果同时跑多个MySQL实例比如一个给旧项目用5.7一个给新项目用8.0就分别映射不同端口docker run -d --name mysql57 -p 3306:3306 -e MYSQL_ROOT_PASSWORDpass57 mysql:5.7.44 docker run -d --name mysql80 -p 3307:3306 -e MYSQL_ROOT_PASSWORDpass80 mysql:8.0.36以后访问3306进5.7访问3307进8.0完全隔离、互不干扰。对做多项目开发的人来说这比在宿主机上绞尽脑汁管理多版本MySQL舒服太多了。4. 配置MySQL字符集、认证插件与远程访问权限4.1 自定义配置文件的正确挂载方式容器启动后如果只有默认配置那顶多算“能跑”。要让MySQL适配项目需求通常要自定义字符集、最大连接数、日志参数。官方镜像默认读取/etc/my.cnf以及/etc/mysql/conf.d/目录下的所有.cnf文件。最推荐的做法是在宿主机写好配置文件然后通过启动参数挂载进去docker run -d \ --name mysql-dev \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDyourpassword \ -v mysql-data:/var/lib/mysql \ -v /path/to/my.cnf:/etc/mysql/conf.d/my.cnf \ mysql:8.0.36宿主机上的配置文件可以这样写[mysqld] character-set-serverutf8mb4 collation-serverutf8mb4_unicode_ci max_connections 500 slow_query_log 1 slow_query_log_file /var/lib/mysql/slow.log long_query_time 2这里把character-set-server设成utf8mb4比较关键。utf8mb4是真正的完整UTF-8实现能存emoji和大部分生僻字符MySQL 8.0默认已经用它但5.7的默认字符集还是latin1如果不显式设置插入中文和表情符号很容易出现乱码或字符截断报错。collation-serverutf8mb4_unicode_ci是配套的排序规则unicode_ci支持大小写不敏感的通用比较适合大多数业务场景。4.2 启动阶段直接指定配置参数除了挂载配置文件MySQL镜像还支持在容器启动命令中直接传配置参数也就是把参数写在镜像名后面MySQL的初始化脚本会把这些参数转交给mysqld。常见写法docker run -d \ --name mysql-dev \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDyourpassword \ mysql:8.0.36 \ --character-set-serverutf8mb4 \ --collation-serverutf8mb4_unicode_ci注意参数必须放在镜像名mysql:8.0.36后面不能放在docker run的参数区否则Docker会当作自己不认识的启动参数直接报错。这种方式适合配置项不多的时候简单直观。但一旦配置项多起来比如要调缓冲池大小、开启二进制日志、配置只读账户命令行就变得又长又难维护这时我更推荐用配置文件挂载的方式。如果想知道MySQL支持哪些启动参数可以进入容器里执行mysqld --verbose --help会打印出完整的参数说明。不过这属于进阶排查手段平时配置还是以官方文档为准。要特别注意的是MySQL容器第一次启动时会执行初始化流程包括创建数据目录、初始化系统表、执行初始化脚本。如果第一次启动时字符集设置不对后面再改配置文件已经创建好的库和表的字符集并不会自动变更。所以“字符集越早定下来越好”这句话是真的第一次启动前就要想清楚。4.3 认证插件与远程访问权限的常见问题很多人在容器里跑起MySQL后发现本地客户端连不上报错信息类似Authentication plugin caching_sha2_password cannot be loaded这就是前面提到过的版本差异。旧版客户端或老开发库不认识8.0的caching_sha2_password认证插件。解决方法是进入容器把root账号或新账号的认证方式改回mysql_native_passworddocker exec -it mysql-dev mysql -uroot -p然后在MySQL命令行里执行ALTER USER root% IDENTIFIED WITH mysql_native_password BY yourpassword; FLUSH PRIVILEGES;这里有个容易让人困惑的概念MySQL的账号不只有用户名而是“用户 主机”的组合。rootlocalhost表示只能从本机连接root%表示允许从任意主机连接。用Docker跑MySQL容器外的客户端要访问多半需要root%这个账号存在。可以先执行下面这条SQL看当前到底有哪些root账号SELECT user, host FROM mysql.user;如果列表里没有root%就手动创建CREATE USER root% IDENTIFIED BY yourpassword; GRANT ALL PRIVILEGES ON *.* TO root% WITH GRANT OPTION; FLUSH PRIVILEGES;如果你不想直接开放root远程权限更稳妥的做法是创建一个专用账号CREATE USER appuser% IDENTIFIED BY appPass123; GRANT ALL PRIVILEGES ON appdb.* TO appuser%; FLUSH PRIVILEGES;这里appdb.*表示只对appdb这个库授权权限粒度比直接给root合理得多。还要提醒一句如果容器映射了端口而密码强度不够MySQL就相当于对所有能访问到该端口的主机开着门。测试环境里无所谓但在服务器或公网环境下一定要把密码设置得足够强并且只对必要的IP开放端口不要图省事把端口暴露到公网。5. 进入容器执行SQL查询、批处理、导入导出全流程5.1 docker exec进入容器的标准姿势配置全部完成后最重要的环节就来了怎么进入容器里执行SQL。标题里特别提到“进入容器执行查询SQL”这也是初学者最容易绕晕的地方。进入容器的标准命令是docker exec -it mysql-dev bashdocker exec是“在运行中的容器里执行命令”的指令-it表示以交互模式运行配合bash参数就是进入容器的Shell。进入后容器内部就像一台独立的Linux小机器再用MySQL客户端连接本机MySQLmysql -uroot -p输入密码后就进入了MySQL的命令行交互界面可以正常执行各种SQLSHOW DATABASES; USE appdb; SELECT * FROM users LIMIT 5; SHOW VARIABLES LIKE character_set%;如果容器里没有bash比如某些基于精简系统的镜像可以用sh代替docker exec -it mysql-dev sh5.2 不进入容器直接执行SQL的高效做法但有的时候我们不想一层层进入容器只想快速查一条SQL结果比如确认版本、看配置、统计表数据。这时候可以用docker exec直接执行MySQL客户端命令docker exec -it mysql-dev mysql -uroot -pYourPassword -e SELECT VERSION();这里的-e SQL语句表示MySQL客户端执行完这条语句后自动退出不用手动进入交互界面特别适合写脚本和快速验证。多个查询也可以一起执行docker exec -it mysql-dev mysql -uroot -pYourPassword -e SHOW DATABASES; SELECT NOW();执行SELECT NOW()能顺便确认容器内MySQL的时间与宿主机是否一致这是个容易被忽视但有时很用的检查项。-e方式还常用于批量初始化和修改配置。比如要批量创建表结构可以先写好一个SQL文件然后通过重定向让MySQL客户端读取docker exec -i mysql-dev mysql -uroot -pYourPassword init.sql注意这里用的是-i而不是-it因为输入来自文件重定向不需要伪终端。如果是导入一个完整的数据库备份文件思路也是一样的docker exec -i mysql-dev mysql -uroot -pYourPassword appdb backup.sql这里的appdb是目标数据库名需要事先存在。导入前用docker exec先创建数据库也行docker exec -it mysql-dev mysql -uroot -pYourPassword -e CREATE DATABASE IF NOT EXISTS appdb DEFAULT CHARACTER SET utf8mb4;5.3 用mysqldump完成容器内数据库的备份与导出有导入就有导出。Docker容器里的MySQL做备份不需要先进容器再执行mysqldump直接通过docker exec调用容器内的mysqldump工具docker exec mysql-dev sh -c exec mysqldump -uroot -pYourPassword appdb appdb_backup.sql这条命令把标准输出重定向到宿主机文件里备份文件直接就留在了宿主机上。做数据迁移时加上--all-databases就能导出全部数据库docker exec mysql-dev sh -c exec mysqldump -uroot -pYourPassword --all-databases all_backup.sql需要注意mysqldump导出的文件里可能包含建库语句导入前先检查文件头部是否有CREATE DATABASE避免导入到错误的目标库。另外提醒一下命令行里直接带密码虽然方便但会触发MySQL的警告信息而且在有安全审计需求的团队环境里不太规范。本地开发环境无所谓生产环境建议使用配置文件中的凭据或者用交互方式输入密码。5.4 查看日志与启动失败排查容器跑起来之后排查问题最常用的命令是docker logsdocker logs mysql-dev docker logs -f mysql-dev第一条查看当前日志第二条-f表示持续跟踪日志输出类似tail -f。MySQL初始化失败、密码变量未设置、权限不足、端口冲突这些问题都会在日志里留下明确线索。新手最容易遇到的一个启动失败是MySQL官方镜像要求设置MYSQL_ROOT_PASSWORD或MYSQL_ALLOW_EMPTY_PASSWORD如果你只写了端口映射没写环境变量容器会反复重试然后退出。此时日志会提示ERROR: Database is uninitialized and password option is not specified看到这条日志基本就能确认是环境变量漏了。解决办法很简单加上-e MYSQL_ROOT_PASSWORD...后重新创建容器即可。端口占用的排查也常见。执行完docker run后容器马上退出docker ps看不到要用docker ps -a加上-a参数看所有状态的容器。把容器删掉后用ss -tlnp | grep 3306或lsof -i:3306查看端口占用来源再决定是释放端口还是换一个宿主机端口映射。5.5 容器重建后数据还在吗这是很多人最关心的问题如果容器坏了重建数据会不会丢只要数据目录是用具名卷挂载的删除容器根本不影响数据。重建命令如下docker rm -f mysql-dev docker run -d --name mysql-dev -p 3306:3306 -e MYSQL_ROOT_PASSWORDyourpassword -v mysql-data:/var/lib/mysql mysql:8.0.36启动后原先创建的库、表、数据都还在。此时有一个容易困惑的细节新建容器时指定的MYSQL_ROOT_PASSWORD并不会覆盖已有数据目录里的root密码因为数据目录已经初始化过了MySQL不会再执行初始化流程。如果你改了环境变量里的密码会发现用新密码连不上但旧密码可以。这是正常的要改密码的话需要进入容器后用SQL语句修改。这个知识点很实用。我在实际工作中不止一次看到有人重建容器后一脸疑惑地说“我明明设了新密码为什么连不上”其实就是对“初始化只发生在第一次启动”这个机制不够熟悉。6. 我踩过的坑和给你的一点建议6.1 我踩过的几个坑Docker跑MySQL这条路我走了几年最大的感受是它把环境的复杂度切走了但也把“数据安全”的责任转移到了卷管理上。如果说话直白一点环境变量、端口、认证插件这些查查文档都能解决目录挂载和权限问题是真的只能靠踩坑记住。第一次用bind mount挂载数据目录时我熬夜排查“MySQL为什么启动不了”最后才发现是宿主目录的属主不是UID 999导致的。第二次是没加字符集参数往表里插表情符号直接报Incorrect string value。还有一次是同事把latest镜像部署上去第二天升级带来的兼容性问题。这些坑单看都很基础但撞到一起就非常折磨人。6.2 给新手的实在建议如果你正打算从头开始走这条路我的建议很简单第一次启动前就把数据卷挂好把字符集定好把密码用强一点版本锁到具体小版本。记住一个核心原则——容器可以随时重建数据不能随便丢所有能固化到命令和配置文件里的东西别依赖手动修改。把这几个习惯养成后面不管是换机器、换环境还是做项目迁移你都会感谢自己一开始的“多此一举”。最后再分享一个常用的快捷技巧在命令行工具里配置一个别名把“进入MySQL容器并打开客户端”这一长串操作缩短成一个短命令。比如在~/.bashrc里加一行alias mysql-devdocker exec -it mysql-dev mysql -uroot -p之后每次想连接开发环境里的MySQL只需敲mysql-dev输入密码即可。一个小改动体验提升非常明显。