先从业务场景说起吧。
我在过去几年里接手过不少中小型项目的数据库改造,几乎每个项目走到某个阶段都会撞上同一个问题:单机MySQL撑不住了。可能是高峰期连接数被打满,可能是主库一宕机全站瘫痪,也可能是报表查询拖垮了在线交易。这个时候到处搜“mysql集群架构”的人,多半不是DBA科班出身,而是像我这样从业务开发半路转过来的架构运维人员。我写这篇博文,就是想把这些年在MySQL集群架构搭建和后续多数据源管理上踩过的坑、验证过的方案、可以直接抄作业的配置,一次性讲清楚。
这篇文章适合谁?一类是已经在用MySQL,业务量涨到开始失眠的技术负责人;另一类是正在准备从单机过渡到集群、但对复制原理、高可用切换、多数据源路由这些概念还停留在“听过”阶段的工程师。我会先把集群架构真正解决的痛点讲透,再给出一套从零搭建的GTID主从集群的完整操作,接着把多数据源管理的几种实现路线逐个拆开,最后用一组真实故障排查来收尾。全程没有废话,全部基于我在生产环境里实际跑过的方案。
1. 单机MySQL的瓶颈与集群架构的核心价值
1.1 什么信号出现时,你该认真考虑集群了
先说一个最常见的现象:很多团队直到线上出了事故才开始看集群方案。我不建议你等那个时刻。我总结了几个比较典型的预警信号,命中两条以上就说明单机架构已经进入风险区:
- 主库CPU在业务高峰期持续超过70%,且慢查询数量同步上涨。
- 每天凌晨的定时任务(汇总统计、报表计算)会明显拖慢白天的在线请求。
- 数据库所在磁盘空间频繁告警,扩容需要停机或者依赖云盘在线扩容,但IOPS仍然不够。
- 团队里没人敢在主库上执行DDL,因为一旦锁表,业务直接就断了。
- 你开始犹豫要不要把读写混合的流量拆开,但不知道从哪入手。
这些信号背后其实就三类问题:单一节点的高可用缺失、读写混合带来的资源争抢、单机容量触顶。集群架构的出发点,从来不是“别人都有所以我也要”,而是这三类问题已经真实发生在你业务里。
1.2 集群到底解决哪三类问题:高可用、读写分离、容量扩展
我们逐个拆。
高可用层面,核心是“当主库出现问题,业务不能长时间中断”。MySQL集群最基础的做法就是让数据实时复制到备用节点,主库宕机后由备用节点顶上。很多人对“高可用”有误解,以为只要能复制就是高可用。实际上复制链路只是前提,真正的关键在切换机制——是人工切换、脚本切换,还是由MGR(MySQL Group Replication)这类具备共识协议的方案自动选主。切换的RTO(恢复时间目标)决定了你在领导面前是“修复了半小时”还是“睡了一觉就恢复了”。
读写分离层面,本质是把读流量从主库上剥离出去。MySQL的单机并发能力和数据量强相关,再好的机器也经不住“读多写少”场景下大量慢查询和热点行锁的叠加。引入从库后,读流量可以分摊到多台只读节点上,主库的负载被显著释放。我在一次压测中做过对比,主库单节点在每秒2000个混合请求时CPU已经到80%,做了1主2从读写分离之后,主库在同样的压力下CPU只到35%,三个节点的整体吞吐却翻了接近一倍。
容量扩展层面,要区分两种情况:一种是通过增加从库来扩展读容量,这个简单;另一种是数据总量超出单机可管理范围,比如单表过亿、整体数据量超过数TB。后一种光靠集群复制解决不了,需要配合分库分表或归档策略。这部分我后文会专门说明。
1.3 澄清一个误区:集群不等于分库分表
我见过太多人把这两个概念混在一起。集群复制(Replication)解决的是“同一份数据多节点持有”的问题,所有节点上数据是一致的;分库分表(Sharding)解决的是“数据按规则拆到多个物理库表”的问题,每个节点只持有部分数据。
这两个方向的使用场景完全相反。如果你的瓶颈在读压力、在可用性,你做集群复制;如果瓶颈在单库数据量太大、写入吞吐到顶,你才需要研究分库分表。绝大多数日活十万级别、数据量在TB以下的项目,集群复制加合理索引优化已经可以解决90%的问题。在把分库分表引入生产之前,先确认你确实无法通过集群复制、缓存、归档这些更便宜的方案解决问题。
2. 集群方案选型:异步复制、半同步、MGR与PXC的取舍
2.1 主从复制的基本原理与延迟根源
所有MySQL集群方案,底层都建立在二进制日志(binlog)之上。主库把数据变更写成binlog事件,从库通过I/O线程拉取这些事件写入本地中继日志(relay log),再由SQL线程回放,这就是经典的主从复制流程。
但这里有几个容易忽略的关键点:
- 复制是异步的。主库提交事务并不会等待从库确认,所以从库的数据总是存在一个极小的时间差。
- 从库回放是单线程的(传统复制模式下)。即使主库写入并发很高,从库的SQL线程仍然受限于单线程回放能力,这就是主从延迟的主要来源。
- DDL操作在从库回放时可能会锁表,这也是延迟的另一大来源。
在MySQL 5.7之后的版本里,提供了基于库级别的并行复制,8.0又改进了基于WriteSet的并行复制,延迟问题大幅缓解,但无法完全消除。做架构时心里要始终带着“复制是最终一致”这个前提,凡是要求读己之写(读到自己刚写入的数据)的场景,必须做专门处理,后文会讲。
2.2 MGR与传统主从的关键差异
传统主从复制加上一个VIP(虚拟IP)漂移脚本,是最轻量的高可用方案。它最大的问题是:当主库真正挂掉时,你无法保证从库已经接收了全部binlog。即使开了半同步复制,也仍然存在主库宕机瞬间事务丢失的窗口。这就是为什么很多团队最后会迁移到MGR。
MGR是MySQL官方提供的组复制方案,核心特点有三个:
- 基于Paxos共识协议,多个节点之间通过投票确定谁是主,避免了“脑裂”。
- 只有多数派节点确认的事务才会提交,数据一致性显著强于异步复制。
- 组内节点自动故障检测和切换,不需要额外的VIP脚本。
我第一次在测试环境跑MGR的体验是:对网络稳定性要求确实高,但它解决了传统主从里最让人心虚的“到底丢没丢数据”的问题。代价是写入延迟会有所增加,因为每个事务都要经过组内多数派确认。
2.3 典型方案对照
这里给出我在选型时常用的对照表:
| 维度 | 传统主从 + VIP | 半同步复制 + 自动切换 | MGR组复制 | PXC(Percona XtraDB Cluster) |
|---|---|---|---|---|
| 数据一致性 | 异步,可能丢失 | 减少丢失,仍有风险 | 组内强一致 | 同步复制,无丢失 |
| 写入性能 | 最高 | 较高 | 中等(需多数派确认) | 较低(每次提交需全节点确认) |
| 自动选主 | 依赖外部脚本 | 依赖外部组件 | 内置共识协议 | 内置,但需仲裁节点 |
| 运维复杂度 | 低 | 中 | 中高 | 高 |
| 适合场景 | 小型项目、可接受延迟 | 多数业务系统 | 对一致性有要求的业务 | 一致性极敏感的核心账务类 |
2.4 我的选型建议
结合我实际做过的一组对比,这里给出一个比较务实的建议方向:
- 业务刚起步、团队没有专职DBA:先做传统主从复制,配上GTID和半同步,日常靠从库备份,高可用切换先人工或简单脚本。这阶段的核心目标是有热备节点。
- 业务进入增长期、对RPO(恢复点目标)敏感:直接上MGR或云厂商提供的MySQL高可用版。MGR天然解决的问题就是主库宕机后的事务丢失风险。
- 数据一致性有强诉求、接受写入性能下降:PXC这类强同步方案可以考虑,但要做好所有节点同机房低延迟网络、写入吞吐减半的心理准备。
另外提醒一句:现在不少云数据库产品已经把主从切换封装成自动能力,如果你的部署环境允许,优先用托管方案,自己搭建的自建集群要时刻做好运维投入的准备。
3. 手把手搭建一套GTID主从复制集群
3.1 环境准备:从安装MySQL开始的三条路线
要搭集群,先把MySQL装好。我接触到的安装方式主要有三种,各自适用场景不同:
RPM安装(CentOS/RHEL系列最常见)
# 下载对应系统的rpm包,例如mysql-8.0.36版本 wget https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-8.0.36-1.el7.x86_64.rpm-bundle.tar tar -xvf mysql-8.0.36-1.el7.x86_64.rpm-bundle.tar # 安装顺序有讲究:common必须先装 rpm -ivh mysql-community-common-8.0.36-1.el7.x86_64.rpm rpm -ivh mysql-community-libs-8.0.36-1.el7.x86_64.rpm rpm -ivh mysql-community-client-8.0.36-1.el7.x86_64.rpm rpm -ivh mysql-community-server-8.0.36-1.el7.x86_64.rpm几个易错细节:安装顺序不对会报依赖缺失,建议直接按common、libs、client、server的顺序来;装完后需要执行mysqld --initialize --user=mysql来初始化数据目录,初始化后临时密码在/var/log/mysqld.log里,用grep 'temporary password'查找。
通用tar包安装(适合内网离线环境)
# 以8.4.11 LTS为例 wget https://dev.mysql.com/get/Downloads/MySQL-8.4/mysql-8.4.11-linux-glibc2.28-x86_64.tar.xz tar -xJf mysql-8.4.11-linux-glibc2.28-x86_64.tar.xz -C /usr/local/ mv /usr/local/mysql-8.4.11-linux-glibc2.28-x86_64 /usr/local/mysql useradd -r -s /sbin/nologin mysql mkdir -p /data/mysql chown -R mysql:mysql /data/mysql /usr/local/mysql/bin/mysqld --initialize --user=mysql --basedir=/usr/local/mysql --datadir=/data/mysql这里容易踩的坑是:glibc版本不匹配会导致mysqld启动报错,先ldd --version确认系统的glibc版本再选择对应tar包。8.4是LTS版本,长期稳定性更好,但部分老驱动不一定兼容,需要先确认客户端SDK版本。
Docker方式(适合开发测试环境快速起集群)
docker run -d --name mysql-master \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=Root@123456 \ -v /data/mysql-master:/var/lib/mysql \ -v /etc/mysql-master/conf:/etc/mysql/conf.d \ mysql:8.0 --server-id=1 --log-bin=mysql-bin --gtid-mode=ONDocker方式有两个高频问题:一是容器内mysqld默认不启用binlog,必须通过命令行参数或配置文件挂载来开启;二是宿主机端口映射和容器网络隔离会影响多节点互相访问,用--network=host可以直接规避网络不通的问题。如果你的场景是Mac或Windows桌面上练手,Docker是体验最快的路径。
3.2 主库参数配置要点
主库的关键不是把它“用起来”,而是把复制需要的参数配置正确。下面这份my.cnf我直接给出生产级的基准配置:
[mysqld] server-id = 1 log-bin = mysql-bin binlog_format = ROW binlog_row_image = FULL gtid_mode = ON enforce_gtid_consistency = ON max_connections = 1000 # 半同步复制相关参数(需安装插件后生效) rpl_semi_sync_master_enabled = 1 rpl_semi_sync_master_timeout = 3000 # 建议保留足够的binlog天数 expire_logs_days = 7 innodb_flush_log_at_trx_commit = 1 sync_binlog = 1这里逐个解释我为什么这么配置:
binlog_format = ROW:行级复制比statement更安全,避免了SYSDATE这类函数在从库上回放结果不一致的问题。gtid_mode = ON配合enforce_gtid_consistency = ON:开启GTID之后,每个事务都有全局唯一标识,从库断线重连不再需要记binlog文件和位置号,这对运维来说省心非常多。innodb_flush_log_at_trx_commit = 1和sync_binlog = 1:这两项是为了保证主库每次提交都把日志落盘,虽然会牺牲一点磁盘IO性能,但副本一致性和故障恢复能力大幅提升。追求极致性能的场景可以妥协,但建议先跑默认值。- 半同步插件的加载:MySQL 8.0之后插件名为
semisync_master.so,在MySQL命令行执行INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so'即可,注意需要提前确认插件安装路径。
3.3 从库复制链路建立
从库的配置文件与主库类似,区别是server-id不能重复,且不需要开启log-bin(如果你打算级联复制或作为后续主库,建议保留binlog)。初始化好数据目录后,执行下面几步:
-- 从库创建复制账号(在主库执行) CREATE USER 'repl'@'192.168.1.%' IDENTIFIED BY 'Repl@123456'; GRANT REPLICATION SLAVE ON *.* TO 'repl'@'192.168.1.%'; FLUSH PRIVILEGES;-- 在从库执行,使用GTID方式 CHANGE MASTER TO MASTER_HOST='192.168.1.10', MASTER_PORT=3306, MASTER_USER='repl', MASTER_PASSWORD='Repl@123456', MASTER_AUTO_POSITION=1; START SLAVE; -- 查看复制状态 SHOW SLAVE STATUS\G重点关注Slave_IO_Running: Yes和Slave_SQL_Running: Yes。如果IO线程是Connecting,先检查主库bind-address和防火墙端口;如果是SQL线程No,看Last_SQL_Error字段定位具体语句冲突。
这里要特别强调一个操作习惯:不要在已经写过业务数据、但没有保留完整binlog的实例上强行配复制。最干净的做法是先做一次全量备份(用xtrabackup或mysqldump --master-data=2),在备份机上restore之后再指向主库。跳过这个步骤,你后面会花几倍时间在处理主从数据不一致上。
3.4 GTID模式踩坑:高频错误三例
我在自建集群时,遇到最典型的三个GTID问题:
第一个坑:[ERROR] [MY-014060] [SERVER] Invalid MySQL server upgrade
这个错误一般出现在MySQL升级或数据目录不匹配时。有次我拿着老版本的数据目录启动新版mysqld,直接报了这个错。解决思路不是强行启动,而是使用官网提供的mysql_upgrade流程或重新初始化数据目录。5.7到8.0的升级尤其不能跳过此步骤。
第二个坑:从库复制报Error_code: 1593
提示类似“Could not execute Write_rows event on table xxx”,根本原因是从库上存在主库修改过的行之外的历史数据冲突。处理方式是用pt-table-checksum和pt-table-sync比对修复数据,或者干脆从备份重建该从库。前者更优雅,但需要安装Percona Toolkit。
第三个坑:开启GTID后无法执行DDL
报错通常是Cannot execute statement: impossible to write to binary log since BINLOG_FORMAT = STATEMENT。这是因为GTID模式下某些语句(如基于临时表的操作)在ROW模式下无法复制。简单翻译就是:你用了不兼容的binlog格式。绝大多数情况把临时表去掉或改用普通表就可以绕开。
3.5 高可用决策:VIP+Keepalived还是MGR自动切换
说完复制,高可用这块同样需要决策。这里把两条路线的差异放在一起说清楚。
VIP+Keepalived方案:架构是主库绑定一个虚拟IP,从库做热备,Keepalived周期性检测MySQL进程,挂掉就漂移VIP。优点是配置简单,应用层完全无感知;缺点是从库可能落后主库,切换过程可能丢事务。另外Keepalived有一个经典隐患:主库网络抖动时VIP可能漂到从库,但主库实际还活着,这时“双主写入”就发生了,必须靠MHA这类工具或脚本做额外保护。
MGR方案:不需要部署外部高可用组件,节点间通过内部通信协议自动识别故障并重新选主。从成本上看MGR更省心,但对底层网络的稳定性很敏感,节点间心跳丢失会触发一系列保护动作。在MGR里,切换后的新主库一定是数据最新的节点,这是传统主从方案无法保证的。
我的建议是:如果预算和团队能力允许,优先推MGR。如果你维护的实例很多、网段复杂,可以先用VIP脚本过渡,半年后再引入MGR也不迟。关键是别停在“搭好了主从但从来不演练切换”的状态。
4. 多数据源管理:从“写死多个连接”到“动态路由”
4.1 多数据源场景从哪来
集群搭建只是第一步,应用层怎么把流量正确分发到不同节点,这才是“多数据源管理”的用武之地。我看到的常见场景有三类:
- 读写分离:所有写操作走主库,所有读操作走从库。
- 分库分表:业务数据分布在多个物理数据库实例上,应用需要根据分片键选择不同的数据源。
- 业务隔离:同一个应用服务同时访问不同的业务数据库(订单库、用户库、日志库),它们之间没有主从关系。
在Java生态里,很多时候我们说的“多数据源”是指同一个应用能动态选择不同数据库连接。这也是绝大多数面试和技术博客讨论“多数据源”时的视角。
4.2 基于AbstractRoutingDataSource实现动态切换
Spring的AbstractRoutingDataSource是理解多数据源管理的核心入口,也是很多框架的底层原理。它的工作方式简单说就是:它本身不是真正的数据源,而是一个路由器,运行时通过determineCurrentLookupKey()方法返回一个key,再从预先配置的targetDataSources里拿出对应的真实数据源。
一个最简实现示例:
public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceKey(); } } public class DynamicDataSourceContextHolder { private static final ThreadLocal<String> CONTEXT = new ThreadLocal<>(); public static void setDataSourceKey(String key) { CONTEXT.set(key); } public static String getDataSourceKey() { return CONTEXT.get(); } public static void clear() { CONTEXT.remove(); } }结合读写分离,做法是定义一个“写路由”注解,在执行写操作的方法上标记为走主库,读方法默认走从库。配合AOP切面,在进入方法前把key设置到ThreadLocal里,方法结束清理。
这里有一个关键细节:ThreadLocal必须在方法结束后清除,否则线程池里复用线程会导致后续请求路由错误。使用Spring的@Around切面时用finally块保证清理。
4.3 注解+AOP完成读写分离的实践范式
下面是我在生产项目中验证过的一套路由设计:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RouteTo { String value() default "master"; } @Aspect @Component public class DataSourceAspect { @Around("@annotation(routeTo)") public Object switchDataSource(ProceedingJoinPoint joinPoint, RouteTo routeTo) throws Throwable { DynamicDataSourceContextHolder.setDataSourceKey(routeTo.value()); try { return joinPoint.proceed(); } finally { DynamicDataSourceContextHolder.clear(); } } }使用方式:
@RouteTo("master") public void createOrder(Order order) { // 写操作走主库 } @RouteTo("slave") public List<Order> queryOrders(Long userId) { // 读操作走从库 }这个方案最大的好处是足够简单、可审计,而且对业务代码侵入小。但它有个前提:你必须清楚自己方法的读写属性。如果某个方法既写又读,建议统一走主库,不要在这个方法内部做多数据源切换,事务的复杂性会在这一步集中爆发。
4.4 引入ShardingSphere的现代化玩法
如果你不想自己维护AOP切面,或者需要更复杂的分片能力,ShardingSphere是当前Java生态里比较成熟的中间件。它提供了两种接入模式:
- ShardingSphere-JDBC:以jar包形式嵌入应用,要求数据源连接在应用内部。
- ShardingSphere-Proxy:独立代理服务,应用可以像连接MySQL一样连接它,由Proxy转发到后端真实库。
在我做的项目里,如果只是读写分离,ShardingSphere-JDBC足够;如果需要集中管控、不想每个应用都改配置,Proxy更合适。配置上,ShardingSphere支持YAML格式的数据源、读写分离规则、分片规则,比较直观。它的读负载均衡策略可以轮询或随机,可以设置load-balance-algorithm-type等参数。
4.5 事务与路由冲突:最容易翻车的点
多数据源管理的头号坑,就是事务。@Transactional默认使用的是当前线程绑定的事务管理器,如果你在事务内切换数据源,Spring很可能不会按预期切换,因为事务管理器已经与第一个数据源绑定。更严重的是,同一个事务跨多个数据源写入,单靠Spring声明式事务完全没有跨库原子性保障。
几个实际验证过的结论:
- 同一个事务内的所有操作,尽量保持在同一个数据源。读写分离时,事务方法一律走主库。
- 需要跨数据源写入的,必须引入分布式事务方案。轻量做法是本地消息表或事务消息(RocketMQ/事务消息),重量级方案是Seata AT/TCC模式。
- 千万不要把
DataSource动态路由和@Transactional混在一起天真的以为能自动处理一切,这是生产事故的高发区。
我在一次项目里就亲历过:一个“下单并扣减库存”的方法,写在主库,读从库,由于事务内第二次查询走了从库,读到了旧库存数据,最后靠强制主库读才解决。
5. 集群与多数据源联调:读写分离落地的完整案例
5.1 一套可复用的整体架构
下面用一个订单系统的典型结构来说明,从集群到多数据源路由的完整链路是怎么串联的。
- 数据库层:1个主库(负责写)、2个从库(负责读),主从通过GTID复制。
- 访问层:应用通过MyBatis访问数据库,连接池使用HikariCP。
- 路由层:基于
AbstractRoutingDataSource+ AOP注解,实现读写分离。 - 兜底层:事务方法、需要实时一致性的读方法、定时任务,全部显式路由到主库。
在实际环境中我会把读流量分配做得更细,比如普通订单查询走从库,秒杀/库存类查询直接走主库。这个“风控意识”很重要,因为从库延迟在压测中可能只有几十毫秒,但一旦业务瞬时并发上来,延迟会被放大到秒级,实时性要求高的读一旦走了从库就会出问题。
5.2 主从延迟怎么在应用层兜底
读己之写一致性,这是读写分离必须面对的问题。两个我实测有效的方案:
方案一:关键读强制走主库
对“支付结果查询”“订单创建后立即查看详情”这类场景,在方法上直接用@RouteTo("master")。这种方法成本最低,也是我推荐的默认选项。
方案二:短延迟补偿机制
如果不想所有实时读都走主库,可以在写入时把时间戳缓存到Redis,读请求先从缓存中判断该用户最近是否有写操作,若有则路由到主库。这种方案更精细,但复杂度也随之上升。
这里要说明一个现实情况:主从延迟很多时候不是因为MySQL慢,而是SQL本身有问题,比如一个大事务在从库回放时占用了大量IO。我通常会先排查慢SQL,再做架构层面的规避。
5.3 事务边界设计:把主库流量管起来
落地读写分离之后,主库并没有完全卸下压力,因为事务和实时读还在主库上。所以主库的负载监控依然要盯紧。我在实际项目里会做到以下几点:
- 所有事务方法必须有明确的
@RouteTo("master"),绝不做隐式判断。 - 从库只挂只读账号,从权限上禁止写入,避免误操作导致复制中断。
- 凌晨批处理任务与在线交易错峰执行,必要时把批处理库单独拆出来。
- 定时任务执行前先观察视图指标,如果主库负载偏高,先暂停非核心任务。
6. 集群日常运维与高频故障排查实录
6.1 备份恢复:xtrabackup + GTID 的正确组合
很多自建集群最后栽在备份环节。生产环境里mysqldump在数据量大时速度太慢,我推荐使用xtrabackup做物理备份,不仅快,还能在备份阶段与复制链路共存。
# 在线全量备份(在主库上执行) xtrabackup --backup \ --target-dir=/data/backup/mysql-backup-$(date +%F) \ --user=backup_user --password=xxx --host=127.0.0.1 # 在从库上准备恢复 xtrabackup --prepare --target-dir=/data/backup/mysql-backup-2025-06-01 xtrabackup --copy-back --target-dir=/data/backup/mysql-backup-2025-06-01备份恢复后,如果要从该备份搭建一个新从库,重点检查备份目录里的xtrabackup_binlog_info文件,它记录了备份时刻的binlog位置。配合GTID,只需要在新从库执行CHANGE MASTER TO MASTER_AUTO_POSITION=1即可,甚至不用手动指定位点。
关于备份周期和保留策略,我的习惯是:每天一次全量,保留7天;每小时一次增量(使用--incremental参数),保留24小时;同时binlog保留7天。这样误操作删表时,可以恢复到任意时间点。
6.2 需要盯的几个关键监控指标
自建集群最怕可视化缺失。我通常会至少监控下面几项,并设置告警阈值:
| 监控项 | 指标含义 | 建议阈值 |
|---|---|---|
| 主从延迟(Seconds_Behind_Master) | 从库落后主库的时间 | 持续大于30秒告警 |
| 复制线程状态 | IO/SQL线程是否正常运行 | 出现No立即告警 |
| 主库QPS/TPS | 数据库整体活跃度 | 根据压测基线设置 |
| 磁盘空间 | 数据目录和binlog目录 | 剩余低于20%告警 |
| 连接使用率 | 连接池或最大连接数占用 | 持续高于80%告警 |
| 慢查询数量 | 每5分钟慢SQL条数 | 持续增高需处理 |
6.3 高频问题排查:四个让我印象深刻的案例
案例一:net start mysql服务无法启动
这类问题在Windows环境特别多。先看错误日志,常见原因有:数据目录权限不对、端口被占用、初始化和my.ini不匹配。有一次排查中发现用户把my.ini放到了错误的位置,MySQL服务根本读不到配置。Windows环境建议在命令行里用mysqld --defaults-file=实际路径 --console启动看输出,比看服务管理器里的系统日志直观得多。
案例二:MySQL SSL连接错误
报错大多是“SSL Connection Error: protocol version mismatch”。通常原因是客户端和服务端的TLS版本协商失败,或者客户端连的服务器不支持SSL。解决方案是在连接串上明确指定ssl-mode=DISABLED或ssl-mode=PREFERRED测试。不过生产环境我更建议直接用require强制SSL,并统一升级客户端到8.x新版本,避免走兼容性黑盒。
案例三:Docker方式安装MySQL失败
docker pull mysql后容器起不来的原因往往不是镜像本身,而是/var/lib/mysql挂载目录的权限。容器内mysql用户UID为999,宿主机目录需要chown -R 999:999。我在宿主机上跑过一条命令:sudo chown -R 999:999 /data/mysql,问题立刻解决。另外容器内启动MySQL时如果[ERROR] [MY-014060]这类升级校验报错,多半是数据目录里残留了旧版本文件,清理后重新初始化。
案例四:mysql -u -p 执行SQL超时
这类问题要分成原因来处理。如果只是某个SQL卡住,用SHOW PROCESSLIST查看线程状态;如果所有连接都卡,先查磁盘IO和锁等待。有一次线上只读实例出现大量Waiting for table metadata lock,原因是有一个长时间未提交的DDL事务占用了元数据锁,导致后续读请求全部阻塞。处理办法是KILL那个卡住的事务,并规定长事务必须设置超时时间。
最后再说几句实操心法
这篇文章写到这里,技术内容覆盖已经比较完整了。但我还是想额外说一点:集群架构的真正难点从来不是复制的搭建,而是切换演练和故障复盘。我每搭建完一个集群,都会抽一个低峰时段模拟主库宕机,整个过程标注时间点,检验RTO和RPO是否达标。这个过程做过三遍以上,上了生产才不会心虚。
如果你准备动手做第一套集群,我的建议是先把GTID主从复制跑通,再写一套简单的切换脚本或直接上MGR,然后从业务系统里挑非核心模块先接入多数据源路由,逐步放量。别一上来就折腾分库分表,也别让“高可用”变成一个永远没有演练过的纸面方案。MySQL集群这条路,走得稳比走得快重要得多,希望这篇内容能帮你把第一步踩实。