MySQL 部署、配置与项目接入
从一次“连得上,却不是那套库”开始
开发者刚启动 Compose,应用也显示连接成功,迁移却提示表已经存在;他打开 GUI,看见一批不属于当前项目的库。继续改密码、删表或重跑迁移,只会扩大事故。真正的问题通常是宿主机已有 3306 服务、GUI 保存了旧连接,或者旧 volume 让初始化变量没有再次执行。
先停在只读检查上:
docker version
docker compose version
docker ps --format "table {{.Names}}\t{{.Ports}}"
mysql --version || true如果 3306 已被占用,后面的开发实例使用宿主端口 3307。实验目录只放 Compose、初始化 SQL 和占位凭证;真实密码留在不提交的 .env 或团队密钥系统中。mysql CLI、MySQL Shell、DBeaver、DataGrip、MySQL Workbench 任一种都能完成连接验证,但生产连接应先换成只读账号并在名称中标出环境。
先选清楚版本与部署形态
MySQL 同时维护 LTS 与 Innovation 发行轨道。核心业务通常优先选择团队已经验证过的 LTS 大版本,并跟随该大版本的安全补丁;需要新特性的团队再评估 Innovation 版本带来的升级频率。MySQL 发布轨道说明还明确了两条轨道的支持周期和升级路径:8.4 与 9.7 都是 LTS 代际,8.4.x 可以升级到下一条 9.7.x LTS,但不能把跨 LTS 升级当成普通补丁更新。下面保留 8.4 作为兼容面更成熟的实验基线;新项目仍要比较 9.7 的驱动、周边工具、托管服务和迁移成本后再定版。镜像 mysql:8.4 会随补丁发布漂移,CI 或恢复演练追求完全复现时应固定补丁 tag,必要时再固定 digest;latest 无法表达兼容基线。
许可证也会改变交付方式。MySQL Server 和客户端库采用 GPLv2 与商业许可并行的模式;把 MySQL 二进制嵌入、捆绑并分发到闭源商业产品时,不能把“能免费下载”当成已经满足交付合规,应根据MySQL 双重许可说明审查具体发行物、Connector、FOSS Exception 和商业许可。Enterprise Backup 等商业能力、PXC、ProxySQL、MHA 和各类云服务还要分别核对版本对应的许可与支持合同,不能由 MySQL Server 的许可替它们背书。
Docker 镜像的初始化变量和 /docker-entrypoint-initdb.d/ 只在空数据目录上执行。MYSQL_ROOT_PASSWORD、MYSQL_RANDOM_ROOT_PASSWORD 与允许空密码的开关代表不同安全选择,共享环境不能靠空密码省掉凭证管理。MySQL Docker 安装说明给出了初始化行为;Compose 的 depends_on 只保证启动顺序,应用仍需要健康检查和连接重试。
复制与高可用也受版本和工具组合约束。Group Replication 管理成员关系与复制状态,客户端故障转移仍需要 Connector、负载均衡器或 MySQL Router;InnoDB Cluster 把 MySQL Server、Group Replication、MySQL Shell 与 Router 组合成完整控制链路。ProxySQL、ShardingSphere、PXC、MHA 则各有独立的发布周期、许可证和维护成本,进入选型表之前应验证目标版本、故障模式与团队接管能力。
数据库“已经可用”至少有四层含义。进程可响应只是第一层;业务账号能在指定 schema 完成写读删是第二层;应用连接池、迁移工具和测试数据能重复初始化是第三层;备份可恢复、复制可追平、故障切换后旧主不会继续接收写入,才接近生产可用。验证记录应明确自己证明了哪一层,避免把 mysqladmin ping 的成功误当成系统验收。
版本升级也沿这四层推进。先在一次性实例恢复脱敏备份,比较字符集、排序规则、SQL mode 和执行计划;再跑迁移与应用回归;随后验证复制、备份和监控工具;最后才安排共享环境或生产窗口。任何阶段出现不可接受的计划退化、复制错误或恢复失败,都回到旧镜像与旧数据副本,而不是在唯一数据目录上反复换 tag。
MySQL 主流部署入口不能只写一种。不同入口解决的是不同问题。生产语境里先分清四类标准部署方式,再讨论 Docker / Compose 这类开发验证入口。
| 入口 | 定义 | 特点 | 适用场景 | 主要风险 |
|---|---|---|---|---|
| 单机单实例部署 | 一台服务器运行一个 MySQL 进程 | 最简单、成本低、无冗余 | 本地开发、测试、小型内部系统、非核心静态业务 | 单点故障、容量和并发上限低 |
| 单机多实例部署 | 一台服务器运行多个独立 MySQL 进程,端口和目录隔离 | 资源复用、隔离轻量、多版本可共存 | 测试环境、多业务轻量隔离、迁移演练 | CPU / IO 互相抢占,目录和端口容易混乱 |
| 物理机集群部署 | 多台独立服务器部署 MySQL,通过复制、HA 或代理协作 | 资源独享、性能稳定、可控性强 | 核心生产业务、强治理团队、自建 IDC | 运维成本高,备份、切换和监控必须配套 |
| 云化部署 | 云厂商 RDS MySQL、云原生 MySQL、Serverless MySQL | 免运维、弹性、控制台化 | 动态流量、中小体量生产、团队 DBA 能力不足时 | 成本、厂商绑定、权限和审计边界 |
开发验证入口单独看:
| 入口 | 主要用途 | 能验证什么 | 不能证明什么 |
|---|---|---|---|
| 本机包管理器 / 安装包 | 学习命令、验证客户端、单机实验 | 本机连接、SQL 行为、客户端配置 | 团队一致性、隔离、清理、版本可复现 |
| Docker 单容器 | 快速验证一个 MySQL 进程 | 版本、端口、账号、初始化脚本 | 生产高可用、备份恢复、资源隔离 |
| Docker Compose | 项目本地联调和 CI 临时依赖 | 服务名、网络、volume、健康检查、项目接入 | 生产容量、切换、审计、长期运维 |
| 本地多实例 | 复制、读写分离、故障切换演练 | server_id、端口目录隔离、基础复制链路 | 真实网络、磁盘、机房故障和生产 SLA |
四类标准部署方式使用不同的验证信号。命令成功还不够,证据必须能对应实例身份、数据路径、复制状态与恢复能力:
| 部署方式 | 最小验证入口 | 必须看到的证据 | 清理或回滚边界 |
|---|---|---|---|
| 单机单实例 | mysql -h <host> -P <port> -u <user> -p -e "SELECT VERSION(), @@hostname, @@port;" | 版本、端口、当前库、普通账号权限、备份文件可恢复 | 本地可删除实例或 volume;生产必须先确认备份 |
| 单机多实例 | 分别连接 3307、3308,查询 @@server_id、数据目录和日志目录 | 两个实例端口、server_id、datadir、socket、binlog 不冲突 | 停错实例风险高,脚本必须打印实例名和目录 |
| 物理机 / 虚机集群 | 查询 source / replica 状态、复制位点、只读状态和切换入口 | SHOW REPLICA STATUS\G、主从延迟、备份位置、故障切换演练记录 | 不在业务高峰做破坏性演练;旧主必须隔离 |
| 云化托管 | 控制台或 API 查看版本、备份策略、白名单、账号、参数组和只读实例 | SLA、备份保留、恢复粒度、网络入口、审计和费用标签 | 释放资源前确认快照、备份、DNS / endpoint 和账号回收 |
本机安装先把隐式状态显式化
需要系统服务、客户端兼容或多版本迁移实验时,可以按 MySQL 8.4 安装手册选择 Windows MSI、macOS DMG、Oracle Yum / APT 仓库或目标平台的二进制包。仓库安装时要明确选择 8.4-lts 轨道;下载页默认展示的版本可能已经变化,不能一路点击默认选项后再假设装到的是 8.4。
安装完成后先留下这些证据:
mysql --version
mysqladmin --version
mysqladmin -h 127.0.0.1 -P 3306 -u root -p ping
mysql -h 127.0.0.1 -P 3306 -u root -p -e "SELECT VERSION(), @@hostname, @@port, @@datadir;"Windows 服务名、Linux systemd unit、端口、配置文件、数据目录、error log、初始 root 凭证处理方式和卸载步骤都要记录。卸载软件包通常不会等价删除数据目录;清理前先打印 @@datadir 并确认没有其他实例使用它。若只是需要 mysql、mysqldump 或 mysqlbinlog,优先只安装客户端工具,服务端仍放进项目 Compose,能少一套难以复现的本机状态。
Docker 单容器完成一次可清理实验
单容器适合验证初始化变量、账号、字符集和客户端行为,随后可以完整删除:
docker volume create mysql84-sandbox-data
docker run -d \
--name mysql84-sandbox \
-p 127.0.0.1:3307:3306 \
-e MYSQL_ROOT_PASSWORD=YOUR_MYSQL_ROOT_PASSWORD \
-e MYSQL_DATABASE=app_dev \
-e MYSQL_USER=app_dev \
-e MYSQL_PASSWORD=YOUR_MYSQL_APP_PASSWORD \
-v mysql84-sandbox-data:/var/lib/mysql \
mysql:8.4 \
--character-set-server=utf8mb4 \
--collation-server=utf8mb4_0900_ai_ci \
--default-time-zone=+00:00先确认进程响应,再用应用账号做真实写读删:
docker exec mysql84-sandbox sh -lc 'mysqladmin ping -h 127.0.0.1 -uroot -p"$MYSQL_ROOT_PASSWORD" --silent'
docker exec -i mysql84-sandbox sh -lc \
'mysql -h 127.0.0.1 -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DATABASE"' <<'SQL'
CREATE TABLE tool_verify (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
marker VARCHAR(64) NOT NULL
);
INSERT INTO tool_verify(marker) VALUES ('single-container-ready');
SELECT marker FROM tool_verify;
DROP TABLE tool_verify;
SQL预期先看到 mysqld is alive,随后查询返回 single-container-ready。如果第二条命令报认证失败或库不存在,先看 docker logs mysql84-sandbox 和 volume 是否曾被初始化,不要反复改密码变量。实验结束后先删容器,再明确删除测试 volume:
docker rm -f mysql84-sandbox
docker volume rm mysql84-sandbox-data删除 volume 会永久删除该实验实例的数据;共享实例和生产实例禁止复用这组清理命令。
架构师真正要做的不是背命令,而是先回答四个问题:
这套 MySQL 是临时验证、共享开发,还是生产核心?它能接受多久不可用,能接受丢多少数据?团队是否有能力维护复制、故障切换、备份恢复和权限治理?
业务瓶颈是连接数、读并发、写入、存储容量,还是跨库事务?
开发机 Compose 基线
先给一套能被项目复用的开发入口。它不是生产模板,但能把版本、端口、volume、初始化、健康检查、字符集和时区固定下来。
your-project/
compose.yaml
.env.example
db/
mysql/
conf.d/
dev.cnf
init/
01-schema.sql
02-seed.sql
verify/
verify.sql
docs/
dependency-setup.md
scripts/
dev-up.sh
dev-reset.sh.env.example 只放占位符:
MYSQL_IMAGE=mysql:8.4
MYSQL_HOST_PORT=3307
MYSQL_DATABASE=app_dev
MYSQL_APP_USER=app_dev
MYSQL_APP_PASSWORD=replace-with-local-password
MYSQL_ROOT_PASSWORD=replace-with-local-root-password
MYSQL_TIME_ZONE=+00:00compose.yaml:
services:
mysql:
image: ${MYSQL_IMAGE:-mysql:8.4}
container_name: your-project-mysql
ports:
- "127.0.0.1:${MYSQL_HOST_PORT:-3307}:3306"
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE:-app_dev}
MYSQL_USER: ${MYSQL_APP_USER:-app_dev}
MYSQL_PASSWORD: ${MYSQL_APP_PASSWORD}
TZ: UTC
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_0900_ai_ci
- --default-time-zone=+00:00
volumes:
- mysql-data:/var/lib/mysql
- ./db/mysql/conf.d:/etc/mysql/conf.d:ro
- ./db/mysql/init:/docker-entrypoint-initdb.d:ro
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -p$${MYSQL_ROOT_PASSWORD} --silent"]
interval: 10s
timeout: 5s
retries: 12
restart: unless-stopped
volumes:
mysql-data:
name: your-project-mysql-data关键点:
宿主只绑定 127.0.0.1,开发机默认不向局域网暴露。宿主端口用 3307,容器内仍是 3306。volume 显式命名,排障时能一眼看到旧数据是否还在。
初始化脚本只在空数据目录执行,改脚本不等于旧库自动重建。healthcheck 只证明 MySQL 进程能响应认证请求,不证明 app_dev、表结构、migration 和测试数据都已完成。应用启动仍要有连接重试和 migration 成功校验。
新版本 MySQL 默认采用 row-based logging 方向;binlog_format 从 8.0.34 起已被标记为 deprecated。开发模板不主动塞这个参数;生产复制、CDC、审计和 PITR 上线前要在目标版本查询 log_bin、GTID、binlog 保留时间与行格式,并用一笔写入验证下游消费。
db/mysql/conf.d/dev.cnf:
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_0900_ai_ci
default-time-zone=+00:00
max_connections=200
skip-name-resolve=ON
slow_query_log=ON
long_query_time=1
[client]
default-character-set=utf8mb4skip-name-resolve 可以减少 DNS 反查干扰,但它会影响按主机名授权的习惯。共享开发实例是否开启,要由环境 owner 决定。
初始化脚本要最小、幂等、可清理:
CREATE TABLE IF NOT EXISTS user_demo (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_user_demo_username (username)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE IF NOT EXISTS tool_verify (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
marker VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;单机多实例配置要点
单机多实例不是复制一份配置换端口这么简单,至少要隔离:
| 对象 | 示例 | 判断标准 |
|---|---|---|
| 端口 | 3307、3308、3309 | netstat 或 lsof 无冲突 |
| 数据目录 | /data/mysql3307/data | 不能共用 /var/lib/mysql |
| 日志目录 | /data/mysql3307/log | error log、slow log、binlog 可追溯 |
| socket / pid | /data/mysql3307/run/mysql.sock | systemd 管理不串实例 |
| server_id | 3307、3308 | 如果复制,必须全局唯一 |
| 配置文件 | my3307.cnf | 每个实例独立审查 |
多实例适合测试和轻量隔离,不适合假装高可用。服务器坏了,所有实例一起不可用。
开发环境不是容器 Up 就结束,必须证明“库、表、账号、字符集、写读清理、项目连接”都成立。
docker compose --env-file .env up -d mysql
docker compose logs -f mysql等健康检查通过:
docker compose ps mysql用应用账号连接:
mysql -h 127.0.0.1 -P 3307 -u app_dev -p app_dev执行最小 SQL:
SELECT VERSION() AS mysql_version;
SELECT @@character_set_server, @@collation_server, @@time_zone;
SHOW VARIABLES WHERE Variable_name IN ('log_bin', 'gtid_mode', 'binlog_expire_logs_seconds');
SELECT DATABASE() AS current_database, CURRENT_USER() AS current_user;
INSERT INTO tool_verify(marker) VALUES ('mysql-tool-ready');
SELECT id, marker, created_at FROM tool_verify ORDER BY id DESC LIMIT 1;
DELETE FROM tool_verify WHERE marker = 'mysql-tool-ready';如果准备验证慢 SQL 和执行计划,再加一组可控数据:
EXPLAIN FORMAT=TREE
SELECT * FROM user_demo WHERE username = 'demo';
EXPLAIN ANALYZE
SELECT * FROM user_demo WHERE username = 'demo';验证完成后不要立刻 down -v。先确认是否需要保留测试数据:
docker compose down
docker volume ls | grep your-project-mysql-data只有确认要重置开发库时才执行:
docker compose down -v项目接入 MySQL 至少要把“连接串、账号权限、连接池、迁移、环境隔离、危险操作保护”写清。
JDBC URL
jdbc:mysql://127.0.0.1:3307/app_dev?useUnicode=true&characterEncoding=utf8&connectionTimeZone=UTC&forceConnectionTimeZoneToSession=true不要把 allowPublicKeyRetrieval=true 当默认模板。它最多用于受控本地排障,不适合共享环境和生产模板。
Spring Boot 本地 profile
spring:
datasource:
url: jdbc:mysql://${MYSQL_HOST:127.0.0.1}:${MYSQL_PORT:3307}/${MYSQL_DATABASE:app_dev}?useUnicode=true&characterEncoding=utf8&connectionTimeZone=UTC&forceConnectionTimeZoneToSession=true
username: ${MYSQL_APP_USER:app_dev}
password: ${MYSQL_APP_PASSWORD}
hikari:
maximum-pool-size: 10
minimum-idle: 2
connection-timeout: 3000
validation-timeout: 2000
flyway:
enabled: true
locations: classpath:db/migration连接池不是越大越好。开发环境连接池过大会掩盖生产连接数设计问题;共享环境里一个服务开 50 个连接,十几个服务就能把实例打满。
迁移工具接入
建议项目保留:
src/main/resources/db/migration/
V1__create_user_demo.sql
V2__add_order_table.sql迁移脚本必须遵守:
DDL 不直接改生产,先在本地和共享环境跑。大表变更必须评估锁、回滚、灰度和索引构建影响。初始化脚本和 migration 不能互相打架:Compose init 只建开发空库,业务 schema 以 migration 为准。
删除字段、删索引、改类型必须有兼容期,不能和应用发布绑定成一次不可回滚变更。
从开发实例走向生产
开发实例的目标是让每位成员得到一致、可销毁、可重复验证的数据库;生产系统的目标则是持续守住数据正确性、可用性、恢复能力和成本边界。两者可以使用同一 MySQL 大版本,却不能共用同一套验收结论。容器启动成功、应用能连接,只能证明接入链路成立,不能证明生产架构成立。
先用故障域定义部署形态,而不是用库表数量定义架构。单机架构是一台主机上的一个 MySQL 服务实例;它可以承载多个 schema 和大量表,也仍然只有一个主机故障域。“单库单表”是逻辑数据模型,不是单机架构的定义。单机多实例虽然隔离了端口、目录和进程,但主机、磁盘、网络和电源仍可能共同失效,因此也不构成高可用。
生产化之前先回答六个问题
| 决策问题 | 开发实例可以接受 | 共享或生产环境必须给出的证据 |
|---|---|---|
| 实例身份 | 本机端口和容器名明确 | 主机、端口、server UUID、环境、owner 和数据目录可追溯 |
| 数据损失 | 测试数据可重建 | RPO 有业务确认,备份与日志保留覆盖恢复窗口 |
| 服务中断 | 手工重启即可 | RTO、故障检测、流量切换、旧主隔离和回切步骤经过演练 |
| 访问边界 | 本机应用账号 | 网络白名单、TLS、最小权限、凭证轮换和审计均有负责人 |
| 容量上限 | 单人联调负载 | 连接、内存、IO、磁盘增长、备份窗口和成本有基线与告警 |
| 变更方式 | 重建 volume | DDL、参数、升级和恢复都有灰度、验证与回滚路径 |
如果这些答案还不存在,务实的选择通常是先采用团队能治理的单实例或托管数据库,而不是直接堆叠代理、同步集群和分片中间件。复杂拓扑不会自动产生高可用,只会增加需要同时正确运行的控制面。
架构演进不是产品功能清单
常见形态可以按“解决什么约束”理解:
| 形态 | 主要解决的问题 | 不会自动解决的问题 | 进入条件 |
|---|---|---|---|
| 单机单实例 | 最低部署和维护成本 | 主机单点、读写扩展、跨故障域恢复 | 非核心或可接受停机,恢复步骤明确 |
| 一主一从 / 一主多从 | 数据冗余、读扩展、备选节点 | 自动切换、零数据损失、写后读一致性 | 复制延迟可观测,提升和旧主隔离可执行 |
| InnoDB Cluster / 其他 HA 控制面 | 成员管理、故障判断和切换编排 | 客户端路由、业务幂等、跨地域零损失 | 仲裁、fencing、路由和回切演练均成立 |
| 代理读写分离 | 路由收敛、连接管理、读流量分担 | 复制延迟语义、代理自身高可用 | 已标识强一致读,并有延迟降级策略 |
| 分库分表 | 单写、容量或单表维护边界 | 跨片事务、查询、扩容和数据修复 | 单库治理已到边界,分片键和迁移回滚经过验证 |
| 云托管 MySQL | 转移部分硬件、备份和 HA 运维 | 账号治理、SQL 性能、成本、审计和退出能力 | 已核对 SLA、网络、权限、恢复粒度和费用模型 |
不要按“单机 -> 主从 -> MGR -> 分片”的顺序机械升级。读多不等于必须读写分离,表大不等于必须分片,采用云服务也不等于不再需要恢复演练。先用指标和故障证据确认瓶颈,再选择最小复杂度的有效方案。
两条进阶学习路径
当问题落到 SQL 为什么慢、索引为什么失效、事务为何互相阻塞,或者需要解释崩溃恢复与提交一致性时,继续阅读 MySQL InnoDB、查询优化与事务锁:从慢查询现场到内核治理。该专题从可销毁实验台出发,深入 B+Tree、统计信息、执行计划、MVCC、锁观测以及 redo 与 binlog 的两阶段提交,不在这里重复一张简化但容易误导的 UPDATE 提交图。
当问题已经涉及 GTID 复制、半同步、故障切换、旧主隔离、Router / ProxySQL、分片迁移、备份恢复或云托管取舍,继续阅读 MySQL 复制、高可用、读写分离、分片与恢复实战。生产验收必须以复制证据、故障注入、路由结果、数据校验和恢复演练为准,不能从开发 Compose 的成功外推。
两条路径不是彼此替代:内核专题回答单实例内部为什么出现性能与一致性问题,生产架构专题回答多节点、路由和恢复链路如何守住业务承诺。主文的 Compose 实例可以作为前者的实验入口,但不能直接充当后者的生产模板。
把上线判断落到证据
从开发走向共享环境时,至少保留一份可复核的验收记录:
环境身份:
endpoint / port / version / server_uuid / owner
接入证据:
应用账号写读删
migration 执行结果
字符集与时区
连接池上限与超时
安全证据:
网络入口与 TLS
最小权限
凭证来源、轮换与回收
运行证据:
慢查询入口
磁盘与连接基线
告警接收人
配置变更记录
恢复证据:
备份位置与保留周期
最近一次恢复演练
恢复耗时与校验结果验收记录里的密码、私钥和完整生产连接串必须引用密钥标识,不能直接粘贴。若实例还没有备份恢复、故障处置和负责人,文档应明确它只是开发或临时共享实例,不要在名称上伪装成生产能力。
# 查看容器和端口
docker compose ps
docker compose logs --tail=200 mysql
# 进入客户端
mysql -h 127.0.0.1 -P 3307 -u app_dev -p app_dev
# 看变量
mysql -h 127.0.0.1 -P 3307 -u app_dev -p -e "SHOW VARIABLES LIKE 'character_set_server';"
# 导出开发库
mysqldump -h 127.0.0.1 -P 3307 -u app_dev -p app_dev > app_dev.sql
# 停止但保留数据
docker compose down
# 重置开发库,破坏性操作
docker compose down -v生产或共享环境必须把危险操作包装成脚本,脚本先打印 host、port、database、user 和环境,再要求显式确认。不要让任何人复制一条 DROP DATABASE 裸命令。
端口占用或误连旧库
现象:容器启动失败,或者连接成功但库、表、账号都不是当前项目模板创建的内容。
判断:
docker compose ps
docker ps --format "table {{.Names}}\t{{.Ports}}"
netstat -ano | findstr ":3306 :3307"修复:先确认宿主端口映射,再检查应用连接串和 GUI 保存的历史连接。开发实例统一使用宿主 3307,避开本机已有 3306。再验证时执行 SELECT @@hostname, @@port, DATABASE();,确认连到目标实例和目标库。
旧 volume 污染
现象:修改 root 密码、初始化脚本、库名或字符集后不生效,表结构像是旧环境留下来的。
判断:
docker volume ls | grep your-project-mysql
docker volume inspect your-project-mysql-data
docker compose logs --tail=120 mysql原因:Docker 官方镜像的初始化变量和 /docker-entrypoint-initdb.d/ 只在数据目录为空时执行。已有 volume 会保留旧账号、旧 schema、旧表和旧配置效果。
修复:个人本地可以执行 docker compose down -v 破坏性重置;共享环境不能这么做,必须先确认数据 owner、备份和清理窗口。再验证时重新执行建表、写入、查询、删除闭环。
初始化脚本没执行
现象:容器 healthy,但应用启动报表不存在、用户不存在或权限不足。
判断:看 entrypoint 日志里是否出现初始化目录执行记录,确认挂载路径是 /docker-entrypoint-initdb.d/,再检查数据目录是否首次初始化。
修复:把初始化脚本放入 db/mysql/init/,按文件名顺序明确 01-schema.sql、02-user.sql、03-seed.sql。如果旧 volume 已经初始化过,修改脚本不会自动重放,只能重建本地 volume 或通过 migration 工具补变更。
字符集、排序规则或时区不一致
现象:中文乱码、排序结果和预期不一致、时间差 8 小时、跨环境测试失败。
判断:
SHOW VARIABLES LIKE 'character_set%';
SHOW VARIABLES LIKE 'collation%';
SELECT @@global.time_zone, @@session.time_zone;
SHOW CREATE TABLE tool_verify\G修复:统一 server、database、table、connection 和 JDBC 连接参数;默认使用 utf8mb4,避免旧 utf8 / utf8mb3 口径。时间问题要同时看 MySQL session、JDBC、应用序列化和容器/宿主系统时区。再验证时写入中文、emoji、UTC 时间和本地时间各一条。
连接池打满
现象:接口大量超时,MySQL CPU 不一定很高,但应用线程池和连接池都排队。
判断:统计服务副本数、每个连接池最大连接数、代理层连接数和 MySQL max_connections;同时查慢 SQL、长事务和连接泄漏。
修复:先治理慢 SQL、长事务和泄漏,再收敛连接池上限。不要简单把 max_connections 调大,因为内存、线程和锁竞争会一起放大。再验证时看连接池 active / idle、MySQL Threads_connected 和接口 p95。
问题已经越过部署接入层
如果现象是执行计划漂移、锁等待、死锁或大事务阻塞,先保留慢日志、事务、锁和 SQL 现场,再沿 MySQL InnoDB、查询优化与事务锁 的证据链排查。不要通过无限增大连接池、关闭隔离或随手加索引掩盖根因。
如果现象是复制延迟、节点切换、读写路由、分片查询或备份恢复失败,先停止破坏性修复并保护位点、GTID、错误日志和路由状态,再进入 MySQL 复制、高可用、读写分离、分片与恢复实战。这些问题需要多节点证据和恢复边界,不能用开发实例的重建方式处理。
网络、权限与凭证
账号分层:
| 账号 | 权限 | 使用场景 |
|---|---|---|
root | 管理权限 | 本机初始化、紧急维护,不给应用 |
app_dev | 指定库 DML / 必要 DDL | 本地开发 |
app_test_rw | 测试库读写 | 测试环境 |
app_prod_rw | 生产必要读写 | 应用生产账号 |
app_prod_ro | 只读 | 报表、排查、控制台 |
migration_user | 变更权限 | Flyway / Liquibase / 变更平台 |
凭证规则:
.env 不提交,.env.example 只放占位符。GUI 客户端不要保存生产高权限密码。生产账号按服务拆,不跨项目共用。
只读账号也要限制库、表和来源。离职、项目下线、环境销毁时要回收账号。连接串、日志、截图和验收记录不得携带明文密码或可复用 token。
网络入口也属于权限边界。本地 Compose 只绑定 127.0.0.1;共享环境通过防火墙、安全组或私网入口限制来源;生产连接应核对服务端证书、主机名校验和驱动 TLS 模式。不要为了排障临时把 3306 暴露到公网,更不要用“密码足够复杂”替代网络隔离。
权限调整后要从应用账号重新连接并执行允许与拒绝两组验证。例如应用账号应能操作业务表,却不应创建用户、读取其他 schema 或修改全局参数。只验证正向权限,会把过度授权留到事故现场。
一个团队真正落地 MySQL,至少要交付这些东西:
docs/mysql/
architecture-decision.md
dependency-setup.md
failover-runbook.md
backup-restore-runbook.md
index-change-rules.md
db/
migration/
seed/
scripts/
mysql-dev-up.sh
mysql-dev-reset.sh
mysql-verify.sh
mysql-dump-dev.sh职责分工:
| 角色 | 负责 |
|---|---|
| 架构师 | 架构选型、边界、容量演进、关键风险 |
| DBA / DevOps | 部署、备份、监控、切换、权限 |
| 后端负责人 | SQL、事务、连接池、迁移脚本 |
| 测试负责人 | 数据准备、回归、压测、故障演练 |
| 安全负责人 | 凭证、审计、脱敏、生产访问 |
落地清单:
每个环境有 owner。每个库有用途、生命周期和清理策略。每个账号有权限说明和回收策略。
每个生产变更有回滚方案。每个备份有恢复演练记录。每个慢 SQL 有归因和关闭标准。
每个读写分离规则有业务语义说明。
实例身份必须先于任何写操作
端口能连通不代表连接目标正确。VPN、SSH 隧道、DNS、端口转发、GUI 历史连接和本机旧服务都可能把同一个 localhost:3306 指向不同实例。每个环境应有不可混淆的连接名称,脚本执行 DDL 或清理前打印 endpoint、@@hostname、@@port、@@server_uuid、当前库和当前用户。
判断标准:任何破坏性脚本在身份不匹配时自动终止;共享与生产连接不能只用 localhost、mysql 或 default 这类名称。应用日志可以记录环境标识和 endpoint 别名,但不能输出密码。
volume 是数据生命周期,不是容器附件
Compose 文件可以重建容器,命名 volume 却会跨重建保留。初始化变量、init SQL 和 root 密码只对空数据目录生效,修改 .env 后重启不会改写已经初始化的账号。团队必须区分“停止服务”“重建容器”和“删除数据”三类动作。
判断标准:down -v、docker volume rm 和数据目录删除只能存在于显式 reset 脚本;脚本先展示 volume 名、项目名和环境,并要求人工确认。共享环境禁止沿用开发机 reset 脚本。
配置文件、运行参数与数据库变量会漂移
镜像 command、conf.d、环境变量、启动参数、SET PERSIST 和云参数组都可能改变最终配置。只审查仓库里的 dev.cnf 无法证明实例按它运行。升级或迁移后,字符集、排序规则、时区、SQL mode、连接上限和日志配置尤其容易漂移。
用运行值建立基线:
SELECT VERSION(), @@hostname, @@port, @@server_uuid;
SHOW VARIABLES WHERE Variable_name IN (
'character_set_server',
'collation_server',
'time_zone',
'sql_mode',
'max_connections'
);判断标准:项目保留期望值与运行值的差异记录;关键变量变化有负责人、变更原因、验证结果和回滚方式。示例配置不能被当成所有环境的事实。
凭证轮换不能靠一次性改密码
应用、迁移任务、GUI、CI 和监控可能分别缓存连接凭证。直接替换账号密码会让一部分连接继续工作、另一部分突然失败,造成难以判断的灰度事故。共享与生产环境应由密钥系统下发,采用“创建新凭证 -> 更新消费者 -> 验证旧连接归零 -> 回收旧凭证”的轮换过程。
判断标准:团队能列出凭证的所有消费者;日志和错误页不会打印完整 JDBC URL;离职、服务下线和密钥泄露都有可执行的回收路径。本地 .env 泄露后也按已泄露凭证处理,不能只从 Git 历史删除文件。
共享实例最容易被连接和测试数据拖垮
共享开发库通常不会先遇到 SQL 极限,而会被无限连接池、长期空闲会话、批量造数、未清理 schema 和大导入占满。每个服务副本的连接池上限相加后,必须低于实例承载边界并留出迁移、监控和排障连接。
判断标准:连接数、磁盘增长和慢查询有团队可见的基线;测试数据有 owner、有效期和清理任务;批量导入先在隔离 schema 验证。不要通过持续增大 max_connections 延后连接泄漏的暴露。
GUI 客户端既提升效率,也扩大误操作面
DataGrip、DBeaver 和 Workbench 会保存连接、自动补全对象、展示全库元数据,也可能在错误窗口执行 DROP 或导出敏感数据。生产连接默认只读,连接名必须同时标出环境和权限,例如 prod-orders-readonly,并使用明显不同的终端或客户端配色。
判断标准:高权限连接不保存在个人默认连接中;导出数据经过审批和脱敏;危险 SQL 有审计;排障完成后及时关闭隧道并回收临时授权。客户端“提示将影响多行”不能替代服务端权限和变更流程。
生产化问题要进入对应证据链
当容量、SQL、事务和锁成为主要矛盾,使用 InnoDB、查询优化与事务锁专题 建立负载、计划和锁证据;当复制、切换、路由、分片和恢复成为主要矛盾,使用 高可用、扩展与恢复专题 建立多节点和演练证据。部署主文负责提供可靠入口,不替生产专题宣称尚未验证的能力。
部署与身份:
版本轨道和镜像基线明确,不使用 latest 表达生产兼容性。endpoint、端口、server_uuid、数据目录、日志位置和 owner 可追溯。单机多实例隔离端口、数据目录、socket、PID、日志和 server_id。
Docker、Compose、本机服务或云实例的故障域没有被混淆。
配置与验证:
字符集、排序规则、时区、SQL mode 和连接上限已核对运行值。应用账号完成建表或 migration、写入、读取、删除和拒绝权限验证。初始化脚本仅用于空数据目录,旧 volume 的行为已有反向验证。
停止、重建容器和删除数据是三条不同且有保护的操作路径。
项目接入:
JDBC URL、连接池、超时和环境变量进入项目模板。.env.example 只有占位符,仓库、日志和截图没有真实凭证。migration 可审查、可重复执行,破坏性变更有兼容期和回滚方式。
应用启动会等待数据库与 migration 就绪,不只依赖容器启动顺序。
故障与清理:
能识别端口占用、误连旧库、旧 volume、初始化未执行和时区漂移。reset 脚本会打印项目、环境和 volume,并要求显式确认。连接池总量为迁移、监控和排障保留容量。
临时账号、隧道、测试 schema、容器和 volume 有清理负责人。
安全与团队治理:
root 不给应用,生产 GUI 连接默认只读并明确标识环境。网络入口受限,共享或生产链路已核对 TLS 与证书校验方式。账号按服务和环境拆分,凭证有轮换、审计与回收路径。
运行配置、危险操作、数据导出和恢复责任均有 owner。
生产化交接:
SQL、索引、事务或锁问题进入 InnoDB 专题的证据链。复制、HA、路由、分片和恢复问题进入生产架构专题的演练链。未完成恢复和故障演练的实例没有被标记为已具备生产能力。
