Zabbix数据库从MySQL迁移到TiDB的实战与避坑指南 如果把一套线上规模不小的 Zabbix 数据库从 MySQL 迁到 TiDB听起来像是个挺折腾的事。但做下来你会发现因为 TiDB 在协议层面兼容 MySQL整个迁移并没有想象中那么重的改造量。这次项目就是冲着解决监控数据库的扩容和清理痛点去的用 TiDB 替换 MySQL 作为 Zabbix 的存储层前端和后端几乎没动数据先全量导入再验证性能与稳定性。整条链路跑通后Zabbix 的写入压力、历史数据堆积、清理慢的问题都有了明显改善。这个项目比较适合两类人看一类是正在被 Zabbix 历史数据膨胀、MySQL 单机写入瓶颈折磨的运维另一类是刚接触 TiDB、想了解分布式数据库怎么和现有 MySQL 生态对接的 DBA。我会把迁移过程中踩过的坑、试过的方案选择、以及最后的配置调整都写出来尽量让你照着做也能复现。1. 项目背景为什么监控库要先“扛不住”1.1 Zabbix 的数据模型与存储压力Zabbix 绝大部分数据都集中在几张表里history、history_uint、history_str、trends、trends_uint。这些表的特征是高频写入、短时间累积巨大、按时间过期。监控项一多比如几千台设备、每台设备几十个监控项、采集间隔 30 秒一晚上就能写出几千万行。再叠加长周期保留90 天甚至更长MySQL 单机的磁盘、IO、事务并发很快就到瓶颈。更关键的一点是 Zabbix 默认的 housekeeper 清理逻辑。它会按频率执行DELETE FROM history WHERE clock xxx。数据量一大这种删除就会拖慢整个实例甚至产生大量 binlog让从库同步也跟不上。很多团队最后不得不用脚本按天删分区表但 MySQL 单机下重建分区的时候锁表、IO 抖动都是绕不开的。1.2 为什么选 TiDB 而不是分库分表再看数据库选型。如果继续待在 MySQL 生态里可以走分库分表但 Zabbix 内部 SQL 对跨库关联、聚合统计用得非常频繁改造 SQL 的工作量很大而且后续升级 Zabbix 还得同步维护分片逻辑。TiDB 对整个应用层依然表现为一个完整的 MySQL 数据库连接串、SQL 语法、事务行为都是 MySQL 兼容的应用不需要感知背后有几个 TiKV 节点。TiDB 的分布式存储把多节点的 CPU、内存、磁盘集合成一个逻辑库单点扩容难、写入并发受限这些问题被拆散到多节点上。同时它的分区表管理也完整支持可以把 Zabbix 最头疼的批量老数据清理从DELETE改成 DDL 式的分区删除效果立竿见影。1.3 兼容层能省多少事TiDB 的通信协议、客户端认证都基于 MySQLZabbix server 本身是 C 写的通过 MySQL 客户端库连库PHP 前端也一样。只要切换连接地址和端口Zabbix 不知道背后换了数据库。前提是把模式和数据正确导入避免个别不兼容的 DDL 和排序规则问题。这个兼容层是整条迁移路径里最值钱的部分也是我最终坚持采用“直接换库、不换应用”方案的原因。2. 迁移方案选型与前置准备2.1 TiDB 部署方式对比如果是测试环境最快的办法是用单机方式启动一个 TiDB 实例组件包括 TiDB Server、PD、TiKV也可以加入 TiFlash但 Zabbix 用不到列存索引先不启用。单机模式适合验证功能连通性和数据完整性但不足以证明生产性能。生产环境建议用集群方式部署至少 3 个 PD、3 个 TiKVTiDB Server 无状态可以多放几个。不同部署方式对最终迁移验证结果影响很大我最终验证的结论来自一套 3 节点 TiKV 的集群每个节点 SSD网络万兆。整体部署使用 TiUP 就能完成如果不想深入 TiDB 的内部原理按官方推荐的拓扑来即可。实践中我的体会是TiDB 扩容友好但初始拓扑还是别太省。尤其是 TiKV 节点内存和磁盘的分配要留足监控数据的压缩比和热数据分布很依赖机器配置。配置太低容易把性能问题误判成产品问题。2.2 迁移工具怎么选直接 dump 还是专用导入Zabbix 的库最重要的问题是要搬运大量历史数据。数据量在几十 GB 以内、或者本来就是做概念验证可以直接用mysqldump导出 SQL再用 MySQL 客户端导入 TiDB简单直观。但数据量上百 GB 后mysqldump 的导出和导入都是单线程逻辑效率上不划算更推荐用dumpling导出加tidb-lightning物理导入的组合。因为这个项目要同时兼顾迁移效率和数据完整性我实际采用了两套方案先用 mysqldump 做小批量联调验证确认库表结构能兼容再在正式切割时用 dumpling 导出、lightning 导入整体花费的时间能少一个数量级。工具的选择不是越高级越好而是和迁移窗口、数据量、可回滚性挂钩。2.3 账号库表初始化与连接准备在 TiDB 集群部署完成后先创建 Zabbix 使用的数据库和账号CREATE DATABASE zabbix DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin; CREATE USER zabbix% IDENTIFIED BY your_password_here; GRANT ALL PRIVILEGES ON zabbix.* TO zabbix%; FLUSH PRIVILEGES;这里账号的权限给得比较宽是为了迁移期间方便调试。上线后可以按最小权限收窄但 Zabbix 运行中会做临时表、分区变更至少得保留 DDL 权限。连接端口要注意TiDB 默认的 SQL 监听端口是 4000不是 3306。第一次用 MySQL 客户端连上去时很多人会下意识写-P3306然后报连接失败。我在这一步就被绕了一下。检查无误后可以用mysql -h tidb_ip -P 4000 -u root -p测试连通性。3. 迁移实操从 MySQL 到 TiDB 的完整流程3.1 源库导出结构与数据分开走先把 Zabbix 库表结构导出。导出时建议加上--single-transaction保证快照一致如果源 MySQL 开启了 GTID还要加--set-gtid-purgedOFF否则导入 TiDB 时会因为 GTID 语句不识别而报错。mysqldump -h 192.168.1.10 -u root -p \ --default-character-setutf8mb4 \ --single-transaction \ --routines \ --no-data \ --set-gtid-purgedOFF \ --databases zabbix zabbix_schema.sql导出数据时不建议再带建表语句这是为了后续可以只专注于大数据表的导入。Zabbix 表之间的外键约束本来就不多跳过约束能让导入速度和安全系数都有提升mysqldump -h 192.168.1.10 -u root -p \ --default-character-setutf8mb4 \ --single-transaction \ --skip-triggers \ --skip-routines \ --no-create-info \ --set-gtid-purgedOFF \ --databases zabbix zabbix_data.sql拆分导出还有一个额外好处结构文件可以人工检查尤其可以提前处理 TiDB 不支持的排序规则。数据文件再怎么大也不用逐个看。3.2 导入到 TiDB先建库、再导结构、最后灌数据按照上面拆分好的文件依次执行mysql -h 127.0.0.1 -P 4000 -u root -p \ -e CREATE DATABASE IF NOT EXISTS zabbix DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin; mysql -h 127.0.0.1 -P 4000 -u root -p \ --default-character-setutf8mb4 \ zabbix zabbix_schema.sql mysql -h 127.0.0.1 -P 4000 -u root -p \ --default-character-setutf8mb4 \ zabbix zabbix_data.sql如果数据量小这些命令一条条跑完就行。数据量大时用 MySQL 客户端导入最大的history相关表会非常慢因为客户端是单连接顺序执行而且每条 INSERT 的提交开销都在。此时可以用 dumpling 和 lightning 替换。dumpling 导出的默认文件格式就是 TiDB 生态最熟悉的 SQL/CSV 格式dumpling -h 192.168.1.10 -u root -p \ -P 3306 \ --threads 8 \ --filetype sql \ --output /data/zabbix_dump \ --database zabbix \ --where clock 2024-01-01 00:00:00后面这个--where条件是我做分批迁移时加的如果做全量一次性迁移不需要加。tidb-lightning 的配置则相对简单核心是告诉它源目录和 TiDB 连接信息[lightning] level info checkpoint true [mydumper]>ALTER TABLE history_uint PARTITION BY RANGE (TO_DAYS(clock)) ( PARTITION p20240101 VALUES LESS THAN (TO_DAYS(2024-01-01)), PARTITION p20240102 VALUES LESS THAN (TO_DAYS(2024-01-02)), PARTITION p20240103 VALUES LESS THAN (TO_DAYS(2024-01-03)) );这一步有两个细节要注意。第一在导入前完成分区能避免导入后重写整表第二TiDB 对分区数量的上限有约定不能无限增加所以按天建分区时批量加一个月或一个季度的分区更稳妥。清理数据时直接删除旧分区比 DELETE 快得多而且不会产生大量长事务ALTER TABLE history_uint DROP PARTITION p20240101;这套逻辑和 Zabbix 自己的 housekeeper 并不冲突。housekeeper 只负责删未分区的过期行而分区表清理可以在凌晨低峰执行一次 DDL 脚本。实测下来DDL 方式对集群整体 IO 的冲击几乎可以忽略。3.4 引入增量同步方案作为切割策略业务量大的系统不建议直接停库导数据。先全量导入 TiDB再用 DM 工具把 MySQL 新增的 binlog 增量同步到 TiDB等到切换窗口时应用停写、手动追平增量、再改 Zabbix 连接串这样能保证数据基本不丢。DM 配置里需要定义源 MySQL 和同步任务核心思路和主从同步很像。注意源端需要开启 binlog 且使用 row 格式Zabbix 表最好带主键否则 DM 同步过程中无法有效定位变更行。这个配置要点在写 DM 任务时一定要回头确认源 MySQL 的参数。4. Zabbix 配置改造与启动验证4.1 zabbix_server.conf 连接参数Zabbix server 的数据库连接集中在/etc/zabbix/zabbix_server.conf里迁移后改动如下DBHosttidb01 DBPort4000 DBNamezabbix DBUserzabbix DBPasswordyour_password_here如果原本配置里有DBSocket或者DBHostlocalhost一定要去掉或改成远端地址否则 Zabbix 会尝试走本地 socket连接不到 TiDB。我当时测试时还犯过一个低级错误端口改成 4000 后密码里带有特殊字符忘记转义导致连接被拒后花了不少时间排查。先确认连接串再动服务能省很多事。4.2 PHP 前端配置切换Zabbix Web 前端在/etc/zabbix/web/zabbix.conf.php数据库类型保持 MYSQL因为 TiDB 走的就是 MySQL 协议。只改服务端地址和端口?php $DB[TYPE] MYSQL; $DB[SERVER] tidb01; $DB[PORT] 4000; $DB[DATABASE] zabbix; $DB[USER] zabbix; $DB[PASSWORD] your_password_here; $DB[SCHEMA] ;前端 PHP 所在的机器要能访问 TiDB 的 4000 端口有些环境里前端服务器和后端服务有防火墙隔离记得加白名单。改完后台页面上如果有报错往往是网络通不通的问题而不是数据库连接类型的问题。4.3 启动与健康检查配置改完先不要直接启动生产服务建议做两步验证。第一步是连接性测试直接用 PHP 的 mysql 扩展或命令行客户端去连mysql -h tidb01 -P 4000 -u zabbix -p -e SELECT 1;第二步是前端页面登录确认配置、用户、监控项都能正常读取。Zabbix 前端加载时会执行一批元数据查询如果排序规则不对有些中文或特殊字符页面会直接显示乱码或报错。把前端跑通了整个底层的连通性才算闭环。如果 Zabbix server 启动后日志里持续报PostgreSQL connection之类的错误先检查是不是改错了配置文件或者旧的数据库进程占用着连接端口。这类排查大部分和 TiDB 无关属于常规的换库后遗症。5. 迁移后的避坑清单与实测体会5.1 排序规则不匹配是最常见的坑Zabbix 源库常见字符集是utf8mb4排序规则可能是utf8mb4_general_ci或utf8mb4_bin。而 MySQL 8.0 默认的utf8mb4_0900_ai_ci在部分 TiDB 版本上不支持导入建表语句时直接报Unknown collation: utf8mb4_0900_ai_ci。解决办法是导入前统一替换 schema 文件里的排序规则建议统一为utf8mb4_bin对 Zabbix 这种监控数据来说完全够用。替换命令一行搞定sed -i s/utf8mb4_0900_ai_ci/utf8mb4_bin/g zabbix_schema.sql遇到其他不支持的排序规则也可以先查目标 TiDB 版本支持列表再替换。这种问题早早在联调阶段发现比正式割接后报错要好处理得多。5.2 ZERO 日期和 SQL_MODE 差异Zabbix 部分历史数据里可能写入0000-00-00 00:00:00这种零值日期。MySQL 8 默认在会话里允许但 TiDB 的 SQL 模式如果偏严格会在导入或查询时拒绝。导入前调整会话或者全局 SQL 模式SET GLOBAL sql_mode ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION;注意一旦动了 SQL_MODE要对 Zabbix 的业务 SQL 做一轮回归测试防止某些聚合查询受ONLY_FULL_GROUP_BY影响。实际生产里我见过一个报表因为时间分组查询不满足严格模式而报错最终还是把ONLY_FULL_GROUP_BY去掉了才通过。好在 TiDB 对 SQL 模式的兼容性已经做得比较成熟Zabbix 的增删改查没有碰到更复杂的问题。如果遇到函数不支持优先看函数名是否大小写敏感TiDB 的兼容函数清单在对应版本文档里都有一个个对照排查即可。5.3 分区维护别让 housekeeper 双份干活Zabbix 如果开启了原生分区housekeeper 还是会尝试清理过期数据。分区表的手动 DROP PARTITION 能很快删除旧数据但 housekeeper 进程对同一张表的 DELETE 操作依然可能产生锁竞争。最稳妥的做法是调整 Zabbix 的自动清理周期或者直接关闭对应历史表的 housekeeper 清理完全依赖分区脚本。实际维护中我会在每周日凌晨统一删除 14 天前的历史分区。脚本思路很简单循环生成需要添加的日期分区然后对history_uint、history、trends_uint、trends依次执行 DDL。#!/bin/bash DBzabbix TIDB_HOSTtidb01 TIDB_PORT4000 for i in $(seq 1 14); do D$(date -d ${i} days %F) PNpartition_name_$(date -d ${i} days %Y%m%d) mysql -h $TIDB_HOST -P $TIDB_PORT -u zabbix -pYourPass \ -e ALTER TABLE history_uint ADD PARTITION (PARTITION $PN VALUES LESS THAN (TO_DAYS($D))); done分区名不能用数字开头所以我在前面拼了字符串前缀。这种细节在写脚本时特别容易忽略报错一次就能记住。5.4 性能观测写入和查询都变稳了迁移完成后我关注的核心指标有三个历史写入延迟、housekeeper 清理耗时、前端图表加载速度。先说写入。TiDB 集群在夜间批量写监控数据时整体的写入延迟曲线比原来 MySQL 单机平稳高峰期的排队延迟明显降低。原因是 TiKV 的多节点天然分担了热点写入不会像单机 InnoDB 那样在大量 INSERT 时互相阻塞。再说清理。原来 MySQL 上清理几千万行老数据可能要跑几十分钟而且期间 CPU 飙升、IO 打满。切换到 TiDB 后通过 DROP PARTITION 清理一个月前的数据几乎是分钟级完成对业务的影响大幅度降低。对我来说仅仅这一个改善就值回迁移成本。前端图表的查询速度在历史时间范围较大的聚合查询里也表现不错。小范围短暂查询和 MySQL 差异不大但长时间跨度、跨多节点聚合统计时TiDB 的分布式优势能发挥出来。5.5 回滚预案要提前写好迁移顺利时大家都很开心但 DBA 的习惯是先想退路。我的回滚方案是保留 MySQL 原库不删切换 TiDB 后只做只读验证等到前端、报表、告警都跑了一周确认没有异常再做原库归档。千万别在迁移当天把 MySQL 直接删掉万一 TiDB 侧碰到隐藏兼容问题想快速回滚都没机会。前后端配置改回原来的 MySQL 地址理论上几分钟就能恢复。真正花时间的反而是确认增量同步任务、权限账号、分区脚本这些外围内容我在预案里都把这部分写清楚了团队演练过一次就知道怎么切。最后的实践感受这套项目做完我最深的体会是TiDB 和 MySQL 的兼容性确实给 Zabbix 这种老牌监控系统留了一条很平滑的迁移路径。真正复杂的并不是数据库类型的变化而是历史数据怎么搬得高效、分区策略怎么重新设计、运维习惯怎么跟着调整。分区清理从 DELETE 改成 DROP PARTITION 这一步给我的印象比迁移本身还深。如果你手头的 Zabbix 也到了 MySQL 单机写入吃力、清理慢、前端报表越来越卡的程度先把历史数据量和增长趋势拉出来看看再对照 TiDB 的集群规格做一次小范围压测大概率值得一试。迁移过程中逐渐把脚本、分区维护、回滚预案沉淀下来之后这套系统再扩容只需要给 TiKV 加节点就行不必再动应用层省下来的时间会非常可观。