简介:面向Zabbix运维人员与监控系统管理员的一份数据库分区优化实操记录,聚焦Zabbix 7.0 LTS在MySQL/MariaDB环境下历史记录与趋势表的分区管理,帮助解决数据量累积后housekeeper进程繁忙、旧数据删除效率低等性能瓶颈。资源包共1个文件,为PDF格式,大小约835KB,内容围绕分区脚本的获取与使用、分区过程的创建与定期执行、前端保留天数配置及分区天数调整等环节展开,并提示备份数据库与大型库执行耗时等注意事项,适用于Zabbix 3.0之后的多个版本。已有357人学习,适合希望降低维护成本、提升海量监控数据处理能力的读者参考借鉴。
1. 管家进程告警:那条 75% busy 的红色警报,到底在删什么
凌晨两点,监控大屏上弹出一条告警:Zabbix housekeeper processes more than 75% busy。点进去看,主机没挂、触发器没炸,唯独数据库的 IO 曲线像心电图一样往上窜。这不是网络问题,也不是 agent 掉线,而是 Zabbix 自己的内务处理(housekeeper)在跟数据库较劲——它正用一条条DELETE语句,从history、trends这些表里把过期数据一行行抠出来。
数据量小的时候你根本感觉不到它,等到历史表涨到几千万行,DELETE就变成了慢查询制造机:锁等待、undo 日志膨胀、主从延迟,一个都跑不掉。更难受的是,删完之后表空间还不一定还给你,磁盘照样满。这套 Zabbix 7.0 LTS 的部署记录,就是围绕这个告警展开的:先把分区脚本跑起来,再让分区过程自动滚动,最后把前端管家关掉,让DROP PARTITION替代DELETE。如果你也正被这条告警追着跑,下面的排查顺序和命令可以直接抄。
2. 先搞清楚谁在删数据:housekeeper 与分区表的职责边界
2.1 告警背后的两条删除路径
Zabbix 存数据分两类表:history*存原始采集值,trends*存按小时聚合的最小/平均/最大值。默认情况下,这两类表的生命周期由前端「管理 → 一般 → 管家」里的天数控制,housekeeper 进程按配置周期扫描并DELETE过期行。问题在于,DELETE是逐行标记删除,行数一多,单次清理可能跑几个小时,进程占用率自然飙到 75% 以上。
分区走的是另一条路:把表按时间切成一个个独立分区,过期时直接ALTER TABLE ... DROP PARTITION。DROP是元数据操作,不扫行、不写 undo,秒级完成。两者不是叠加关系,而是替代关系——上了分区,就必须把前端管家关掉,否则 housekeeper 还在那儿一行行删,分区白做。
| 对比项 | housekeeper(DELETE) | 分区(DROP PARTITION) |
|---|---|---|
| 删除粒度 | 逐行 | 整个分区 |
| 对 IO 影响 | 高,随数据量线性上升 | 低,元数据操作 |
| 表空间回收 | 需 OPTIMIZE 才彻底 | DROP 即释放 |
| 适用数据量 | 小表、低频 | 历史/趋势大表 |
2.2 分区脚本里到底有几个过程
这份zbx_db_partitiong.sql脚本不是一条 SQL,而是一组存储过程,各司其职:
partition_create:检查目标分区是否已存在,不存在则ADD PARTITION;partition_drop:游标遍历information_schema.partitions,把名字日期早于保留期的分区拼成一条DROP PARTITION批量执行;partition_verify:首次运行时判断表是否已分区,没分区就先做初始分区;partition_maintenance:单个表的调度入口,负责算时间戳、建未来分区、删过期分区;partition_maintenance_all:对history、history_log、history_str、history_text、history_uint、trends、trends_uint七张表依次调用partition_maintenance。
理解这层结构很关键:后面改保留天数,不是改脚本重跑,而是新建一个partition_maintenance_all_xxandyy过程替换调度目标。很多人第一次翻车就翻在这里,以为改脚本文件就行,结果事件调度器还在调旧过程。
2.3 导入脚本前必须确认的三件事
第一,备份。新装环境可以跳过,但只要库里有生产数据,先mysqldump一份,分区过程在大表上可能跑数小时,中途出错回滚代价很高。第二,确认clock字段类型,Zabbix 的历史表用clock int存 Unix 时间戳,分区键就是它,如果表结构被改过要先核对。第三,确认数据库账号有CREATE ROUTINE、ALTER、EVENT权限,否则脚本导入到一半报权限错误。
导入命令本身很简单,但参数要对:
# 解压脚本 tar -zxvf zbx_db_partitiong.tar.gz # 用 Zabbix 库账号导入,注意 -p 和密码之间不要有空格 mysql -u 'zabbix' -p'zabbixDBpass' zabbix < zbx_db_partitiong.sql-u后是数据库用户名,-p后紧跟密码(有空格会被当成库名),最后一个zabbix是库名,<把脚本喂给客户端。导入成功后不会有花哨输出,用下面这条确认过程已建好:
SELECT ROUTINE_NAME FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = 'zabbix' AND ROUTINE_NAME LIKE 'partition%';正常应看到partition_create、partition_drop、partition_maintenance、partition_maintenance_all、partition_verify五个。少一个,说明脚本没导全,别急着往下走。
3. 自动调度翻车现场:事件调度器没开、分区没建、空图报 1526
3.1 报错 [Z3005] 表没有值对应的分区
这是分区上线后最常见的一条:Zabbix 日志里刷[Z3005] query failed: [1526] Table has no partition for value ...,前端对应监控项变成空图。根因是分区过程没跑,或者跑了但没覆盖到当前时间点——新数据写入时找不到落点分区,InnoDB 直接拒绝插入。
先手动跑一次维护过程,看它到底建没建分区:
mysql -u 'zabbix' -p'zabbixDBpass' zabbix -e "CALL partition_maintenance_all('zabbix');"输出会是一串partition_create(zabbix,history,p202401150000,...)之类的消息,说明在补建分区。跑完再查表结构:
SHOW CREATE TABLE history\G输出里应出现PARTITION BY RANGE (clock)以及若干PARTITION pYYYYMMDDHH00 VALUES LESS THAN (...)。如果还是原始表结构,说明partition_verify那步没生效,回头检查脚本是否完整导入。
3.2 事件调度器默认是关的
手动能跑通,不代表自动能跑。MySQL/MariaDB 的event_scheduler默认OFF,不打开它,你建的事件永远不会触发。先查:
SHOW VARIABLES LIKE 'event_scheduler';值是OFF就去配置文件里加。MariaDB 一般在/etc/mysql/mariadb.conf.d/,MySQL 在/etc/my.cnf.d/,找不到就用grep搜:
sudo grep --include=*.cnf -irl / -e "\[mysqld\]"在[mysqld]段落下加一行:
[mysqld] event_scheduler = ON改完必须重启数据库才生效:
sudo systemctl restart mysql重启后再查一次变量,确认变成ON。这一步没做就建事件,是最典型的「以为配好了其实没跑」——事件在information_schema.events里躺着,LAST_EXECUTED永远是NULL。
3.3 建事件与验证执行
调度器打开后,创建每 12 小时执行一次的事件:
CREATE EVENT zbx_partitioning ON SCHEDULE EVERY 12 HOUR DO CALL partition_maintenance_all('zabbix');EVERY 12 HOUR是执行频率,分区维护本身是幂等的,跑勤一点没坏处;DO CALL后面跟过程名和库名参数。建完等一个周期,或者手动触发一次后查执行记录:
SELECT EVENT_NAME, LAST_EXECUTED, STATUS FROM information_schema.events\GLAST_EXECUTED有值、STATUS为ENABLED才算真正跑起来。如果LAST_EXECUTED为空,优先怀疑调度器没开或事件被禁用。
3.4 没有事件调度器权限时的 crontab 兜底
有些托管数据库不给EVENT权限,那就用系统 crontab 调。sudo crontab -e加一行,每天凌晨 3:30 执行并把日志落到文件:
30 03 * * * /usr/bin/mysql -u 'zabbix' -p'zabbixDBpass' zabbix -e "CALL partition_maintenance_all('zabbix');" > /tmp/CronDBpartitiong.log 2>&1五个时间字段依次是分、时、日、月、周,> /tmp/... 2>&1把标准输出和错误都重定向进日志,出问题先看这个文件。注意mysql要用绝对路径,cron 环境的PATH和你登录 shell 不一样,写mysql很可能报 command not found。
3.5 前端管家必须关掉
分区跑起来后,回到前端「管理 → 一般 → 管家」,把「历史记录和趋势」下的「开启内部管家」勾去掉,同时勾上「覆盖监控项趋势期间」,把历史和趋势天数填成与分区脚本一致的值(默认历史 7 天、趋势 365 天)。这一步不做,housekeeper 会和分区抢着删数据,告警照旧。天数两边必须对齐,前端填 7 天、分区留 30 天,会出现前端查不到但磁盘还占着的情况。
4. 保留天数改不动:新建过程替换,而不是重跑脚本
4.1 为什么改脚本文件没用
脚本导入时,partition_maintenance_all里的天数(7 和 365)已经写死在过程体里。你改.sql文件再导入,只会因为过程已存在而报错或覆盖失败;就算覆盖成功,事件调度器调的还是同名过程,行为不一定按你预期变。正确做法是新建一个带新天数的过程,再把调度目标切过去,旧过程留着不删,方便回退。
4.2 新建 30 天历史、400 天趋势的过程
DELIMITER $$ CREATE PROCEDURE partition_maintenance_all_30and400(SCHEMA_NAME VARCHAR(32)) BEGIN CALL partition_maintenance(SCHEMA_NAME, 'history', 30, 24, 3); CALL partition_maintenance(SCHEMA_NAME, 'history_log', 30, 24, 3); CALL partition_maintenance(SCHEMA_NAME, 'history_str', 30, 24, 3); CALL partition_maintenance(SCHEMA_NAME, 'history_text', 30, 24, 3); CALL partition_maintenance(SCHEMA_NAME, 'history_uint', 30, 24, 3); CALL partition_maintenance(SCHEMA_NAME, 'trends', 400, 24, 3); CALL partition_maintenance(SCHEMA_NAME, 'trends_uint', 400, 24, 3); END$$ DELIMITER ;partition_maintenance的四个参数依次是:库名、表名、保留天数、分区小时跨度、预建分区数。这里24表示按天分区(每小时分区就填 1),3表示提前建好未来 3 个分区,防止维护任务延迟时新数据无处可写。七张表一个不能漏,漏掉history_uint这种高频表,照样会报 1526。
4.3 把调度目标切到新过程
用事件调度器的,直接ALTER EVENT:
ALTER EVENT zbx_partitioning ON SCHEDULE EVERY 12 HOUR DO CALL partition_maintenance_all_30and400('zabbix');用 crontab 的,编辑时把旧行注释掉、新行加上,保留旧行方便回滚:
# 旧过程保留,需要时取消注释即可 # 30 03 * * * /usr/bin/mysql -u 'zabbix' -p'zabbixDBpass' zabbix -e "CALL partition_maintenance_all('zabbix');" > /tmp/CronDBpartitiong.log 2>&1 30 03 * * * /usr/bin/mysql -u 'zabbix' -p'zabbixDBpass' zabbix -e "CALL partition_maintenance_all_30and400('zabbix');" > /tmp/CronDBpartitiong.log 2>&1切换后别忘了同步改前端管家里的天数,两边不一致等于白改。改完手动跑一次新过程,看输出里partition_drop是否按新保留期删分区,再观察一两天磁盘曲线是否按预期下降。
5. 避坑清单:分区上线前后最容易踩的五个坑
坑一:先关管家后建分区,中间出现数据空洞。现象是前端历史数据突然断档。原因是管家关了、分区还没建,过期数据没人删也没人管,看似无害,但一旦分区过程首次运行按保留期一次性删掉大量旧分区,查询会突然少一大段。解决:先建分区、手动跑一次维护、确认分区结构正常,再关管家,顺序不能反。
坑二:分区过程在大表上跑了几小时,以为卡死。现象是CALL partition_maintenance_all长时间无返回。原因是首次分区要对存量数据做ALTER TABLE ... PARTITION BY,本质是重建表,几千万行跑几小时正常。解决:放到维护窗口执行,用SHOW PROCESSLIST看状态是不是在copy to tmp table,别中途 kill,否则表可能处于中间状态。
坑三:只分区了 history,忘了 trends_uint。现象是历史图正常,趋势图报 1526 或空图。原因是partition_maintenance_all里七张表是并列的,手工建过程时容易漏。解决:建完过程后查一遍information_schema.partitions,确认七张表都有PARTITION BY RANGE。
坑四:事件建了但LAST_EXECUTED一直为空。现象是分区不滚动,几天后新数据写不进去。原因多半是event_scheduler没开,或者事件STATUS是DISABLED。解决:查变量、查事件状态,必要时ALTER EVENT ... ENABLE,再手动CALL一次验证过程本身没问题。
坑五:前端天数和分区天数不一致。现象是前端显示「无数据」但磁盘没降,或者磁盘降了但前端还能查到本该过期的数据。原因是两套保留策略各管各的。解决:把前端管家天数和过程里的保留天数对齐,改一处就同步改另一处,写进变更记录。
6. 快速定位:三条查询把分区问题锁死
真出问题时,别从头翻日志,按下面顺序查,基本三分钟内能定位到层。
第一步,确认表到底分没分区:
SELECT TABLE_NAME, PARTITION_NAME, PARTITION_DESCRIPTION FROM information_schema.PARTITIONS WHERE TABLE_SCHEMA = 'zabbix' AND TABLE_NAME IN ('history','trends') ORDER BY TABLE_NAME, PARTITION_ORDINAL_POSITION DESC LIMIT 10;有PARTITION_NAME说明分区存在,PARTITION_DESCRIPTION是分区上界的时间戳,拿它和当前UNIX_TIMESTAMP()比,如果最大值比现在小,说明未来分区没预建,马上会报 1526。
第二步,确认调度在不在跑:
SELECT EVENT_NAME, STATUS, LAST_EXECUTED, INTERVAL_VALUE, INTERVAL_FIELD FROM information_schema.events;STATUS不是ENABLED、LAST_EXECUTED超过一个调度周期没更新,就是调度层的问题,回去查event_scheduler变量或 crontab 日志。
第三步,确认管家关没关:
SELECT * FROM config WHERE `key` LIKE '%housekeeping%';hk_history_global和hk_trends_global应为 1(表示使用覆盖值),配合前端「覆盖监控项趋势期间」生效;如果还是 0,说明管家仍在按监控项级别删数据,和分区打架。
| 现象 | 优先查 | 大概率根因 |
|---|---|---|
| 日志刷 1526 | 分区上界时间戳 | 未来分区未预建 |
| 分区不滚动 | information_schema.events | 调度器关闭或事件禁用 |
| 磁盘不降 | 前端管家配置 | 管家未关,DELETE 仍在跑 |
| 前端无数据 | 前端天数 vs 过程天数 | 两边保留期不一致 |
从那以后我每次上分区,都强制走一遍「导入脚本 → 手动 CALL 一次 → 查分区结构 → 开调度 → 等一个周期看 LAST_EXECUTED → 关管家 → 对齐天数」这条链路,任何一步没验证就不往下走。这套顺序看着笨,但能挡住上面五个坑里的四个。希望帮到你。
本文还有配套的精品资源,点击获取