news 2026/10/31 7:25:08

Zabbix 7.0 LTS 数据库分区实战:告别 housekeeper 告警,实现历史数据自动清理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Zabbix 7.0 LTS 数据库分区实战:告别 housekeeper 告警,实现历史数据自动清理

简介:面向运维工程师与Zabbix使用者的数据库调优实操资料,聚焦Zabbix 7.0 LTS在MySQL/MariaDB环境下历史记录与趋势表的分区方案,用于缓解数据量增长带来的性能压力与housekeeper高负载告警。资源为1个PDF文件,压缩包约835KB,内容围绕分区脚本的获取与执行、分区过程的定期自动运行、前端保留天数配置及分区天数调整等环节展开,并提示备份数据库与大型库执行耗时等注意事项,适用于3.0以后各版本。已有357人学习。读者可据此掌握分区脚本的落地用法,理解DROP分区相较DELETE的清理优势,获得可复用的维护思路与配置参考,降低长期运维成本。

1. Zabbix 7.0 LTS 数据库分区:housekeeper 告警背后的真问题

监控跑了大半年,某天早上打开 Zabbix 前端,最新告警不是某台主机 CPU 飙高,而是Zabbix housekeeper processes more than 75% busy。点进历史数据一看,history_uint表已经膨胀到几十 GB,housekeeper 每次清理都在跟 DELETE 语句较劲,删一批锁一片,监控数据写入跟着抖动。这不是个例,是 Zabbix 用久了几乎都会撞上的坎。

Zabbix 7.0 LTS 把时序数据存在 MySQL/MariaDB 的history*和trends*表里,默认靠 housekeeper 进程用 DELETE 删过期数据。数据量一上来,DELETE 就变成性能黑洞。数据库分区换了个思路:按小时或按天把表切成独立分区,过期时直接DROP PARTITION,元数据操作,秒级完成,不产生大量 undo log,也不锁全表。这份操作记录就是围绕一套 SQL 分区脚本,把「建过程、定时跑、前端对齐、改保留天数」串成可复现的流程。适合正在被 housekeeper 告警折磨、又不想上 TimescaleDB 或 ClickHouse 的 Zabbix 运维,也适合刚接手一套存量 Zabbix 7.0 环境、需要把数据生命周期管起来的工程师。

2. 分区脚本拆解:四个存储过程各管什么

2.1 为什么 RANGE(clock) 是 Zabbix 场景的合理选择

Zabbix 的history、history_uint、history_str、history_text、history_log、trends、trends_uint这几张表有个共同特征:时间字段clock单调递增,查询几乎都带时间范围,过期数据按时间整段淘汰。这正好命中 RANGE 分区的适用面——按clock的 Unix 时间戳切边界,每个分区覆盖一个固定时间窗,删除时整段摘掉。

对比另外两种常见方案:用 housekeeper 的 DELETE 是逐行删,大表上会产生大量 binlog 和间隙锁;用 MySQL 原生 Event 直接DELETE FROM history WHERE clock < ?本质还是逐行删,只是换了个调度器。分区把「删数据」降级成「删分区」,DDL 操作走元数据,代价跟分区里有多少行无关。脚本里PARTITION BY RANGE (clock)的边界值就是 Unix 时间戳,p201910150000 VALUES LESS THAN (1571180400)表示这个分区装的是clock < 1571180400的数据,命名里的201910150000是给人看的时间标签。

一个容易忽略的点:分区表的主键或唯一索引必须包含分区键。Zabbix 原表结构里history只有KEY history_1 (itemid,clock),没有主键,所以脚本能直接ALTER TABLE ... PARTITION BY RANGE。如果你之前手动加过自增主键,这一步会直接报错,得先把主键调整成包含clock的复合键。

2.2 partition_create / partition_drop:建与删的原子动作

脚本的核心是四个存储过程,先看建和删这两个底层动作。partition_create负责在表上追加一个新分区,入参是库名、表名、分区名和边界时间戳;partition_drop负责把早于指定日期的分区整段删掉。两者都先查information_schema.partitions做存在性判断,避免重复建或误删。

-- partition_create 的关键逻辑(摘自脚本,做了注释补全) CREATE PROCEDURE `partition_create`( SCHEMANAME VARCHAR(64), -- 库名,如 zabbix TABLENAME VARCHAR(64), -- 表名,如 history PARTITIONNAME VARCHAR(64),-- 分区名,如 p202501150000 CLOCK INT -- 边界 Unix 时间戳 ) BEGIN DECLARE RETROWS INT; -- 查是否已存在边界 >= CLOCK 的分区,存在就不重复建 SELECT COUNT(1) INTO RETROWS FROM information_schema.partitions WHERE table_schema = SCHEMANAME AND table_name = TABLENAME AND partition_description >= CLOCK; IF RETROWS = 0 THEN SELECT CONCAT("partition_create(", SCHEMANAME, ",", TABLENAME, ",", PARTITIONNAME, ",", CLOCK, ")") AS msg; -- 动态拼 ALTER TABLE ... ADD PARTITION SET @sql = CONCAT('ALTER TABLE ', SCHEMANAME, '.', TABLENAME, ' ADD PARTITION (PARTITION ', PARTITIONNAME, ' VALUES LESS THAN (', CLOCK, '));'); PREPARE STMT FROM @sql; EXECUTE STMT; DEALLOCATE PREPARE STMT; END IF; END$$

partition_drop用游标遍历information_schema.partitions,把分区名去掉前缀p后转成数字,跟DELETE_BELOW_PARTITION_DATE(格式yyyyMMdd0000)比较,小于阈值的全部拼进一条ALTER TABLE ... DROP PARTITION p1,p2,...批量执行。这里有个设计细节:它把多个待删分区合并成一条 DDL,而不是逐个 DROP,减少元数据锁的获取次数。参数上DELETE_BELOW_PARTITION_DATE是BIGINT,但实际传的是201910150000这种「日期+时分」的数字串,比较时按数值大小走,所以分区名必须严格保持p%Y%m%d%H00格式,否则SUBSTRING(partition_name FROM 2)转数字会得到意外结果。

2.3 partition_maintenance:调度入口与保留天数

partition_maintenance是真正被定时调用的过程,入参KEEP_DATA_DAYS、HOURLY_INTERVAL、CREATE_NEXT_INTERVALS决定了保留多久、每个分区多宽、提前建几个。脚本默认HOURLY_INTERVAL=24(一天一个分区)、CREATE_NEXT_INTERVALS=3(提前建未来 3 天),KEEP_DATA_DAYS在partition_maintenance_all里按表分别传 7 和 365。

-- partition_maintenance 的建分区循环(节选) SET CUR_TIME = UNIX_TIMESTAMP(DATE_FORMAT(NOW(), '%Y-%m-%d 00:00:00')); SET @__interval = 1; create_loop: LOOP IF @__interval > CREATE_NEXT_INTERVALS THEN LEAVE create_loop; END IF; -- 每个分区的上界 = 今天零点 + 间隔小时数 * 序号 SET LESS_THAN_TIMESTAMP = CUR_TIME + (HOURLY_INTERVAL * @__interval * 3600); SET PARTITION_NAME = FROM_UNIXTIME(CUR_TIME + HOURLY_INTERVAL * (@__interval - 1) * 3600, 'p%Y%m%d%H00'); IF (PARTITION_NAME != OLD_PARTITION_NAME) THEN CALL partition_create(SCHEMA_NAME, TABLE_NAME, PARTITION_NAME, LESS_THAN_TIMESTAMP); END IF; SET @__interval = @__interval + 1; SET OLD_PARTITION_NAME = PARTITION_NAME; END LOOP; -- 计算删除阈值:当前时间往前推 KEEP_DATA_DAYS 天 SET OLDER_THAN_PARTITION_DATE = DATE_FORMAT(DATE_SUB(NOW(), INTERVAL KEEP_DATA_DAYS DAY), '%Y%m%d0000'); CALL partition_drop(SCHEMA_NAME, TABLE_NAME, OLDER_THAN_PARTITION_DATE);

partition_verify在维护开始时先跑一遍,检查目标表是否已经有分区;如果partition_name IS NULL的记录数为 1,说明还是普通表,需要先做一次初始分区。这一步在存量库上会触发一次全表重建,大表上耗时可能到小时级,这也是原文提醒「大型数据库上此过程可能持续数小时」的原因。partition_maintenance_all只是把七张表挨个调一遍partition_maintenance,把保留天数集中在一处,改策略时只动这一个过程。

提示:HOURLY_INTERVAL改成 1 就是按小时分区,分区数量会涨 24 倍,information_schema查询和 DDL 开销都跟着涨。除非单表日增量极大,否则保持 24 更稳。

3. 从脚本到定时任务:把分区跑起来的完整链路

3.1 导入脚本与首次分区

拿到zbx_db_partitiong.sql后,先确认数据库账号有CREATE ROUTINE、ALTER、DROP权限。生产环境先做逻辑备份,mysqldump --single-transaction --routines把库和存储过程一起导出,别只备数据不备过程。

# 解压脚本包 tar -zxvf zbx_db_partitiong.tar.gz # 导入脚本,创建四个存储过程 # -u/-p 后跟 Zabbix 数据库账号,最后一个参数是库名 mysql -u zabbix -p'你的密码' zabbix < zbx_db_partitiong.sql # 确认过程已创建 mysql -u zabbix -p'你的密码' zabbix -e "SHOW PROCEDURE STATUS WHERE Db='zabbix';"

导入本身很快,因为只是建过程,不碰数据。真正的重活是第一次调用partition_maintenance_all,它会对七张表做初始分区。新装的 Zabbix 表里没多少数据,几秒到几分钟;存量库上history_uint动辄几十 GB,ALTER TABLE ... PARTITION BY要重建整表,期间会持有元数据锁,Zabbix server 的写入会阻塞。我的做法是挑维护窗口,先停 Zabbix server,跑完再起。

# 停采集,避免分区期间写入冲突 systemctl stop zabbix-server # 首次执行全量分区,输出会打印每个分区的创建信息 mysql -u zabbix -p'你的密码' zabbix -e "CALL partition_maintenance_all('zabbix');" # 验证 history 表已带分区定义 mysql -u zabbix -p'你的密码' zabbix -e "SHOW CREATE TABLE history\G" | grep -A5 "PARTITION BY"

SHOW CREATE TABLE输出里出现PARTITION BY RANGE (clock)和若干PARTITION p...行,说明初始分区成功。如果只看到表结构没有分区段,多半是过程执行中途报错,去 MySQL error log 里找ALTER TABLE相关的错误,常见的是索引不含分区键或磁盘空间不足。

3.2 Event Scheduler 与 Crontab 二选一

分区过程建好后不会自己跑,得挂调度。原文给了两条路:MySQL Event Scheduler 或系统 Crontab。Event Scheduler 的好处是调度逻辑留在数据库侧,不依赖服务器 cron 服务;坏处是它跟 MySQL 生命周期绑定,主从切换或实例迁移时事件不会自动跟过去,而且默认是关闭的。

# /etc/mysql/mariadb.conf.d/ 或 /etc/my.cnf.d/ 下的配置文件 [mysqld] event_scheduler = ON

改完重启数据库,用SHOW VARIABLES LIKE 'event_scheduler';确认值为ON。然后创建每 12 小时执行一次的事件:

CREATE EVENT zbx_partitioning ON SCHEDULE EVERY 12 HOUR DO CALL partition_maintenance_all('zabbix');

Crontab 方案更适合已经有配置管理体系的团队,任务定义在文件里可版本化,迁移时跟着走。每天凌晨 3:30 跑一次,日志落到/tmp方便排查:

# sudo crontab -e 追加 30 03 * * * /usr/bin/mysql -u zabbix -p'你的密码' zabbix -e "CALL partition_maintenance_all('zabbix');" >> /var/log/zbx_partition.log 2>&1

两种方式别同时开。我见过有人 Event 和 cron 都配了,结果同一时段两个会话同时ALTER TABLE ADD PARTITION,后到的那个报「分区已存在」还算轻的,严重时元数据锁互相等待,Zabbix 写入直接卡死。选一个,另一个明确不配。

3.3 前端 housekeeper 与分区策略对齐

数据库侧分区只管「物理上删哪些分区」,Zabbix 前端还管着「逻辑上认为数据该保留多久」。两边不一致会出现诡异现象:前端显示历史保留 90 天,但分区脚本 7 天就把数据删了,用户查一周前的图全是断点。

进「管理 → 一般 → 管家」,取消勾选「开启内部管家」下的历史记录和趋势开关,勾选「覆盖监控项趋势期间」,把历史和趋势天数填成跟脚本里KEEP_DATA_DAYS完全一致的值。脚本默认历史 7 天、趋势 365 天,前端就填 7 和 365。这一步的本质是把数据淘汰权从 housekeeper 手里收走,交给分区过程,前端只负责展示层的保留声明。

注意:关掉 housekeeper 后,history之外的一些表(比如alerts、events)仍由 housekeeper 按各自周期清理,别把整个 housekeeper 进程停掉,只关历史/趋势那两项。

4. 避坑排查:分区上线后最容易翻车的五件事

4.1 空图加 Z3005 报错:分区边界没跟上

现象:Zabbix 前端最新数据图空白,server 日志刷[Z3005] query failed: [1526] Table has no partition for value ...。

原因:partition_maintenance没按时跑,或者CREATE_NEXT_INTERVALS太小,未来分区没提前建出来,新写入的clock超出了最后一个分区的上界,MySQL 找不到落点直接拒绝插入。

解决:手动补跑一次CALL partition_maintenance_all('zabbix');把未来分区建出来,然后检查调度是否生效——Event 看information_schema.events的LAST_EXECUTED,cron 看日志文件时间戳。把CREATE_NEXT_INTERVALS从 3 调到 7,给调度失败留出缓冲天数。

4.2 首次分区卡住数小时:大表 ALTER 的锁

现象:在存量库上执行partition_maintenance_all,命令挂住几十分钟没返回,Zabbix 写入超时。

原因:partition_verify检测到表还没分区,触发ALTER TABLE ... PARTITION BY RANGE,这是重建整表的 DDL,数据量越大越久,期间持有元数据锁。

解决:维护窗口内先停 zabbix-server 再跑;单表超过 50 GB 时考虑用pt-online-schema-change或 gh-ost 做在线变更,或者先归档旧数据再分区。别在业务高峰硬跑。

4.3 分区建了但旧数据没删:阈值日期格式错

现象:分区正常运行,新分区天天建,但几个月前的旧分区一直不消失,磁盘只涨不降。

原因:partition_drop比较的是分区名去掉p后的数字和OLDER_THAN_PARTITION_DATE。如果手工改过分区命名(比如改成p_2025_01_15),SUBSTRING(... FROM 2)转数字得到 0 或异常值,永远不小于阈值,删除逻辑静默跳过。

解决:分区名严格保持脚本生成的p%Y%m%d%H00格式,别手工重命名。排查时直接查SELECT partition_name, partition_description FROM information_schema.partitions WHERE table_name='history';看命名和边界是否符合预期。

4.4 改了保留天数但没生效:过程没换、调度没改

现象:把历史保留从 7 天改成 30 天,重新导入了脚本,但数据还是 7 天就没了。

原因:重新导入脚本只重建了partition_maintenance_all,但 Event 或 cron 里调用的可能还是旧过程名(比如之前自建的partition_maintenance_all_30and400),或者 Event 的DO CALL子句没更新。

解决:改保留天数时,要么直接改partition_maintenance_all里的参数再DROP PROCEDURE重建,要么新建过程后用ALTER EVENT ... DO CALL 新过程名('zabbix');切换。cron 方案就改 crontab 里的过程名。改完手动跑一次确认输出里的partition_drop阈值日期正确。

4.5 主从环境事件不同步:Event 只在主库跑

现象:主库分区正常,从库SHOW CREATE TABLE看不到分区,或者从库延迟突然拉大。

原因:MySQL Event 默认在写入主库执行,DDL 通过 binlog 复制到从库,但如果从库被提升为主库,原主库上的 Event 不会自动在新主库上存在;另外大批量DROP PARTITION在从库回放时可能造成瞬时延迟。

解决:Event 方案要在切换后手动在新主库重建事件;或者干脆用 cron + 配置管理,让调度跟着主机角色走。从库延迟方面,把partition_maintenance_all的调用时间错开业务高峰,单次 DROP 的分区数别太多。

5. 保留天数调优与验证:把分区策略变成可观测的日常

分区跑起来只是起点,保留天数设多少、怎么验证它真的在按预期工作,才是长期运维的关键。我一般按「磁盘增速 ÷ 单分区大小」反推:先看history_uint一周增长多少 GB,除以 7 得到日增,再用磁盘余量除以日增,得出理论上限天数,然后在这个上限的 70% 左右定KEEP_DATA_DAYS,留出突发流量和备份的缓冲。趋势表增长慢,365 天通常够用,除非有跨年同比分析需求。

改保留天数不建议直接改原过程再重建,因为DROP PROCEDURE到重建之间有个空窗,如果正好赶上调度触发会报过程不存在。更稳的做法是新建一个带参数后缀的过程,验证通过后再切调度:

-- 新建 30 天历史 / 400 天趋势的过程,不动原过程 DELIMITER $$ CREATE PROCEDURE partition_maintenance_all_30and400(SCHEMA_NAME VARCHAR(32)) BEGIN CALL partition_maintenance(SCHEMA_NAME, 'history', 30, 24, 7); CALL partition_maintenance(SCHEMA_NAME, 'history_log', 30, 24, 7); CALL partition_maintenance(SCHEMA_NAME, 'history_str', 30, 24, 7); CALL partition_maintenance(SCHEMA_NAME, 'history_text', 30, 24, 7); CALL partition_maintenance(SCHEMA_NAME, 'history_uint', 30, 24, 7); CALL partition_maintenance(SCHEMA_NAME, 'trends', 400, 24, 7); CALL partition_maintenance(SCHEMA_NAME, 'trends_uint', 400, 24, 7); END$$ DELIMITER ; -- 手动跑一次看输出,确认建/删的分区符合预期 CALL partition_maintenance_all_30and400('zabbix'); -- 验证通过后切换 Event 指向新过程 ALTER EVENT zbx_partitioning ON SCHEDULE EVERY 12 HOUR DO CALL partition_maintenance_all_30and400('zabbix');

验证不能只看「没报错」。我习惯跑完立刻查三样东西:分区数量是否稳定在「保留天数 + 预建天数」附近、最老分区的边界日期是否等于今天减保留天数、最新分区的上界是否覆盖到未来几天。下面这条查询一次把三样都带出来:

SELECT table_name, COUNT(*) AS partition_count, MIN(FROM_UNIXTIME(partition_description)) AS oldest_boundary, MAX(FROM_UNIXTIME(partition_description)) AS newest_boundary FROM information_schema.partitions WHERE table_schema = 'zabbix' AND table_name IN ('history','history_uint','trends','trends_uint') AND partition_name IS NOT NULL GROUP BY table_name;

partition_count对history应该接近 30+7,oldest_boundary约等于当前日期减 30 天,newest_boundary在当前日期之后 7 天左右。三个数任何一个偏离,就回去查调度日志和过程参数。另外把SHOW GLOBAL STATUS LIKE 'Com_alter_table';的增量和information_schema.events的LAST_EXECUTED一起看,能确认调度真的在触发而不是「看起来配了」。

血泪经验是:分区上线第一周每天手动核对一次上面这条查询,确认删除和创建都按节奏走,之后再交给监控。我吃过一次亏——cron 里密码带特殊字符没转义,任务静默失败了两周,直到磁盘告警才发现分区早就停止滚动。从那以后我每次配完分区调度,都会在 Zabbix 里加一条对partition_count的触发器,数量低于阈值就告警,比事后翻日志靠谱得多。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/31 7:23:46

Oracle OGG跨平台同步部署:Windows到Linux实战与排错

简介&#xff1a;面向 Oracle DBA 与数据同步实施人员的一份 OGG 部署实操文档&#xff0c;聚焦 Oracle GoldenGate 12.2 在 Windows 源库与 Linux 目标库之间的异构同步落地&#xff0c;从环境准备到进程配置形成完整链路。内容围绕源库开启归档、强制日志与附加日志&#xff…

作者头像 李华
网站建设 2026/10/31 7:22:52

老机器Win7 32位安装SQL Server 2012图解教程与避坑指南

简介&#xff1a;这份PDF图解教程面向初次接触微软数据库的开发者与运维人员&#xff0c;围绕SQL Server 2012在Windows 7 SP1 32位环境下的完整安装流程展开&#xff0c;帮助读者解决从下载安装包、系统配置检查到实例配置各环节的实际问题。资源包内仅含1个PDF文件&#xff0…

作者头像 李华
网站建设 2026/10/31 7:22:45

Beancount:技术人员的双重记账系统实践指南

1. Beancount项目概述Beancount是一款基于纯文本的双重记账系统&#xff0c;专为技术人员和财务爱好者设计。作为一个开源命令行工具&#xff0c;它通过简单的文本文件记录财务交易&#xff0c;并生成详细的财务报告。与传统的电子表格或商业会计软件不同&#xff0c;Beancount…

作者头像 李华
网站建设 2026/10/31 7:22:30

C++标准文档深度解读:从翻译阶段到重载决议的编译原理

简介&#xff1a;ISO/IEC 14882:2020(E) 是 C 语言的第六版国际标准正式文本&#xff0c;面向编译器与工具链开发者、语言研究者、高校教师及希望深入理解 C 语义边界的中高级程序员&#xff0c;为跨平台实现的一致性与代码可移植性提供权威依据。资源包内仅含 1 个 PDF 文件&a…

作者头像 李华
网站建设 2026/10/31 7:22:29

Consistency Model:突破扩散模型速度瓶颈的新一代生成架构

1. Consistency Model&#xff08;CM&#xff09;概述Consistency Model&#xff08;一致性模型&#xff09;是2023年由Yang Song等人提出的一种新型生成模型架构。作为扩散模型&#xff08;Diffusion Models&#xff09;的进化版本&#xff0c;它解决了传统扩散模型最致命的缺…

作者头像 李华
网站建设 2026/10/31 7:21:51

C#安全开发:从合规检查到安全基因的转变

1. 从合规检查到安全基因的范式转变在传统C#开发实践中&#xff0c;安全合规往往被视为项目交付前的"最后一道工序"——就像给已经完工的建筑刷上一层防火涂料。我曾见过太多团队在项目验收前仓促引入安全扫描工具&#xff0c;把发现的漏洞当作检查清单上的待办事项机…

作者头像 李华