news 2026/10/8 3:17:08

Zabbix 3.0.10数据库膨胀清理:history_uint与alerts大表瘦身实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Zabbix 3.0.10数据库膨胀清理:history_uint与alerts大表瘦身实战

一个跑了一年多的 Zabbix 3.0.10,数据库膨胀几乎必然会发生。尤其当你打开 MySQL 看到history_uint和alerts几个表占了十几个 GB,前端查询历史告警越来越慢,甚至在 Zabbix 前端里点开“问题”都会转圈。这个版本不像后面 4.0/5.0 自带那么完善的分区支持,维护老版本,清理历史告警问题数据几乎是必修课。写这篇文章,是因为我最近刚帮一个朋友处理了同样的场景:Zabbix 3.0.10,数据库后端是 MySQL,某天磁盘告警,一查全是告警历史表和问题事件表,最后用一套手工脚本安全把几个大表从 60GB 缩到 20GB 以内。下面把整个思路、具体 SQL、自动脚本和踩过的坑都整理出来,供同样被困在 3.0.x 老版本里的运维参考。

1. 先搞清楚 Zabbix 3.0.10 的数据都放在哪儿

很多人在清理前容易犯一个错误:上来就DELETE FROM history;,结果发现数据量没降多少,还误删了想要的趋势数据。Zabbix 里的“历史数据”和“告警数据”其实是两套体系,先分清楚,后面才不会把该留的也删了。

1.1 监控历史数据与告警数据是两套体系

history系列表存的是监控项采集到的原始值,比如 CPU、内存、流量、磁盘 IO。Zabbix 按数值类型拆成了多张表:history存浮点数,history_uint存整数,history_str存短字符串,history_text存长文本,history_log存日志文件。这些表是增长最快的大户,但它们属于“监控历史”,不完全等于告警数据。

另一套是与“问题/告警”相关的表。events表记录触发器状态变化的事件,也就是问题产生、恢复、发现、自动注销等完整事件流;alerts表记录每一次告警动作的发送情况,比如发邮件、发脚本、标记动作执行结果;problem表是 Zabbix 3.0 开始新增的“问题”视图表,前端“问题”页面能看到的内容基本都来自它。你说的“历史告警问题数据”,实际指的就是alerts、events、problem以及它们关联的acknowledges这类表。

从这里可以得出一个结论:如果是磁盘空间告急,清理优先级最高的是history和trends;如果是要让前端“问题/告警”历史变清爽,重心则在alerts、events、problem。两者可以一起做,但 SQL 条件不能混着写。

1.2 3.0.10 版本的表结构特征

Zabbix 3.0.10 的表结构和高版本有区别。在 3.0.x 里,events表字段大概是eventid、source、object、objectid、value、clock、ns、value_changed、acknowledged。problem表里会保存与问题状态相关的r_clock、r_eventid、r_ns这类恢复时间字段。alerts表则记录了eventid、mediatypeid、sendto、status、retries、error等动作发送信息。

如果对这些字段不熟,动手前先跑两条查询看一眼实际结构:

SHOW CREATE TABLE events; SHOW CREATE TABLE problem; SHOW CREATE TABLE alerts;

把字段结构确认清楚再写删除 SQL,能少走很多弯路。老版本的官方文档和前端页面不一定对得上,实际库里到底有没有某个列,以SHOW CREATE TABLE为准。

下面这张表是 3.0.10 里最常见的几类大表,以及它们在清理时的定位:

表名存储内容数据量增长特点清理建议
history/history_uint监控项原始采集值每秒都可能写入,增长最猛按保留天数清理,严格控制
history_str/history_text长文本型监控项字符型主机名、告警信息多视情况保留更短周期
trends/trends_uint小时级趋势数据相对稳定,但久了也大建议保留一年以上
alerts告警动作发送记录依赖动作配置和告警频率保留 90~180 天即可
events所有触发器/发现事件每次问题产生、恢复都插入保留 90~180 天即可
problem前端问题列表数据如果长期不清理会留大量已恢复记录只保留当前问题和最近历史问题
auditlog用户操作日志登录、改配置都写按需保留

这张表也解释了为什么我们待会要按“先删子表、再删主表”的顺序,而不是把events一下清掉。

2. 动手清理前,先想清楚保留策略和备份方案

这一步别省。Zabbix 3.0.10 的数据库清理不像高版本有可视化按钮,全得靠 SQL。一旦删错,可能把正在告警的问题也删了,或者把审计需要的历史记录弄丢。

2.1 保留多久合适:结合容量和审计需求

没有统一的天数,我的建议是:

  • history/history_uint保留 7~30 天。如果 Grafana 有长期趋势需求,可以通过trends来补,不用靠原始历史。
  • trends/trends_uint保留 365 天左右,个别需要长期回溯的监控项可以在前端单独调整。
  • alerts和events保留 90~180 天。如果公司有告警审计要求,千万不能小于 180 天,否则审计要历史记录时很被动。
  • problem只保留当前未恢复问题和近 90 天已恢复问题,避免前端问题列表越翻越长。

我见过一个极端案例:某环境每天产生 8~10 万条告警动作记录,一个月alerts表就增加了近 300 万行。算下来一年光alerts就超 3000 万行,按单行 200 字节估算,光这一张表就要 6GB 以上,还不算索引。如果不定保留策略,清理速度肯定赶不上增长速度。

2.2 备份与影响范围评估

清理前要做备份,但大数据库做全量mysqldump不现实。比较稳妥的做法是:

  • 如果有从库或备份系统,先确认最近一次备份可用,再在从库上做一次快照验证。
  • 单库超过 50GB 时,优先在从库上把备份导出到一个独立文件,而不是在主库上mysqldump。
  • 如果没有从库,至少要针对要删除的alerts、events、problem表做一次独立备份,比如导出这些表今天之前的数据。

清理期间的影响范围也要提前评估。alerts、events都是大表,DELETE 时会占用较多数据库 IO,可能导致 Zabbix Server 写入变慢、前端查询变卡,甚至告警动作发送延迟。所以一定要选在低峰期,比如凌晨 2 点到 5 点,并通告相关同事避免误报。

2.3 检查当前数据库引擎和空间占用

在清理前先定位最占空间的表。MySQL 后端可以用这段 SQL:

SELECT table_name AS '表名', engine AS '引擎', ROUND((data_length + index_length) / 1024 / 1024, 2) AS '总大小MB', table_rows AS '估算行数' FROM information_schema.tables WHERE table_schema = 'zabbix' ORDER BY (data_length + index_length) DESC LIMIT 20;

如果后端是 PostgreSQL,换这条:

SELECT relname AS table_name, pg_size_pretty(pg_total_relation_size(c.oid)) AS total_size, n_live_tup AS live_rows FROM pg_class c JOIN pg_stat_user_tables t ON c.relname = t.relname WHERE c.relname NOT LIKE 'pg_%' ORDER BY pg_total_relation_size(c.oid) DESC LIMIT 20;

另外要确认innodb_file_per_table参数。MySQL 5.6/5.7 默认是开启的,每个 InnoDB 表有独立的.ibd文件,这样删除后执行OPTIMIZE TABLE才能释放磁盘空间。如果没开,所有表都挤在共享表空间里,删除后即便OPTIMIZE也很难缩容。3.0.10 时代很多环境还在用 MySQL 5.5,这一点尤其要留意。

3. 手工清理历史告警问题数据的标准流程

清理告警问题数据,我建议按这个顺序执行:先删alerts,再处理problem里的已恢复历史记录,最后删events。原因很简单:alerts和problem都关联了events.eventid,先删子表可以避免留下孤儿数据,也避免后面删除events时出现外键关联报错。

3.1 先拿“告警动作记录”开刀:alerts 表

alerts表是你确认动作是否发送成功、邮件是否有报错的关键记录,但它不是唯一的数据来源。只要 Zabbix Server 的日志和审计系统独立保留,alerts没必要留太久。

删除前先看一眼要删多少:

SELECT COUNT(*) AS cnt FROM alerts WHERE clock < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 180 DAY));

确认数量之后,分块删除。一次性DELETE几百万行在 InnoDB 里会产生超大事务,持有大量行锁,副作用是你可能把 Zabbix Server 的写事务也堵死。分块的标准姿势是每次只删 5000 行:

DELETE FROM alerts WHERE clock < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 180 DAY)) LIMIT 5000;

重复执行直到影响行数为 0。如果担心删除顺序不稳定,也可以加ORDER BY alertid LIMIT 5000,让每次删除的是一个递增区间:

DELETE FROM alerts WHERE clock < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 180 DAY)) ORDER BY alertid LIMIT 5000;

3.2 再清理历史事件表:events

events表保存了所有事件流水。光看容量,它可能没有history_uint大,但它直接影响前端“事件”和“问题”历史页面。

在删除前,需要确认一个问题:当前problem表里还有哪些事件正在使用。我们可以用LEFT JOIN把所有当前仍被综合问题表引用的eventid排除掉:

DELETE FROM events WHERE eventid NOT IN ( SELECT eventid FROM problem ) AND clock < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 180 DAY));

这种写法在大表上性能不理想,尤其是problem表很大时,NOT IN会让优化器做大量扫描。更稳妥的写法是分块删除,每次先挑一批要删的eventid:

DELETE FROM events WHERE eventid IN ( SELECT eventid FROM ( SELECT e.eventid FROM events e LEFT JOIN problem p ON e.eventid = p.eventid WHERE p.eventid IS NULL AND e.clock < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 180 DAY)) ORDER BY e.eventid LIMIT 5000 ) tmp );

如果这步报“You can't specify target table for update in FROM clause”,可以把内层子查询改成 JOIN 形式:

DELETE e FROM events e JOIN ( SELECT e2.eventid FROM events e2 LEFT JOIN problem p ON e2.eventid = p.eventid WHERE p.eventid IS NULL AND e2.clock < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 180 DAY)) ORDER BY e2.eventid LIMIT 5000 ) x ON e.eventid = x.eventid;

这里有个细节:如果你之前已经用OPTIMIZE TABLE或者别的方式把problem表的时间范围缩短了,但events里还有对应的事件,这条语句会把对应事件保留下来。换句话说,清理顺序很重要,必须先把不再需要的problem历史问题记录清掉,再删events。

3.3 处理 problem 表里已经恢复但残留的问题记录

Zabbix 3.0.10 的problem表容易出现两类问题:

  • 正常场景下,问题恢复后r_clock会写入恢复时间,但表里仍然保留这条已恢复问题记录,用于前端“问题历史”显示。
  • 异常场景下,Zabbix Server 由于数据库抖动或处理异常,问题已经恢复了,但problem表还留着r_clock = 0的记录,前端就一直显示“正在处理”。

对第一类,我们要清理的是已恢复且超过保留期的记录。可以用这条 SQL:

DELETE FROM problem WHERE r_clock > 0 AND r_clock < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 90 DAY));

不要动r_clock = 0的记录,因为r_clock = 0通常代表当前仍未恢复,一旦误删,前端会丢失正在告警的问题,并且在触发器仍处于异常状态时,你短期内看不到告警。

对第二类,问题已经实际恢复但problem表状态没更新,最安全的做法不是直接 DELETE,而是先重启 Zabbix Server 让它重新计算一次问题状态:

systemctl restart zabbix-server

如果重启后仍然显示问题,再去人工确认事件历史。确认主机对应触发器确实已经产生了“恢复”事件,再删除残留的problem记录。比如查到残留的eventid = 123456,可以执行:

DELETE FROM problem WHERE eventid = 123456; DELETE FROM acknowledges WHERE eventid = 123456;

这里是“确认后”再操作,别拿这条命令当成通用清理手段。

3.4 如果 history 和 trends 也想清

磁盘压力太大时,history系列表肯定要处理。删除命令很简单,但问题在于表太大,直接 DELETE 会长时间锁表。建议也是分块:

DELETE FROM history_uint WHERE clock < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 30 DAY)) LIMIT 5000;

这里有几个坑要提一下。history_uint的写入频率非常高,如果 Zabbix Server 还在运行,删除事务和写入事务混在一起,优化器可能选择奇怪的执行计划。我的实操经验是,清理大表前,临时停掉 Zabbix Server 的 housekeeper,甚至停掉 Server 本体的采集进程一段时间,不然很容易出现“一直删不完”的假象。

另外,删除后空间不会立刻释放。MySQL InnoDB 的表空间默认不会因为 DELETE 变小,你必须在删除后执行:

OPTIMIZE TABLE alerts, events, problem, history_uint, history, trends, trends_uint;

如果是 PostgreSQL:

VACUUM (FULL, ANALYZE) alerts; VACUUM (FULL, ANALYZE) events; REINDEX TABLE events;

OPTIMIZE TABLE是重打表数据并重建索引,耗时和表大小成正比。执行期间会有短暂锁表,请务必放在低峰期,并提前算好磁盘可用空间,因为重建过程需要临时占用一些额外空间。

4. 把清理写成自动化脚本,并接入定时任务

手敲 SQL 只适合一次性清理。如果已经形成“三个月清一次”的固定维护周期,直接把清理动作写成脚本,要比每次都人工查、人工删省事得多。下面给一个我实际在用的 MySQL 后端清理脚本,核心思路是:设置保留天数、分块删除、记录日志。

4.1 MySQL 版本的一键清理脚本

#!/bin/bash # zabbix_cleanup.sh # 针对 Zabbix 3.0.10 + MySQL 后端,清理历史/告警/问题数据 DB_HOST=127.0.0.1 DB_PORT=3306 DB_USER=zabbix DB_PASS='your_password' DB_NAME=zabbix EVENT_DAYS=180 ALERT_DAYS=180 PROBLEM_DAYS=90 HISTORY_DAYS=30 TRENDS_DAYS=365 MYSQL="mysql -h${DB_HOST} -P${DB_PORT} -u${DB_USER} -p${DB_PASS} ${DB_NAME} -N -s" EVENT_TS=$(date -d "${EVENT_DAYS} days ago" +%s) ALERT_TS=$(date -d "${ALERT_DAYS} days ago" +%s) PROBLEM_TS=$(date -d "${PROBLEM_DAYS} days ago" +%s) HISTORY_TS=$(date -d "${HISTORY_DAYS} days ago" +%s) TRENDS_TS=$(date -d "${TRENDS_DAYS} days ago" +%s) log() { echo "$(date '+%F %T') $1" } delete_by_chunk() { local table=$1 local ts=$2 while :; do local cnt cnt=$(${MYSQL} -e "SELECT COUNT(*) FROM ${table} WHERE clock < ${ts};") if [ "${cnt}" -le 0 ]; then break fi log "清理 ${table} 剩余 ${cnt} 行" ${MYSQL} -e "DELETE FROM ${table} WHERE clock < ${ts} LIMIT 5000;" sleep 1 done } log "开始清理 alerts" delete_by_chunk alerts "$ALERT_TS" log "清理已恢复且超过保留期的 problem" while :; do cnt=$(${MYSQL} -e "SELECT COUNT(*) FROM problem WHERE r_clock > 0 AND r_clock < ${PROBLEM_TS};") if [ "${cnt}" -le 0 ]; then break fi ${MYSQL} -e "DELETE FROM problem WHERE r_clock > 0 AND r_clock < ${PROBLEM_TS} LIMIT 5000;" sleep 1 done log "清理 events(保留 problem 仍在引用的记录)" while :; do cnt=$(${MYSQL} -e "SELECT COUNT(*) FROM events e LEFT JOIN problem p ON e.eventid = p.eventid WHERE p.eventid IS NULL AND e.clock < ${EVENT_TS};") if [ "${cnt}" -le 0 ]; then break fi ${MYSQL} -e "DELETE e FROM events e JOIN (SELECT e2.eventid FROM events e2 LEFT JOIN problem p ON e2.eventid = p.eventid WHERE p.eventid IS NULL AND e2.clock < ${EVENT_TS} ORDER BY e2.eventid LIMIT 5000) x ON e.eventid = x.eventid;" sleep 1 done for table in history history_uint history_str history_text history_log; do log "开始清理 ${table}" delete_by_chunk "$table" "$HISTORY_TS" done for table in trends trends_uint; do log "开始清理 ${table}" delete_by_chunk "$table" "$TRENDS_TS" done log "开始优化表" ${MYSQL} -e "OPTIMIZE TABLE alerts, events, problem, history, history_uint, history_str, history_text, history_log, trends, trends_uint;" log "清理完成"

这个脚本把password直接放在脚本里有安全隐患,推荐改用~/.my.cnf或者环境变量。比如在配置文件里写:

[client] host=127.0.0.1 user=zabbix password=your_password

然后把脚本里的-p${DB_PASS}去掉,脚本看起来更干净,也不会在进程列表中暴露密码。

4.2 用 crontab 定时执行

把这个脚本放到/opt/scripts/下,给执行权限:

chmod +x /opt/scripts/zabbix_cleanup.sh

然后加 crontab,比如每个月 1 号凌晨执行:

0 3 1 * * /opt/scripts/zabbix_cleanup.sh > /var/log/zabbix_cleanup.log 2>&1

日志一定要保留。下一次清理前先看上一次跑完过了多久、每张表删了多少,才能判断这个清理频率是否跟得上数据增长速度。如果每次跑完不到半个月就又爆空间,说明保留天数设得太长,或者 Zabbix 采集 item 数量本身需要优化,而不是继续靠清理脚本硬扛。

4.3 清理完怎么验证

清理完不要直接走人,建议做三件事:

  1. 看磁盘空间释放了多少,确认OPTIMIZE TABLE执行成功。
  2. 打开 Zabbix 前端,分别看“问题”“事件”“报表”页面是否有异常。
  3. 跑一条最近数据查询,确认数据写入无异常,当天监控值正常展示。

如果前端某个页面查历史变空白,先检查保留天数是否把“最近”也误删了。比如clock存的 Unix 时间戳,date +%s换算这一步写错,很容易删掉近几天的数据。

5. 常见问题与排查技巧实录

这节收集几个我在清理过程中真正遇到过的问题,按速查表的方式整理出来,遇到同类情况可以直接照做。

现象常见原因解决办法
执行 DELETE 卡住不动大事务锁等待,Zabbix Server 正在写入同一张表低峰期执行,分块删除;必要时临时停 Server
删完空间没释放InnoDB 表空间没有重建执行OPTIMIZE TABLE,并确保innodb_file_per_table=ON
前端“问题”仍显示已解决的告警problem表残留未更新记录先重启 Zabbix Server 让状态重算;确认恢复后再清理残留
alerts删了,events还很大两个表没同步清理按顺序先删alerts,再删events
housekeeper 总是和手动清理抢资源没有关闭 housekeeper临时配置StartHousekeepers=0,清理完再改回 1
误删了当天数据时间阈值计算有误从备份恢复对应表,后续用UNIX_TIMESTAMP时先SELECT确认
PostgreSQL 删除后空间不释放缺少 VACUUM FULL执行VACUUM (FULL, ANALYZE)

这里单独说一下StartHousekeepers=0的操作。Zabbix 3.0.10 的 housekeeper 是 Zabbix Server 自带的清理线程,默认开启,它会定期按保留策略删除历史数据。手动清理时如果不关,会出现两个进程同时 DELETE 同一张表,互相争锁,轻则删除变慢,重则产生大量锁等待。操作方式是修改zabbix_server.conf:

StartHousekeepers=0

然后重启zabbix-server。清理完再改回 1,再次重启。这个方法只建议在维护窗口做,不要长期关闭 housekeeper,否则之后还得全靠手动清理。

另一个容易被忽略的点是分区表。如果你的环境已经把history_uint、events按天或按月做了分区,那清理效率会高很多,因为可以直接DROP PARTITION,而不是逐行 DELETE。但 3.0.10 官方原生没有默认分区,很多人的分区脚本是自己写的。如果你准备以后长期维护 3.0.10,建议尽早把大表改造成分区表,否则数据量到亿级之后,再跑这种清理脚本也会非常痛苦。

最后再分享一个个人经验:清理 Zabbix 老版本数据库这种事,最怕的不是数据库太大,而是“这表到底是什么含义”没搞清楚。我在现场踩过最狠的坑,是直接执行了DELETE FROM events WHERE clock < ...,结果虽然没有外键约束,但前端在查看历史问题时能看到事件记录,problem表里又留着对应的已恢复记录,两边对不上,查询结果非常怪。后来所有操作统一成“先删 alerts 和已恢复 problem,再删 events”的顺序,才稳定下来。如果你也要在 3.0.10 上清理历史告警问题数据,强烈建议先跑一遍只读统计,仔细核对表结构和保留周期,再动手。这样清完不翻车,磁盘也干净。

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

AI搜索时代GEO优化全案:从SEO到生成式引擎优化的底层逻辑与实操指南

1. AI搜索生态与生成式引擎优化的底层逻辑1.1 从传统SEO到GEO&#xff1a;搜索逻辑的根本性迁移过去十年&#xff0c;我们做搜索优化的核心逻辑是“关键词匹配外链权重页面结构”。你只要把标题、描述、H标签、正文关键词密度这些要素做到位&#xff0c;再配合一定量的高质量外…

作者头像 李华
网站建设 2026/10/8 3:16:01

探矿RAG数据清洗实战:TXT、Word、PDF、网页四类格式处理链路

1. 探矿数据为什么总在清洗环节翻车搞过探矿项目的人都有一个共同体会&#xff1a;钻探编录、地质填图、采样化验这几类数据&#xff0c;原始形态远比想象中杂乱。一个中型勘查区跑下来&#xff0c;TXT格式的测井曲线记录、Word写的钻孔柱状图说明、PDF扫描的化验报告、还有从内…

作者头像 李华
网站建设 2026/10/8 3:15:56

Grid网格布局实战复盘:从Flexbox进阶到二维布局

Grid 网格布局这些年反复被提起&#xff0c;可真把它用明白的人&#xff0c;并不算多。我带前端新人时最常看到这样一个画面&#xff1a;垂直居中会用 Flexbox&#xff0c;做导航条会用 Flexbox&#xff0c;一旦要搭“左边菜单、右边内容、顶上栏、底下栏”这种整页骨架&#x…

作者头像 李华
网站建设 2026/10/8 3:15:32

IMS网络路由组织原理与实战配置指南

简介&#xff1a;本资源是一份面向通信工程专业学生、IMS网络运维工程师及VoLTE/5G核心网初学者的权威技术课件&#xff0c;系统解析电信级IMS网络路由组织的核心架构与落地实践。内容涵盖IMS分省部署模型、ENUM/DNS两级号码解析机制、与固网/C网/异网运营商的信令&#xff08;…

作者头像 李华
网站建设 2026/10/8 3:13:55

数据采集基础梳理:从网页抓取到清洗入库的实战经验

做数据分析、搞业务报表的时候&#xff0c;最尴尬的事情往往不是模型不会调&#xff0c;也不是可视化不够炫&#xff0c;而是数据根本拿不到。数据采集说白了&#xff0c;就是把散落在网页、接口、文档里的信息&#xff0c;按照结构化的方式收集、清洗&#xff0c;再存进自己可…

作者头像 李华
网站建设 2026/10/8 3:13:24

一条SQL查询语句的完整执行链路与优化实践

很多朋友问过我同一个问题&#xff1a;我明明就是执行了一条select&#xff0c;MySQL 到底在里面做了什么&#xff1f;为什么同样的 SQL&#xff0c;数据量一上来就慢得离谱&#xff1f;为什么索引明明建了&#xff0c;执行计划里却看不到&#xff1f;这三个问题如果只看 SQL 本…

作者头像 李华