简介:面向Zabbix运维与监控系统管理员的数据库优化操作记录,针对Zabbix 7.0 LTS及3.0后各版本在MySQL/MariaDB场景下历史表、趋势表数据膨胀导致的性能下降与housekeeper繁忙告警,提供基于zbx_db_partitiong.sql的分区实施方案。内容涵盖备份提醒、SQL脚本下载与执行、分区过程自动调度、前端保留天数配置及自定义分区周期等关键环节,并附操作注意事项,适合有一定Zabbix基础、需要落地数据库分区策略的读者。资料为单个PDF文档,大小835KB,已吸引354人学习查看。通过阅读可系统掌握用分区替代DELETE清理旧数据、提升维护效率的具体步骤与排错思路,避免大型库执行分区时的常见风险。
1. Zabbix 7.0 LTS 数据库分区:先把历史表和趋势表的删除压力拆掉
Zabbix 7.0 LTS 部署完成后,真正让人睡不安稳的往往不是采集器或前端,而是数据库。监控规模一上来,history和trends系列表的数据量会以肉眼可见的速度膨胀,默认的 housekeeper 通过DELETE清理旧数据,每次触发都把 InnoDB 的 undo log 和二级索引搅得天翻地覆,直接表现就是告警“Zabbix housekeeper processes more than 75% busy”。分区不是玄学,它把“删行”变成“删表”,让旧数据的清理从 SQL 层的逐行操作变成文件系统层面的目录摘除。这份资源核心就是一套zbx_db_partitiong.sql脚本和配套操作记录,适用 Zabbix 3.0 之后的所有版本,包括 7.0 LTS,直接能落地。
2. 分区脚本选型与部署:拿到脚本先改三个参数再入库
2.1 为什么选 RANGE COLUMNS 而不是 HASH 分区
监控数据的查询模式非常固定:按itemid加时间范围取数,写入天然是顺序追加,几乎没有跨分区更新的需求。看到脚本里用的是PARTITION BY RANGE (clock),这点选型是对的。按时间范围做 RANGE 分区,每个分区对应一个时间窗口,查询优化器能直接做分区裁剪,只扫必要的那几个分区,而不是全表扫描。HASH 分区虽然能把数据打散到不同分区,但对时间范围查询不友好,Zabbix 的历史表按时间清理时也必须跨分区扫描,等于白做。
Zabbix 7.0 LTS 对 MySQL 8.0 和 MariaDB 10.5+ 的兼容性都已经很成熟,分区语法在这两个分支下没有差别。值得留意的是 MySQL 8.0 之后不再支持PARTITION BY RANGE里的TO_DAYS()这类函数写法,脚本用的是VALUES LESS THAN (unix_timestamp)的原始格式,这反而是兼容性最好的做法。
2.2 下载解压并核对脚本内容
这份资源的脚本包体积不大,按记录里的做法是 wget 加 tar 解压,我在实际部署时习惯先下载到/opt/scripts目录,避免随手丢在 root 家目录里造成管理混乱。解压后不要直接执行,先看一眼脚本里的三个关键参数。
mkdir -p /opt/scripts cd /opt/scripts wget http://bestmonitoringtools.com/dl/zbx_db_partitiong.tar.gz tar -zxvf zbx_db_partitiong.tar.gz ls -l zbx_db_partitiong.sql这里有个习惯问题:tar 包解压出来的文件名是固定的zbx_db_partitiong.sql,如果你之前下载过旧版本,新文件会直接覆盖旧文件。我一般先在别的目录解压,确认内容后再复制到工作目录。脚本本身不包含建库建表的 DDL,它只处理已有的 Zabbix 库表,所以不用担心误初始化数据。
2.3 入库前必改的三个参数
脚本默认的保留策略是历史数据 7 天、趋势数据 365 天,这是给小型监控环境兜底的保守值。但凡你的监控项超过 500 个,或者有高频采集(30 秒以下间隔),7 天的 history 保留期会让分区数量增长速度超出预期。我在生产环境落这个脚本时,通常直接改成 30 天历史、400 天趋势,这个量级对 disk 和查询性能都比较均衡。
vim zbx_db_partitiong.sql # 找到 CREATE PROCEDURE partition_maintenance_all 部分,修改 keep 天数 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);每个partition_maintenance调用的四个参数含义分别是:库名、表名、保留数据天数、分区时间间隔(小时)、预创建分区数量。24表示按天分区,3表示提前创建 3 个未来分区,这个预创建机制很关键,避免跨天瞬间没有可用分区导致写入失败。改完参数后保存,再执行下一步导入。
3. 存储过程逻辑拆解与首轮执行:四段式结构背后的运行顺序
3.1 核心存储过程的功能拆解
这套脚本并不是一次性跑完就结束,它在数据库里创建了四个存储过程,真正干活的是partition_maintenance_all这个调度入口。拆开看,四个过程各司其职:
partition_verify:检查目标表是否已有分区,没有分区时先做初始化分区。partition_create:按给定时间戳创建新分区,创建前会查 information_schema 防止重复。partition_drop:扫描分区名,把时间早于保留阈值的分区整体 DROP 掉。partition_maintenance:串联前三个过程,按间隔计算分区名和时间戳,控制预创建数量。
3.2 脚本执行命令与首次运行诊断
导入脚本的命令语法在记录里写得很明确,但实际执行有几个细节容易翻车。先确认 Zabbix 数据库名和账号权限,如果 Zabbix 库名不是zabbix,比如有人习惯叫zabbix_prod,命令里的库名必须同步改。
mysql -u 'zabbix' -p'zabbixDBpass' zabbix < zbx_db_partitiong.sql提示:执行导入时不要用 root 账号去跑,Zabbix 库的读写账号没有 SUPER 权限也不影响存储过程创建,但 root 执行会把历史遗留的权限问题掩盖掉,后面调度阶段容易出幺蛾子。
首次导入脚本时,如果库里已经积累了几个月的数据,partition_verify会执行一次全表重建分区,这个操作在 InnoDB 下会触发 Online DDL,大表可能要跑一两个小时。期间不要手动去 kill 会话,我见过有人等不及直接 Ctrl+C,结果表结构停留在中间状态,后面所有写入都报Table has no partition for value,修复比等待更痛苦。
导入完成后马上验证存储过程是否到位,这是判断脚本有没有成功的第一步。
SELECT routine_name FROM information_schema.routines WHERE routine_schema = 'zabbix';正常情况下能看到partition_create、partition_drop、partition_maintenance、partition_verify四个存储过程。如果缺了某个,多半是导入时 SQL 语法报错被忽略,把 mysql 客户端的错误输出重定向到文件再查一次。
3.3 手动触发一次全流程,验证表分区结构
脚本导入成功不代表分区逻辑正确,必须先手动调用一次partition_maintenance_all,观察返回信息。这一步是上线前唯一能低成本验证整套逻辑的机会。
mysql -u 'zabbix' -p'zabbixDBpass' zabbix -e "CALL partition_maintenance_all('zabbix');"正常输出会包含partition_create(zabbix, history, p202501200000, 1737331200)这样的信息行,以及partition_drop返回的N/A或实际删除的分区名。看到这些输出后,再用SHOW CREATE TABLE history确认分区定义。
SHOW CREATE TABLE history\G输出中PARTITION BY RANGE (clock)之后跟着一串PARTITION p202501190000 VALUES LESS THAN (1737244800)的定义,说明分区已经生效。如果这里看到PARTITION p202501200000 VALUES LESS THAN (1737331200),对应的时间戳是和 UTC 对齐的,注意 Zabbix 7.0 前端时区设置和数据库时区不一致会导致分区边界偏移,后面避坑章会展开说。
4. 定时调度:事件调度器和 Crontab 双方案及故障切换
4.1 MySQL 事件调度器的开启与权限边界
分区过程必须周期性执行才能持续生效,最推荐的做法是 MySQL 自带的事件调度器(Event Scheduler),它把调度逻辑留在数据库内部,不依赖外部 cron 环境,即使 Zabbix server 所在的机器重装,只要数据库还在,分区任务就不会丢。
开启事件调度器需要改 MySQL 配置文件,注意 MySQL 8.0 和 MariaDB 的配置文件路径不同,不要照搬网上教程。
# 在 [mysqld] 段下追加 [mysqld] event_scheduler = ON修改后重启 MySQL 服务。如果是 MariaDB,配置文件可能在/etc/mysql/mariadb.conf.d/下;如果是 MySQL 8.0,通常改/etc/my.cnf或/etc/mysql/mysql.conf.d/mysqld.cnf。重启后确认状态:
SHOW VARIABLES LIKE 'event_scheduler';检查完状态后创建事件。每条记录里的事件名我建议带上环境后缀,比如zbx_partitioning_prod,方便多环境管理。间隔选 12 小时一次,而不是 24 小时,这样即使某次执行失败,还有一次补救机会。
CREATE EVENT zbx_partitioning ON SCHEDULE EVERY 12 HOUR DO CALL partition_maintenance_all('zabbix');这个事件创建后,可以用SHOW EVENTS\G查看LAST_EXECUTED字段确认是否按时跑过。如果你发现STATUS是DISABLED,检查创建事件时是否用了ENABLE关键字,或者事件调度器在重启后没有重新激活。
4.2 Crontab 方案的适用场景与命令细节
事件调度器要求 MySQL 配置里显式开启,某些托管数据库实例不给 SUPER 权限,无法开启全局 event_scheduler,这时候 Crontab 是更现实的替代方案。用 Crontab 有个隐性好处:每次执行的输出会写入日志文件,排障时能直接看数据库返回信息,而不是去翻 MySQL 的 error log。
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注意 cron 环境变量问题:crontab 里运行的 mysql 默认用的是/usr/bin/mysql这个绝对路径,如果你用which mysql查出来是/usr/local/mysql/bin/mysql,必须改成实际路径,否则 job 会静默失败。密码直接写在 crontab 文件里有安全风险,但 Zabbix 数据库分区这个场景通常运行在内网,权衡下来可以接受;更讲究的做法是创建一个~/.my.cnf文件存放凭据,把密码从命令行参数里去掉。
4.3 调度方案切换时的常见错位
从事件调度器切到 Crontab,或者反过来,最容易出的问题是两边同时启用,分区过程被并发调用。partition_create里有去重检查,但partition_drop并发执行时可能因为另一个会话已经删掉了分区而报错。切换方案时,先禁用旧方案,观察一个完整周期确认没有新的分区维护执行,再启用新方案。
-- 临时停用事件,而不是直接 DROP ALTER EVENT zbx_partitioning DISABLE;保留事件定义而不是删除,是给自己留一条后悔药。万一 Crontab 脚本路径写错或者权限配置失误,只需要一条ALTER EVENT ... ENABLE就能恢复。这个习惯救过我一次,当时某台机器的 crontab 被系统加固脚本重置,分区停了三天没跑,靠重新启用事件调度器补回来了。
5. 前端 Housekeeper 配置与常见问题排查:五个高频翻车点
5.1 前端保留天数必须和存储过程参数对齐
分区脚本解决了数据库层面的删除效率问题,但 Zabbix 前端还有一套自己的清理逻辑。前端Administration → General → Housekeeper里的“数据存储期”设置,控制的是 housekeeper 对历史表和趋势表的 DELETE 行为。如果这里的天数大于分区保留天数,会出现一个尴尬局面:分区已经把 30 天前的历史表数据 DROP 掉了,但 housekeeper 认为还有数据需要保留,扫描表时发现分区已经不存在,反复报错,白白消耗 CPU。
配置原则只有一条:前端 Housekeeper 里的历史记录和趋势存储天数,必须小于等于存储过程里的 KEEP_DATA_DAYS。我的习惯是前端设置比后端少 1 天,比如后端保留 30 天,前端设 29 天,用前端的时间触顶,让 housekeeper 的 DELETE 永远落在已有分区内,分区 DROP 再去清理更早的边界数据,两层逻辑互不踩踏。
5.2 避坑一:分区边界时间戳与前端时区错位
现象:分区表创建成功后,Zabbix 报表页面出现部分时间点数据空白,但数据库查SELECT COUNT(*)有数据。
原因:partition_maintenance里用UNIX_TIMESTAMP(DATE_FORMAT(NOW(), '%Y-%m-%d 00:00:00'))计算当天零点的时间戳,这个NOW()取的是 MySQL 会话时区。如果 MySQL 的time_zone是SYSTEM且和 Zabbix 前端的时区不一致(比如数据库用 UTC,前端用 Asia/Shanghai),分区边界会比前端显示时间晚 8 个小时,新数据落入前一天的分区,前端按当天分区查询时自然缺数据。
解决:统一 MySQL 和 Zabbix 前端的时区。最稳的做法是在 MySQL 配置里固定时区,而不是依赖操作系统的SYSTEM时区。
[mysqld] default-time-zone = '+08:00'改完重启 MySQL 后,执行CALL partition_maintenance_all('zabbix')清理一次错位的分区边界,再观察数据写入是否落到正确的分区。
5.3 避坑二:大表首次分区耗时过长且无进度反馈
现象:导入脚本后执行CALL partition_maintenance_all在history_uint表上卡住,跑了两个多小时还没结束,既没有报错也没有返回。
原因:history_uint表如果已经有上亿行数据,首次分区相当于全表重建,InnoDB 需要重写聚簇索引和二级索引,耗时和数据量成正比。这个操作本身就是阻塞性的,期间表不可写,Zabbix 采集数据会持续积压。
解决:不要在生产环境直接对最大的表做首次分区。我的做法是拆分两步:先把history_uint和trends_uint单独处理,用partition_verify单独初始化,再跑partition_maintenance_all。如果表实在太大,建议在维护窗口做,或者先停掉对应监控项的采集,让表停止增长再操作。另外,information_schema.partitions查询在分区初始化期间会锁元数据,不要频繁执行诊断查询,除了增加压力没有意义。
5.4 避坑三:事件执行报错Table has no partition for value
现象:开启事件调度器后,Zabbix 日志持续刷[Z3005] query failed: [1526] Table has no partition for value ...,前端图表大面积空白。
原因:事件调度器指定的执行间隔是 12 小时,但partition_maintenance_all预创建的分区数量是 3 个,按天分区足够覆盖,问题通常出在事件创建语句本身写错表名或库名。执行CALL partition_maintenance_all('zabbix')时传入的库名和实际 Zabbix 数据库名不一致,分区创建到错误的库下,原表一直没有新分区。
解决:确认传入的库名参数和连接数据库时使用的库名完全一致。这里有个坑:mysql -e "CALL partition_maintenance_all('zabbix')"环境里,如果命令行连接时没有指定默认库,存储过程内部仍然能执行,但 create 语句里拼接的库名是错的。所以调度命令必须写成mysql zabbix -e "CALL ..."这种带库名的形式,和导入脚本时的命令保持同一个库上下文。
5.5 避坑四:磁盘空间告警不降反升
现象:分区配置完成后,磁盘空间占用没有明显下降,甚至短暂上涨。
原因:DROP PARTITION确实比DELETE高效,但在 InnoDB 下,删除分区释放的空间不会立刻归还给操作系统,而是先标记为可复用。如果表空间是独立文件模式(innodb_file_per_table=ON),DROP 分区会删除对应的.ibd文件片段,空间能真正释放;如果用的是系统表空间,碎片会留在文件内部,看起来占用没变。
解决:确认innodb_file_per_table是开启状态,MySQL 8.0 默认开启,老版本迁移上来的实例要检查。如果已经用系统表空间跑了很久,分区后空间没有释放,需要OPTIMIZE TABLE重建一次,这个操作耗时较长,建议维护窗口做。另外,Zabbix 的history写入高峰期和定时分区执行时间重叠时,新分区创建需要分配额外空间,短期内磁盘占用不降反升是正常的,把调度时间调整到采集低峰期能缓解。
5.6 避坑五:MySQL 事件调度器重启后状态丢失
现象:MySQL 服务重启后,SHOW VARIABLES LIKE 'event_scheduler'显示ON,但SHOW EVENTS显示所有事件的STATUS为DISABLED。
原因:MySQL 的事件状态默认不持久化,服务重启后事件会恢复到DISABLED状态,除非在事件定义时显式指定ON COMPLETION PRESERVE ENABLE。网上很多教程没有提这一点,照抄创建语句的人最容易踩这个坑。
解决:创建事件时显式声明状态。
CREATE EVENT zbx_partitioning ON SCHEDULE EVERY 12 HOUR ON COMPLETION PRESERVE ENABLE DO CALL partition_maintenance_all('zabbix');如果事件已经创建,用ALTER EVENT修改状态:
ALTER EVENT zbx_partitioning ENABLE;为了保证 MySQL 重启后事件自动恢复启用,我一般会写一个小的 shell 脚本来做状态校验,放在 cron 里每天检查一次。脚本逻辑很简单:查询事件状态,如果发现DISABLED就自动ALTER EVENT ... ENABLE,并记录日志。这个脚本不在这份资源里,但属于分区运维的必备补充。
6. 修改保留天数与数据一致性验证:从存储过程到前端对齐的关键步
分区方案上线一段时间后,业务方会提出新需求:历史数据保留期要从默认值改成自定义值,比如历史 90 天、趋势 18 个月。直接改前端 Housekeeper 的天数不够,存储过程里的 KEEP_DATA_DAYS 参数也必须同步改,否则分区 DROP 会把你前端还想保留的数据提前删掉。正确做法是创建一个新的存储过程,而不是修改原来的过程,保留旧过程作为回滚方案。
6.1 创建新存储过程与事件切换
以历史 90 天、趋势 550 天为例,创建新的存储过程,保持和原来相同的调度入口风格,但参数值换掉。
DELIMITER $$ CREATE PROCEDURE partition_maintenance_all_90and550(SCHEMA_NAME VARCHAR(32)) BEGIN CALL partition_maintenance(SCHEMA_NAME, 'history', 90, 24, 3); CALL partition_maintenance(SCHEMA_NAME, 'history_log', 90, 24, 3); CALL partition_maintenance(SCHEMA_NAME, 'history_str', 90, 24, 3); CALL partition_maintenance(SCHEMA_NAME, 'history_text', 90, 24, 3); CALL partition_maintenance(SCHEMA_NAME, 'history_uint', 90, 24, 3); CALL partition_maintenance(SCHEMA_NAME, 'trends', 550, 24, 3); CALL partition_maintenance(SCHEMA_NAME, 'trends_uint', 550, 24, 3); END$$ DELIMITER ;创建完成后,把事件调度器指向新过程。这里注意不要直接 DROP 旧过程,保留它对后续参数回退有帮助。
ALTER EVENT zbx_partitioning ON SCHEDULE EVERY 12 HOUR DO CALL partition_maintenance_all_90and550('zabbix');如果用的是 Crontab 方案,直接改 crontab 里的调用命令,把partition_maintenance_all换成新过程的名称。旧过程的调用行可以注释掉,不要删除,下次改参数时翻历史记录能快速对比。
6.2 数据一致性验证的四个检查点
切换保留天数后,不能只看前端图表正常就认为万事大吉,需要从数据层面做一致性验证。我的验证顺序是从分区边界开始,逐步向上层查,每一步都确认没有异常再进入下一步。
验证分区创建和删除是否符合新参数,重点看预创建的分区数量和保留边界。
SELECT TABLE_NAME, PARTITION_NAME, PARTITION_DESCRIPTION FROM information_schema.partitions WHERE TABLE_SCHEMA = 'zabbix' AND TABLE_NAME = 'history' ORDER BY PARTITION_DESCRIPTION DESC LIMIT 5;正常结果里,最顶上的分区应该覆盖未来两到三天的时间戳,且不存在PARTITION_DESCRIPTION为NULL的非分区表记录。如果这里看到的都是过去时间戳,说明事件调度没跑起来,或者跑的是旧过程。验证历史数据和趋势数据的分区覆盖时长。
SELECT MIN(FROM_UNIXTIME(clock)), MAX(FROM_UNIXTIME(clock)) FROM history;查询结果最小时间应该不早于当前时间减去保留天数。如果出现更早的数据,说明partition_drop还没执行到位,等下一个调度周期再看,或者手动执行一次过程。同时注意history和history_uint两张表的覆盖范围不同是正常的,取决于各表实际写入的监控项类型。验证 Zabbix 前端 Housekeeper 配置的一致性。
# 登录 Zabbix 前端,Administration -> General -> Housekeeper # 对比历史记录、趋势的数据存储期与数据库分区保留天数前端天数要小于等于数据库保留天数。这一步最容易被忽略,前端改完,数据库没改,等到边界期一到,housekeeper 会尝试删除已被分区 DROP 的数据,报错日志里全是No partition found for ...。最后验证事件调度器的 LAST_EXECUTED 时间戳确认调度周期正常。
SELECT EVENT_NAME, STATUS, LAST_EXECUTED FROM information_schema.events WHERE EVENT_NAME = 'zbx_partitioning';6.3 调度时间窗口与分区预创建数量的调优
改动保留天数时,顺带可以重新评估partition_maintenance里的CREATE_NEXT_INTERVALS参数。默认值是 3,代表预创建未来 3 个分区。对按天分区的场景,3 个预创建分区只覆盖未来 3 天,如果事件调度器连续故障两天,恢复后就会面对“当前时间没有可用分区”的写入失败。
我会把CREATE_NEXT_INTERVALS从 3 调整到 7,成本是每次循环多创建几个空分区,但缓冲时间拉长到一周,调度故障的容错空间大了很多。这个参数在存储过程定义里,改动需要重建存储过程或手工 DROP 后重新创建。
-- 在 partition_maintenance 里把 3 改成 7 后重跑创建语句 SET @__interval=1; create_loop: LOOP IF @__interval > 7 THEN LEAVE create_loop; END IF; -- 后续逻辑不变 END LOOP;调整完这个参数,检查分区表和information_schema的一致性,确认新的未来分区已经创建出来。另外注意,LAST_EXECUTED距离当前时间超过 24 小时,基本可以断定调度链路出了问题,不是调整参数能解决的,需要回到事件状态或 crontab 配置去排查。
6.4 保留天数调整后的回滚路径
新参数上线运行后发现业务方不接受,比如趋势数据保留 550 天的查询性能下降明显,需要回退到原来的 400 天。不用重新导入整个脚本,把调度入口指回旧过程即可。事件调度器方案一条命令能做回滚:
ALTER EVENT zbx_partitioning ON SCHEDULE EVERY 12 HOUR DO CALL partition_maintenance_all('zabbix');Crontab 方案则把被注释的旧命令取消注释,新命令注释掉。执行完成后,跑一次CALL partition_maintenance_all('zabbix')手动触发,强制把多余分区的 DROP 逻辑执行一遍,快速收敛到旧保留边界。从那以后,我每次改保留天数都强制走一遍 6.2 里的一整套验证流程,先看分区边界,再看数据覆盖,最后核对前端配置,这套顺序跑完才敢宣布操作完成。希望帮到你。
本文还有配套的精品资源,点击获取