ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Docker部署MySQL 8.0实战:数据持久化与连接坑全解

Docker部署MySQL 8.0实战:数据持久化与连接坑全解 最近在整理韦奇这套部署环境时碰上了一个特别典型的任务用Docker把MySQL 8.0拉起来数据还得持久化开发、测试、预发三套环境都要用同一套部署方式不能各自为政。折腾了一轮之后我把整个落地过程完整梳理了一遍包括镜像选择、容器启动参数、常见连接报错、备份恢复这些内容。如果你正好也要在Docker里跑MySQL或者刚被Navicat连不上、容器重启数据丢失这类问题卡住这篇文章应该能帮你省下不少时间。先说结论Docker跑MySQL不复杂但也不是一条docker run mysql就能高枕无忧那么简单。真正让人踩坑的地方集中在数据卷挂载、认证插件、网络模式、字符集这几块。下面我会按自己实际操作时的顺序来讲尽量把每一步“为什么这么做”也一并说清楚。1. 先把方案想清楚为什么选Docker跑MySQL1.1 不是所有数据库都适合容器化但常规业务库完全够用我见过很多团队对“容器化数据库”这事有顾虑担心数据安全、担心性能损耗。这个担心在早期确实有道理但放到今天对于绝大多数中小型业务系统来说Docker跑MySQL的稳定性和便捷性已经非常成熟了。韦奇这个项目的情况比较典型需要在一台新服务器上快速部署一套MySQL同时本地开发环境也要保持一致。如果走传统方式我得分别在Windows和Linux上装MySQL两边的配置文件、服务管理方式还不一样光是版本差异就够喝一壶的。改用Docker之后镜像里已经包含了MySQL的完整运行环境我只需要关心配置参数和数据卷开发环境与生产环境用的完全同一个镜像迁移和复现都变得很简单。还有个很现实的好处卸载和清理变得干净了。传统方式装MySQL卸载时经常会残留服务、注册表项、数据目录而Docker容器删掉之后除了你主动挂载出来的数据目录几乎不会留下任何垃圾。1.2 镜像是选MySQL还是MariaDB版本怎么定这一步看着简单但直接决定后面所有操作。Docker Hub上MySQL相关的镜像主要有mysql和mariadb两大家族虽然MariaDB和MySQL高度兼容很多命令都通用但如果你团队用的客户端、ORM框架、监控工具是基于官方MySQL做的适配那我建议还是老老实实用官方mysql镜像减少意料之外的兼容性问题。版本选择上我的建议是优先用MySQL 8.0系列而不是latest标签。原因很现实latest指向的版本随时可能变化今天拉下来是8.0过几个月再拉可能就变成8.4甚至9.x了如果生产环境重建镜像时版本漂移后果比较麻烦。在韦奇这个项目里我直接锁定了mysql:8.0同时用docker image inspect确认了具体的小版本号后面所有服务器都用同一个tag版本一致性就有了保障。如果是一些老项目代码里用了比较老的数据库驱动MySQL 8.0的默认认证插件caching_sha2_password可能会让旧版驱动连不上。这种情况可以选MySQL 5.7镜像或者稍后通过参数把认证插件改回来。这个后面在连接报错部分会详细展开。1.3 数据卷、端口、网络这三个点必须在规划阶段定下来很多人部署MySQL失败不是命令敲错而是没想清楚几个关键决策点。我自己习惯在动手之前先回答三个问题第一个问题容器删掉之后数据放哪里答案是挂载数据卷到宿主机目录。这一步不做容器一旦被删除整个数据库就“格式化”了这是新手最容易犯的致命错误。第二个问题宿主机用哪个端口映射给MySQL默认3306如果被占用或者你想让开发库和生产库在同一台机器上共存就需要提前规划端口比如33061、33062。第三个问题容器网络怎么处理单独docker run启动的容器默认走bridge网络多个容器之间用别名互访如果用docker-compose则会自动创建专用网络。韦奇项目里我选择compose方式因为除了MySQL还要一起起Redis、应用服务统一网络便于容器间通信。这三个问题想清楚后面的命令和配置文件几乎是水到渠成。2. 环境准备Docker本身不能装歪2.1 Windows下安装Docker Desktop的几个关键点很多人看到Docker Desktop安装包就一路下一步结果启动时却报各种错特别是“virtualization support not detected”这个提示其实就是虚拟化没开或者版本不对。Windows上跑Docker Desktop底层依赖是WSL 2或者Hyper-V。我自己更推荐WSL 2方案因为性能比Hyper-V更好资源占用也更轻。具体安装时注意下面几件事先去控制面板启用“适用于Linux的Windows子系统”和“虚拟机平台”两个Windows功能重启后才能继续。安装完Docker Desktop之后在Settings里确认使用的后端是WSL 2。如果安装之前已经开了WSL功能但Docker还是起不来去BIOS里确认虚拟化技术Intel VT-x或AMD-V有没有被安全软件或主板设置禁掉。一个常见场景笔记本上装了第三方杀毒软件或虚拟机工具比如VirtualBox它们会干扰Windows Hypervisor Platform导致Docker Desktop启动失败。如果遇到这情况检查是否有软件占用了虚拟机监控程序权限必要时临时关闭再启动Docker。2.2 Linux服务器安装Docker的常用姿势Linux服务器上装Docker要区分发行版。以CentOS/RHEL系为例官方推荐先用yum install -y yum-utils然后配置Docker官方yum源再安装docker-ce。Ubuntu系则用apt-get install docker.io或者同样走官方apt源。这里我有个实际经验国内网络环境下配置官方源可能很慢如果服务器在内网或者访问外网受限建议配置好镜像加速器。镜像加速器主要影响的是docker pull拉镜像的速度不影响容器的实际运行。配置方式是在/etc/docker/daemon.json里写入注册地址然后重启Docker。在韦奇项目里服务器是CentOS环境我安装完docker-ce之后执行了如下几步systemctl enable docker systemctl start docker docker versionsystemctl enable docker这一步很容易被忽略但这决定了服务器重启后Docker能不能自动起来。如果没设置服务器一旦重启你的MySQL容器不会自动恢复数据库服务就“隐身”了。2.3 快速验证Docker环境是否可用装完Docker之后别急着拉MySQL镜像先跑一个最基础的验证docker run --rm hello-world这条命令会拉取一个极小的测试镜像并运行如果正常输出一段提示信息说明Docker引擎工作正常。--rm参数表示容器运行结束后自动删除不会留下垃圾。再检查一下资源占用。Docker Desktop在Windows上运行需要一定的内存配额如果电脑内存只有8GB我建议给Docker Desktop分配4GB左右不然MySQL容器跑起来后宿主机很容易卡顿。在Docker Desktop的Settings - Resources里可以调整内存和CPU上限。3. 核心部署MySQL容器到底怎么跑3.1 最基础的docker run每个参数都要能解释如果不用compose一条最经典的MySQL 8.0部署命令是这样docker run -d \ --name mysql-wegic \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDWg123456 \ -v /data/mysql:/var/lib/mysql \ -v /etc/localtime:/etc/localtime:ro \ mysql:8.0逐个参数说-d后台运行容器。--name容器名这里我起的是mysql-wegic方便后面管理。-p 3306:3306宿主机3306端口映射到容器3306端口。左边是宿主机端口右边是容器端口顺序不要搞反。-e MYSQL_ROOT_PASSWORD设置root账号密码。这只是初始化时的行为如果数据目录已经存在这个环境变量就不再生效。-v /data/mysql:/var/lib/mysql宿主机/data/mysql目录挂载为容器的数据目录。这是整个命令里最重要的一项直接决定了容器删除后数据是否保留。-v /etc/localtime:/etc/localtime:ro让容器时区与宿主机一致避免数据库时间跟业务对不上。这里有一个关键点第一次启动时如果/data/mysql目录是空的MySQL镜像会自动执行初始化流程包括创建系统表、设置root密码。但如果之前已经初始化过再重复启动容器时环境变量MYSQL_ROOT_PASSWORD不会覆盖已有密码你改密码必须进容器里执行SQL或使用初始化脚本。3.2 字符集和时区刚部署完最容易忽略的两个配置MySQL 8.0默认字符集已经是utf8mb4了这个比5.7时期默认utf8要好很多但时区问题依然存在。如果不在启动时指定时区容器默认使用UTC时间而你所在服务器的业务系统需要使用北京时间那存进去的时间会比正常时间晚8个小时排查起来特别费劲。我在实际部署时会在docker run命令里再追加两个参数-e TZAsia/Shanghai \ --character-set-serverutf8mb4 \ --collation-serverutf8mb4_unicode_ci如果你创建数据库时也希望默认字符集一致可以另外传入--default-authentication-pluginmysql_native_password关于这个参数有些人会为了兼容旧客户端而加上但我建议不要一上来就加。MySQL 8.0默认的caching_sha2_password更安全现代客户端如最新版Navicat、DBeaver、新版JDBC驱动都支持。等真的遇到连接报错时再加不迟。3.3 用docker-compose管理MySQL对项目更友好项目里如果只有单条命令还好但像韦奇这种同时要管理多个容器的场景我强烈建议用docker-compose.yml把配置固化下来。好处是配置可见、可版本化管理别人克隆项目后一条命令就能把环境拉起来。下面是我在项目中使用的compose片段version: 3.8 services: mysql: image: mysql:8.0 container_name: mysql-wegic restart: always environment: MYSQL_ROOT_PASSWORD: Wg123456 MYSQL_DATABASE: wegic_db MYSQL_USER: wegic MYSQL_PASSWORD: Wegic123 TZ: Asia/Shanghai ports: - 3306:3306 volumes: - /data/mysql:/var/lib/mysql - ./sql/init:/docker-entrypoint-initdb.d command: - --character-set-serverutf8mb4 - --collation-serverutf8mb4_unicode_ci这里有两处很值得注意第一restart: always配合Docker服务自启动可以保证服务器重启后MySQL容器自动恢复。这个比单独记忆一条docker update --restartalways要省事很多。第二./sql/init:/docker-entrypoint-initdb.d这个挂载目录非常有用。MySQL官方镜像在首次初始化数据库时会自动执行该目录下的.sql脚本。这意味着你可以把建库、建表、初始化数据全部放进脚本里第一次docker-compose up就能得到一个完整可用的数据库环境。3.4 初始化脚本第一次启动自动建库建用户因为韦奇项目里有多个服务各自需要独立的数据库账号和权限我在./sql/init目录里放了一个init.sql内容大致如下CREATE DATABASE IF NOT EXISTS wegic_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER wegic% IDENTIFIED BY Wegic123; GRANT ALL PRIVILEGES ON wegic_db.* TO wegic%; CREATE USER readonly% IDENTIFIED BY ReadOnly123; GRANT SELECT ON wegic_db.* TO readonly%; FLUSH PRIVILEGES;需要注意这个目录里的脚本只有在数据目录为空、MySQL首次初始化时才会执行。如果你之前已经启动过容器数据目录里有了内容再改脚本然后重启容器是不会生效的。那怎么办要么把宿主机的数据目录清空再重新初始化要么直接进入容器手动执行SQL。这两种方式我后面会详细讲。4. 连接不上我把常见报错都过了一遍4.1 Navicat报SSL连接错误认证插件是关键用Navicat连接Docker里的MySQL 8.0时最容易见到类似“Authentication plugin ‘caching_sha2_password’ cannot be loaded”的报错老版本Navicat对这个插件支持得不够好就会在连接阶段直接失败。解决办法通常有两种。第一种是改账号的认证插件为mysql_native_passwordALTER USER root% IDENTIFIED WITH mysql_native_password BY 你的密码; FLUSH PRIVILEGES;第二种是升级客户端到支持MySQL 8.0的版本。Navicat 16以上基本没问题旧的11/12版本就很容易踩坑。这里我特别提示一句修改认证插件只影响你指定的账号不要全局改配置文件否则会让MySQL 8.0原本的安全性优势打折扣。另外如果你不希望MySQL使用SSL也可以在连接字符串中显式关闭SSL或者连接时加ssl-modeDISABLED。但这属于绕路方案根本问题还是客户端插件兼容性。4.2 ERROR 2002 (HY000)到底是socket问题还是TCP问题很多人在容器里执行mysql -uroot -p时报这个错ERROR 2002 (HY000): Cant connect to local MySQL server through socket /var/run/mysqld/mysqld.sock先理清原因MySQL客户端在连接localhost时默认走Unix socket而非TCP。容器环境中socket文件的路径取决于镜像版本和启动参数如果文件不在客户端默认寻找的位置就会报2002错误。我去现场排查时的思路是先确认容器是不是真的起来了docker ps看状态。进入容器查看socket文件位置ls /var/run/mysqld/。如果不是默认路径连接时显式指定mysql -hlocalhost -uroot -p --socket/var/run/mysqld/mysqld.sock。如果用TCP连接容器外部的MySQL应该指定-h 127.0.0.1而不是localhost。大多数场景只要改成mysql -h127.0.0.1 -P3306 -uroot -p就能绕开socket路径问题。这个报错本身不复杂但在容器场景里出现频率极高尤其在刚部署完还没配好外部客户端的时候。4.3 Docker Desktop启动失败virtualization support not detected“virtualization support not detected”是Windows Docker Desktop高频报错。根本原因就是虚拟化不可用。排查顺序建议如下确认BIOS/固件中开启了虚拟化技术。在Windows功能里确认“Virtual Machine Platform”和“适用于Linux的Windows子系统”都勾选了。确认Windows版本是专业版或企业版家庭版跑Hyper-V会遇到不少限制当然WSL 2方案在Windows 10/11家庭版也能跑。运行bcdedit查看hypervisorlaunchtype是否为auto这是之前帮同事排查时发现的一个隐蔽坑如果被安全软件或其它虚拟化工具改成了offDocker Desktop就检测不到Hyper-V。Docker Desktop侧还要检查WSL发行版的状态。打开命令行执行wsl --status wsl --list --verbose如果你安装了某个Linux发行版但版本是1而不是2需要升级wsl --set-version Ubuntu-20.04 2这套流程走下来90%的“virtualization support not detected”都能解决。4.4 Docker网络不通先分清楚是容器间还是容器外“docker网络不通”这个问题很多新手会直接怀疑是不是端口映射错了。实际上要分几种情况。一种是容器访问宿主机不同网段的服务或者宿主机访问容器IP不通。这时候要先确认容器IPdocker inspect mysql-wegic | grep IPAddress如果应用容器和MySQL容器在同一个compose网络里应该用服务名mysql而不是IP去连接因为IP会随着容器重建改变。例如在应用容器里执行mysql -hmysql -P3306 -uwegic -p这里的mysql是compose服务名Docker内置DNS会自动解析到对应容器IP。另一种是宿主机连接MySQL不通。这种情况先确认端口映射docker port mysql-wegic如果输出是3306/tcp - 0.0.0.0:3306说明端口绑定没问题。再排查防火墙Linux检查firewalld或iptablesWindows检查防火墙规则和Docker Desktop的端口转发设置。还有一个容易忽略的点如果你把MySQL的bind-address改成了127.0.0.1那么即使端口映射正确外部也连不进来。容器里默认绑定的地址是0.0.0.0但如果你在配置文件或command参数里覆盖了bind-address就要格外小心。4.5 远程连接授权的正确操作很多人在Docker部署MySQL后用Navicat或DBeaver远程连接时被拒报Access denied for user root...。这个问题的本质是MySQL的账号授权host范围没覆盖到你的客户端地址。我自己的习惯是不直接用root做远程连接而是专门创建业务账号并指定允许的网段。CREATE USER app192.168.1.% IDENTIFIED BY 密码; GRANT ALL PRIVILEGES ON wegic_db.* TO app192.168.1.%; FLUSH PRIVILEGES;如果为了开发方便需要允许任意IP访问则用app%。但在生产环境host范围越窄越好尽量不要用%一刀切。如果之前已经授权过了还是连不上检查一下用户表SELECT user, host, plugin FROM mysql.user;确认目标账号的host是不是包含你客户端所在的网段。有时候问题不是密码错误而是host不匹配比如你客户端IP是192.168.1.100但授权的是192.168.2.%那自然连不上。5. 数据安全备份、恢复和初始化脚本5.1 用mysqldump做备份最简单的保命手段容器里的MySQL也同样支持使用mysqldump备份。我的备份命令长这样docker exec mysql-wegic sh -c exec mysqldump --single-transaction --set-gtid-purgedOFF -uroot -p密码 wegic_db /data/backup/wegic_db_$(date %F).sql几个参数值得解释--single-transaction在InnoDB引擎下通过事务保证备份数据一致性备份过程中不锁表。--set-gtid-purgedOFF如果是MySQL 8.0且有主从复制或GTID配置导出文件时不包含GTID信息方便后续导入到其它实例。重定向到宿主机/data/backup目录而不是直接存在容器里这样容器删除后备份依然安全。恢复的时候有两种常用姿势。一种是直接把备份文件导入容器内cat /data/backup/wegic_db.sql | docker exec -i mysql-wegic mysql -uroot -p密码 wegic_db注意这里用的是-i而不是-it因为管道输入不需要分配tty。另一种是挂载恢复目录利用MySQL客户端source命令导入。不过我这里更推荐第一种命令简单直接。5.2 初始化脚本和已有数据卷的冲突问题前面提到过/docker-entrypoint-initdb.d只在数据目录为空时执行。如果你中途改了初始化脚本希望在新环境或干净环境下重建数据库那么操作顺序是先停掉旧容器、备份数据、删除数据卷内容再重新启动容器。韦奇项目当时从开发机迁移到测试机时我就是这么干的docker-compose down docker volume rm wegic_mysql_data # 如果用的是volume # 或者 rm -rf /data/mysql/* docker-compose up -d注意删除数据目录是危险操作一定要先确认备份已经存在。我的建议是在删除之前把/data/mysql目录整体复制一份到别处cp -a /data/mysql /data/mysql_backup_$(date %F)这样即使新环境的初始化脚本有问题你还能回到老数据重新启动容器。5.3 从备份文件恢复的完整演示恢复操作最怕的是数据库已经存在里面又有新数据。此时应该尽量避免直接覆盖导入。正确流程一般是创建一个临时库CREATE DATABASE restore_test DEFAULT CHARACTER SET utf8mb4;导入备份cat /data/backup/wegic_db.sql | docker exec -i mysql-wegic mysql -uroot -p密码 restore_test对比重要表的行数确认导入结果正确然后再把应用切换到新库或导入正式库。这样做的原因是备份文件里可能包含DROP TABLE IF EXISTS语句如果直接导入正式库会直接覆盖现有表造成不可逆的数据丢失。宁可多花几分钟建个临时库验证也不要在生产环境上直接赌一把。6. 顺手调优参数、性能与日常维护6.1 设置默认值为0这个需求怎么落地热词榜里经常出现“mysql设置默认值为0”实际业务中典型场景是新加的字段要求默认为0而不是NULL。例如一个状态字段业务里用0表示“待处理”。建表语句可以这样写ALTER TABLE orders ADD COLUMN status INT NOT NULL DEFAULT 0;如果表已经存在可以用MODIFY COLUMN修改ALTER TABLE orders MODIFY COLUMN status INT NOT NULL DEFAULT 0;这个操作在Docker部署的MySQL里执行没有任何特殊之处直接进容器用SQL执行即可。但还有一个相关概念容易被混淆MySQL 8.0里有一个系统变量explicit_defaults_for_timestamp它影响TIMESTAMP列的默认值行为。在旧版本中第一个TIMESTAMP列会自动带DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP容易造成不可预期的行为。如果你在项目里发现时间戳字段的行为和预期不一致可以检查这个变量SHOW VARIABLES LIKE explicit_defaults_for_timestamp;MySQL 8.0中该变量默认是ON逻辑更符合直觉TIMESTAMP列不会自动添加默认值除非你显示声明。6.2 容器级别的性能限制Docker跑MySQL毕竟是共享宿主机资源如果不做任何限制MySQL在业务高峰期可能吃满宿主机CPU和内存影响同机其它服务。反过来如果宿主机资源少MySQL也可能因为内存不足被OOM杀掉。我通常会做两件事第一在compose文件里配置deploy.resources.limits或者在docker run时加--memory与--cpus参数。deploy: resources: limits: cpus: 2 memory: 4G第二在MySQL启动command里设置InnoDB缓冲池大小command: - --innodb_buffer_pool_size2G - --max_connections200innodb_buffer_pool_size是MySQL内存大户一般建议设置为可用内存的50%~70%。如果设置得太小查询大量走磁盘性能会明显下降设置得太大宿主机又容易内存不足。我在韦奇的测试环境里给了2G生产环境给了8G需要根据机器配置评估。6.3 日常维护日志、监控与连接池容器化MySQL日常维护比裸机部署多了一个“查日志”的入口。看容器日志docker logs -f mysql-wegic如果MySQL崩溃或初始化失败通常这条日志里会有明确线索。比如之前一次部署我通过日志发现是数据目录权限不对导致初始化失败容器启动后一直处于Restarting状态。监控方面如果只是临时看状态可以进容器执行docker exec -it mysql-wegic mysqladmin -uroot -p status如果项目规模大了还是建议接一套监控比如Prometheus加mysqld_exporter一段时间后你会发现它对发现慢查询和连接数暴涨特别有用。连接池这块虽然主要是应用侧配置但数据库侧要注意max_connections不能设太低。Docker默认配置可能只支持151个并发连接如果Java应用连接池初始大小设置得比较大很容易把连接数打满。提前调大max_connections再把应用的wait_timeout调小可以降低“数据库连接数耗尽”的风险。最后分享一个小小的实操习惯每次部署完MySQL容器我会把当前的镜像版本、容器启动参数、数据卷路径、备份策略写进项目的README里哪怕只有几行字。这个习惯已经帮我很多次了尤其是几周后再回来看当初到底怎么部署的光靠记忆是完全不可靠的。祝你在自己的Docker MySQL部署路上少踩几个坑。
返回列表