简介:本资源为Zabbix 7.0 LTS部署及数据库分区优化的操作记录文档,面向运维工程师、监控系统管理员及需要处理Zabbix数据库性能瓶颈的技术人员。内容聚焦MySQL/MariaDB环境下历史记录与趋势表的分区方案,针对housekeeper进程繁忙、旧数据删除效率低等常见问题给出可落地的解决思路。资源包为1个PDF文件,大小约835KB,便于在数据库服务器或本地环境中随时查阅与对照操作。文档围绕分区脚本的获取与执行、分区过程的自动调度、前端保留天数配置以及分区参数调整等环节展开,并提示备份数据库、大型库执行耗时等注意事项,适用于Zabbix 3.0以后各版本。已有354人学习,适合希望降低维护成本、提升监控系统长期稳定性的读者参考。
1. Zabbix 7.0 LTS 落地:为什么数据库分区是绕不开的第一道坎
很多团队装完 Zabbix 7.0 LTS,把主机一加、模板一挂,看着绿油油的监控面板觉得万事大吉。结果跑上两三个月,history、trends 这几张表膨胀到几千万行,前端翻个最新数据要转圈十几秒,MySQL 磁盘 IO 直接打满,告警延迟从秒级掉到分钟级。这不是 Zabbix 本身慢,是底层数据库没做分区,所有历史数据堆在一张无界大表里,查询和清理全靠全表扫描硬扛。
Zabbix 7.0 LTS 是官方长期支持版本,默认用 MySQL 或 MariaDB 存历史数据,核心的 history、history_uint、trends、trends_uint 四张表会随监控项数量线性增长。分区优化的本质,是按时间维度把大表切成若干小表,让查询只扫对应分区、过期数据直接 drop 分区而不是 delete 行。这套操作适合自建 Zabbix 的运维和 SRE,尤其是监控主机超过 200 台、或者采集间隔压到 30 秒以内的场景。下面从选型、部署、分区到排错,把我实际跑过一遍的路径讲清楚。
2. 部署前的选型与系统准备:MySQL 还是 MariaDB,装在哪
2.1 数据库选型:MySQL 8.0 与 MariaDB 10.11 的取舍
Zabbix 7.0 LTS 官方同时支持 MySQL 8.0 和 MariaDB 10.5 以上版本。两者在分区语法上几乎一致,差异主要在细节:MySQL 8.0 的information_schema查询性能更好,窗口函数和 CTE 支持完整,适合后续做报表分析;MariaDB 10.11 在中小规模下内存占用略低,mysql命令行工具对老运维更顺手。
我一般这样选:新机器、监控规模 500 台以内,用 MariaDB 10.11,装完即用,配置简单;如果后续要接 Grafana 做复杂查询,或者团队已有 MySQL 8.0 运维经验,直接上 MySQL 8.0。两者都别用 5.7,Zabbix 7.0 对 5.7 的支持已经不在推荐列表里,字符集和分区行为都有坑。
操作系统层面,CentOS 7.9 和 Ubuntu 22.04 是最常见的两个选择。CentOS 7.9 自带 MariaDB 5.5,版本太老,必须换源装新版;Ubuntu 22.04 默认仓库的 MySQL 8.0 版本够用,省一步。下面以 Ubuntu 22.04 + MySQL 8.0 为主线,CentOS 7.9 的差异我会单独标注。
2.2 系统参数与依赖安装
装数据库之前,先把系统层面的限制调开,否则后面 Zabbix Server 连上来会报连接数或文件句柄错误。
# 调整内核参数,Zabbix 和 MySQL 都需要较多文件句柄和连接 sudo tee -a /etc/sysctl.conf <<'EOF' fs.file-max = 2097152 net.ipv4.ip_local_port_range = 1024 65000 net.core.somaxconn = 65535 vm.swappiness = 10 EOF sudo sysctl -p # 调整用户进程限制 sudo tee -a /etc/security/limits.conf <<'EOF' zabbix soft nofile 65535 zabbix hard nofile 65535 mysql soft nofile 65535 mysql hard nofile 65535 EOFfs.file-max控制系统级文件句柄上限,Zabbix Server 每个采集进程都会占句柄,200 台主机起步就要几万。vm.swappiness=10让系统尽量少用 swap,数据库场景下 swap 会拖慢查询。limits.conf里的配置需要重新登录或重启对应服务才生效,别改完就急着装。
依赖包方面,Ubuntu 22.04 执行:
sudo apt update sudo apt install -y wget gnupg2 software-properties-common \ mysql-server mysql-client libmysqlclient-dev \ snmp snmpd fping curlCentOS 7.9 则先换 MySQL 官方源,再装mysql-community-server,同时把fping从 EPEL 装,因为 Zabbix 的 ICMP 采集依赖它。snmp和snmpd是监控网络设备时用的,不监控交换机可以先不装。
提示:CentOS 7.9 的默认防火墙是 firewalld,装完 MySQL 后要放行 3306,Zabbix Server 本机连接可以只监听 127.0.0.1,减少暴露面。
3. Zabbix 7.0 LTS 服务端与数据库的安装配置
3.1 MySQL 8.0 初始化与 Zabbix 专用库
MySQL 装好后先做安全初始化,设置 root 密码、移除匿名用户和测试库。
sudo mysql_secure_installation # 按提示设置 root 密码,其余选项一路 Y接着创建 Zabbix 专用数据库和用户。字符集必须用utf8mb4,排序规则用utf8mb4_bin,这是 Zabbix 官方要求,用错会导致中文主机名乱码或索引失效。
CREATE DATABASE zabbix CHARACTER SET utf8mb4 COLLATE utf8mb4_bin; CREATE USER 'zabbix'@'localhost' IDENTIFIED BY 'Zbx_Str0ng_Pass'; GRANT ALL PRIVILEGES ON zabbix.* TO 'zabbix'@'localhost'; -- 如果 Zabbix Server 和数据库不在同一台机器,把 localhost 换成 Server 的 IP FLUSH PRIVILEGES;utf8mb4_bin是二进制排序,区分大小写,Zabbix 的键值匹配依赖这个行为。密码别用纯数字或常见词,Zabbix 前端登录和数据库是两套凭据,数据库这层被爆破一样会丢数据。
然后导入 Zabbix 7.0 的初始表结构。从官方仓库下载对应版本的server.sql.gz,解压导入:
wget https://cdn.zabbix.com/zabbix/sources/stable/7.0/zabbix-7.0.0.tar.gz tar -zxvf zabbix-7.0.0.tar.gz cd zabbix-7.0.0/database/mysql zcat schema.sql.gz | mysql -uzabbix -p zabbix zcat images.sql.gz | mysql -uzabbix -p zabbix zcat data.sql.gz | mysql -uzabbix -p zabbix导入顺序不能乱:schema 建表,images 存前端图标,data 插初始配置。三个文件都导入完,用mysql -uzabbix -p zabbix -e "show tables;"确认表数量在 170 张左右。
3.2 Zabbix Server 编译安装与关键配置
Zabbix 7.0 可以用包管理装,也可以源码编译。包管理省事但版本受仓库限制,源码编译能精确控制编译选项。我一般用源码编译,因为要开 MySQL 和 SNMP 支持。
sudo apt install -y build-essential libpcre3-dev libevent-dev \ libmysqlclient-dev libsnmp-dev libssh2-1-dev libopenipmi-dev ./configure --prefix=/usr/local/zabbix \ --enable-server --enable-agent \ --with-mysql --with-net-snmp \ --with-libcurl --with-ssh2 \ --with-openipmi make -j$(nproc) && sudo make install--with-mysql是必须的,不指定数据库后端编译出来的 Server 起不来。--with-net-snmp决定能不能监控交换机。-j$(nproc)用满 CPU 核数加速编译,内存小于 2G 的机器改成-j2,否则容易 OOM。
编译完配置/usr/local/zabbix/etc/zabbix_server.conf,核心几项:
DBHost=localhost DBName=zabbix DBUser=zabbix DBPassword=Zbx_Str0ng_Pass DBPort=3306 StartPollers=20 StartPollersUnreachable=5 StartTrappers=10 StartPingers=5 CacheSize=64M HistoryCacheSize=64M TrendCacheSize=32M ValueCacheSize=128MStartPollers是采集进程数,200 台主机建议 20 起步,每增加 100 台加 5。CacheSize和HistoryCacheSize根据内存调,16G 内存的机器可以给到 128M。ValueCacheSize影响前端取最新值的速度,给太小前端会卡。
启动前先建 zabbix 系统用户,再写 systemd 服务文件:
sudo useradd -r -s /sbin/nologin zabbix sudo tee /etc/systemd/system/zabbix-server.service <<'EOF' [Unit] Description=Zabbix Server After=network.target mysql.service [Service] Type=simple User=zabbix ExecStart=/usr/local/zabbix/sbin/zabbix_server -c /usr/local/zabbix/etc/zabbix_server.conf Restart=on-failure [Install] WantedBy=multi-user.target EOF sudo systemctl daemon-reload sudo systemctl enable --now zabbix-serverAfter=mysql.service保证数据库先起,否则 Zabbix Server 启动时连不上库会反复重启。Restart=on-failure让进程异常退出后自动拉起,生产环境必加。
3.3 前端与 Agent 的快速验证
前端用 Nginx + PHP-FPM 部署,把 Zabbix 源码里的ui目录拷到 web 根目录,改conf/zabbix.conf.php里的数据库连接信息。PHP 版本要求 8.0 以上,扩展要开bcmath、mbstring、gd、mysqli、xml。
Agent 装在被监控机上,配置Server=指向 Zabbix Server 的 IP,启动后到前端「数据采集 → 主机」里添加主机,关联Linux by Zabbix agent模板。等一两分钟,最新数据里出现 CPU、内存、磁盘曲线,说明整条链路通了。
注意:如果前端显示「Zabbix server is not running」,先看
/var/log/zabbix/zabbix_server.log,八成是数据库密码错或zabbix库权限没给全。
4. 数据库分区优化:把 history 和 trends 按时间切开
4.1 为什么必须分区,以及分区键怎么选
Zabbix 默认把每一条采集数据写进history、history_uint、history_str、history_text、history_log以及对应的trends、trends_uint。一台主机 50 个监控项、60 秒采集一次,一天就是 7 万多行,一年 2500 万行。不分区的话,delete from history where clock < xxx这种清理语句会锁表几十分钟,期间采集数据全堵在队列里。
分区的核心是按clock字段做 RANGE 分区,每个分区放一天或一周的数据。查询时 MySQL 只扫对应分区,清理时直接ALTER TABLE ... DROP PARTITION,秒级完成,不产生大事务。
分区键必须选clock,因为 Zabbix 所有历史查询都带时间范围条件。分区粒度我一般用「一天一分区」,保留 30 到 90 天,具体看磁盘和合规要求。趋势数据trends保留时间长,可以按「一月一分区」,保留 12 到 24 个月。
4.2 分区存储过程与定时任务
Zabbix 官方提供了一套分区脚本,核心是几个存储过程:partition_create、partition_drop、partition_maintenance。我把它精简成适合自己环境的版本,先建过程:
DELIMITER // CREATE PROCEDURE partition_create( IN p_schema VARCHAR(64), IN p_table VARCHAR(64), IN p_partition VARCHAR(64), IN p_start BIGINT, IN p_end BIGINT ) BEGIN SET @sql = CONCAT( 'ALTER TABLE ', p_schema, '.', p_table, ' ADD PARTITION (PARTITION ', p_partition, ' VALUES LESS THAN (', p_end, '))' ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; END // DELIMITER ;这个过程的逻辑是拼一条ALTER TABLE ... ADD PARTITION动态 SQL 并执行。p_start和p_end是 Unix 时间戳,p_end决定分区上界。注意分区名不能重复,重复执行会报错,所以调用前要先查information_schema.partitions确认不存在。
再建删除过程:
DELIMITER // CREATE PROCEDURE partition_drop( IN p_schema VARCHAR(64), IN p_table VARCHAR(64), IN p_retention INT ) BEGIN DECLARE done INT DEFAULT 0; DECLARE part_name VARCHAR(64); DECLARE cur CURSOR FOR SELECT PARTITION_NAME FROM information_schema.PARTITIONS WHERE TABLE_SCHEMA = p_schema AND TABLE_NAME = p_table AND PARTITION_NAME IS NOT NULL AND PARTITION_NAME != 'p_first' AND PARTITION_DESCRIPTION < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL p_retention DAY)); DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; OPEN cur; read_loop: LOOP FETCH cur INTO part_name; IF done THEN LEAVE read_loop; END IF; SET @sql = CONCAT('ALTER TABLE ', p_schema, '.', p_table, ' DROP PARTITION ', part_name); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; END LOOP; CLOSE cur; END // DELIMITER ;p_retention是保留天数,游标遍历所有分区描述小于「当前时间减保留天数」的分区,逐个 drop。PARTITION_NAME != 'p_first'是保护第一个兜底分区,防止误删导致数据无处可放。
最后是维护主过程,把创建和删除串起来:
DELIMITER // CREATE PROCEDURE partition_maintenance( IN p_schema VARCHAR(64), IN p_table VARCHAR(64), IN p_retention INT, IN p_interval INT ) BEGIN DECLARE p_name VARCHAR(64); DECLARE p_start BIGINT; DECLARE p_end BIGINT; SET p_start = UNIX_TIMESTAMP(DATE(NOW())); SET p_end = p_start + p_interval; SET p_name = CONCAT('p', DATE_FORMAT(NOW(), '%Y%m%d')); IF NOT EXISTS ( SELECT 1 FROM information_schema.PARTITIONS WHERE TABLE_SCHEMA = p_schema AND TABLE_NAME = p_table AND PARTITION_NAME = p_name ) THEN CALL partition_create(p_schema, p_table, p_name, p_start, p_end); END IF; CALL partition_drop(p_schema, p_table, p_retention); END // DELIMITER ;p_interval是分区跨度,按天分区就传 86400。过程先算今天零点的时间戳,拼出分区名如p20250101,不存在就创建,然后调删除过程清理过期分区。
4.3 对现有大表做分区改造
如果 Zabbix 已经跑了一段时间,history 表里已有大量数据,不能直接ALTER TABLE ... PARTITION BY RANGE,因为 MySQL 要求分区表的主键必须包含分区键。Zabbix 的 history 表主键是(itemid, clock),clock 已经在主键里,可以直接改。但trends表主键是(itemid, clock)同样满足。真正麻烦的是history_uint这类表,主键也是(itemid, clock),没问题。
改造语句:
ALTER TABLE history PARTITION BY RANGE (clock) ( PARTITION p_first VALUES LESS THAN (UNIX_TIMESTAMP('2025-01-01 00:00:00')) );先建一个兜底分区,把所有历史数据装进去。然后调partition_maintenance逐天补建新分区。注意这个 ALTER 在大表上执行会锁表,几千万行的表可能要跑十几分钟,建议在业务低峰期做,或者用pt-online-schema-change工具在线改。
改造完成后,用这条 SQL 验证分区是否生效:
SELECT PARTITION_NAME, TABLE_ROWS, PARTITION_DESCRIPTION FROM information_schema.PARTITIONS WHERE TABLE_SCHEMA = 'zabbix' AND TABLE_NAME = 'history' ORDER BY PARTITION_ORDINAL_POSITION;TABLE_ROWS是估算值,不精确但能看出数据分布。PARTITION_DESCRIPTION是分区上界时间戳,确认每个分区对应一天。
4.4 定时任务与参数调优
分区维护要挂到 cron 或 MySQL 事件调度器。我一般用系统 cron,每天凌晨 2 点跑一次:
0 2 * * * mysql -uzabbix -p'Zbx_Str0ng_Pass' zabbix -e "CALL partition_maintenance('zabbix','history',30,86400); CALL partition_maintenance('zabbix','history_uint',30,86400); CALL partition_maintenance('zabbix','trends',365,86400); CALL partition_maintenance('zabbix','trends_uint',365,86400);"history 保留 30 天,trends 保留 365 天,这是常见组合。如果磁盘紧张,history 可以压到 7 天,trends 压到 180 天。注意 cron 里的密码明文有安全风险,可以用~/.my.cnf存凭据,权限设 600。
MySQL 侧还要调几个参数配合分区:
innodb_file_per_table = ON innodb_buffer_pool_size = 4G innodb_flush_log_at_trx_commit = 2innodb_file_per_table让每个分区独立表空间,drop 分区时直接删文件,回收磁盘快。innodb_buffer_pool_size给物理内存的 50% 到 70%,16G 内存给 8G。innodb_flush_log_at_trx_commit=2牺牲一点持久性换写入性能,监控数据丢几秒可接受。
5. 避坑与排查:分区改造中最容易翻车的五个点
5.1 分区键不在主键里,ALTER 直接报错
现象:执行ALTER TABLE history PARTITION BY RANGE (clock)时报A PRIMARY KEY must include all columns in the table's partitioning function。
原因:MySQL 要求分区键必须是主键或唯一索引的一部分。Zabbix 的 history 表主键是(itemid, clock),clock 在里面,所以能改。但如果之前有人手动改过表结构,把主键改成只有itemid,就会报这个错。
解决:先SHOW CREATE TABLE history确认主键定义,如果 clock 不在主键里,需要先加回主键或建包含 clock 的唯一索引。改主键在大表上同样锁表,务必低峰操作。
5.2 分区名重复导致定时任务报错
现象:cron 每天跑,某天开始报Duplicate partition name p20250101。
原因:partition_maintenance里虽然查了information_schema判断存在性,但如果同一天手动执行过一次,或者 cron 跑了两次,第二次就会撞名。
解决:过程里已经加了IF NOT EXISTS判断,正常不会重复。如果还是报,检查是不是有多个 cron 任务同时跑,或者 MySQL 事件调度器和系统 cron 都在调。保留一个即可。
5.3 drop 分区后磁盘空间没释放
现象:ALTER TABLE ... DROP PARTITION执行成功,但df -h看磁盘占用没降。
原因:如果innodb_file_per_table是 OFF,所有分区共享 ibdata1,drop 分区不会释放文件。另外 MySQL 8.0 的innodb_undo_tablespaces和临时表空间也可能占着空间。
解决:确认innodb_file_per_table=ON,drop 分区后等几分钟让 InnoDB 后台线程回收。如果还是没降,检查是不是有长事务或未提交的查询持有旧分区文件的句柄,SHOW PROCESSLIST看有没有卡住的会话。
5.4 分区后查询反而变慢
现象:分区建好了,但前端查最新数据比以前还慢。
原因:分区裁剪没生效。如果查询条件里clock用了函数,比如WHERE FROM_UNIXTIME(clock) > ...,MySQL 无法裁剪分区,会扫全部分区。
解决:Zabbix 内部查询都是直接比较 clock 时间戳,不会用函数包。如果自己写报表 SQL,确保clock是裸字段比较。另外分区数量别太多,一天一分区、保留 90 天就是 90 个分区,超过 200 个分区元数据管理开销会上升,可以改成一周一分区。
5.5 Zabbix Server 的 housekeeper 和分区冲突
现象:开了分区后,Zabbix 自带的 housekeeper 还在跑,两边同时删数据,数据库负载高。
原因:Zabbix 的 housekeeper 是按行 delete 清理历史数据,和分区 drop 功能重叠。分区生效后应该关掉 housekeeper 对 history 和 trends 的清理。
解决:在前端「管理 → 常规 → 管家」里,把History和Trends的保留天数设为 0,表示不通过 housekeeper 清理。或者直接在zabbix_server.conf里设HousekeepingFrequency=0完全关掉。清理工作全部交给分区过程。
6. 分区效果验证与长期维护的一个关键习惯
分区做完不是终点,得有一套验证方法确认它真的在干活。我一般用三个指标交叉看:分区数量、单分区行数、查询扫描行数。
先看分区分布:
SELECT TABLE_NAME, COUNT(*) AS part_count, SUM(TABLE_ROWS) AS total_rows FROM information_schema.PARTITIONS WHERE TABLE_SCHEMA = 'zabbix' AND TABLE_NAME IN ('history','history_uint','trends','trends_uint') GROUP BY TABLE_NAME;正常情况下 history 的分区数应该等于保留天数加一两个兜底分区,trends 同理。如果分区数远小于保留天数,说明维护过程没跑成功,去查 cron 日志。
再看单分区行数是否均匀:
SELECT PARTITION_NAME, TABLE_ROWS FROM information_schema.PARTITIONS WHERE TABLE_SCHEMA = 'zabbix' AND TABLE_NAME = 'history' ORDER BY PARTITION_ORDINAL_POSITION DESC LIMIT 10;最近几个分区的行数应该在同一量级,如果某一天突然翻倍,可能是那天加了大量监控项或改了采集间隔,属于正常波动。但如果某个分区行数为 0,说明那天没有数据写入,要查 Zabbix Server 是否宕过。
最后用EXPLAIN验证分区裁剪:
EXPLAIN SELECT * FROM history WHERE itemid = 12345 AND clock > UNIX_TIMESTAMP(NOW()) - 3600;看partitions列,应该只列出最近一两个分区名,而不是全部。如果列出了所有分区,说明裁剪没生效,回头检查 clock 字段有没有被函数包裹。
长期维护上,我养成了一个习惯:每周一早上花五分钟看一眼分区数量和磁盘趋势。分区数量对不上保留天数,立刻查 cron;磁盘增长曲线突然变陡,提前扩盘或缩短保留期。这套动作比等数据库报警了再救火省心得多。Zabbix 7.0 LTS 本身很稳,真正让它长期稳定的,是底下这套按时间切分、自动清理的分区机制。希望帮到你。
本文还有配套的精品资源,点击获取