[翻譯]——Zabbix: Partitioning MySQL / MariaDB database Tables in 5 min


 

前言:本文是對這篇博客Zabbix: Partitioning MySQL / MariaDB database Tables in 5 min的翻譯,如有翻譯不當的地方,敬請諒解,請尊重原創和翻譯勞動成果,轉載的時候請注明出處。謝謝!

 

英文地址:https://bestmonitoringtools.com/zabbix-partitioning-tables-on-mysql-database

 

在本教程中,我們將逐步學習如何使用分區腳本(partitioning script)在MySQL或MariaDB服務器上對Zabbix數據庫(history和trends表)進行分區。

 

Zabbix從主機采集數據並將其存儲在history和trends表中,Zabbix的history表中保存原始數據(Zabbix采集的每一個值),trends表中存儲每小時內的合並數據,那些數據的平均值、最小值、最大值。

 

Zabbix的housekeeping進程負責刪除trends表和history表中的舊數據。使用delete語句從數據庫刪除舊數據可能對數據庫性能產生負面的性能影響。因此,我們中許多人都收到過令人討厭的警報Zabbix housekeeper processes more than 75% busy

 

 

這個問題能夠通過數據庫分區方案輕松的解決。每小時或每一天為分區表創建一個分區,並在不需要它們的時候,刪除這些分區。使用SQL刪除分區比使用DELET語句刪除數據更高效。

 

你可以將本教程用於任何Zabbix 3.0之后的版本(3.2、3.4、4.0、4.2、4.4、5.0、5.2等)

 

在開始之前,請備份你的zabbix數據庫,但是如果是新安裝的Zabbix Server,則無需備份。

 

步驟1:下載分區腳本進行分區

 

在數據庫服務器上下載並解壓SQL腳本zbx_db_partitiong.sql

 

wget https://bestmonitoringtools.com/wp-content/uploads/2019/10/zbx_db_partitiong.tar.gz
 
tar -zxvf zbx_db_partitiong.tar.gz

 

腳本"zbx_db_partitiong.sql"中配置保留hisotry表中7天的數據和trends表中365天的數據——如果你可以接受這些設置,請轉到步驟2.

 

如果你想修改trends表和history表中保留數據的天數,請打開zbx_db_partitiong.sql文件,如下圖所示,修改設置並保存文件。

 

clip_image001

 

 

步驟2:使用腳本創建分區的存儲過程。

 

運行腳本的語法為mysql -u '<db_username>' -p'<db_password>' <zb_database_name> < zbx_db_partitiong.sql,現在用你Zabbix服務器的數據庫名稱、用戶名和密碼去創建這些存儲過程。

 

mysql -u 'zabbix' -p'zabbixDBpass' zabbix < zbx_db_partitiong.sql

 

在新安裝的Zabbix 服務器上,,腳本將非常快速的創建分區過程,但是在大型數據庫上,此過程坑你持續數小時。

 

譯者點評: 這里應該是作者的筆誤或表達有誤,不管zabbix數據庫多大,創建這些存儲過程都非常快,但是在大型的zabbix數據庫上,運行這些維護分區(創建分區、刪除分區)的存儲過程腳本將非常慢。猜測是作者表達有誤!

 

 

步驟3:自動運行分區存儲過程

 

我們已經創建了分區的存儲過程,但是在我們運行它之前,它是不會做任何事情的。

 

這一步是最重要的,因為我們必須使用分區的存儲過程定期(每天)刪除和創建分區。

 

不用擔心,你不必手工做這些操作,我們可以使用兩種工具來完成這些任務: MySQL事件調度程序或Crontab - 選擇你喜歡的一種技術。

 

 

 

注意:如果沒有正確設置Crontab作業來定期創建新分區,Zabbix將會停止采集數據,並且在MySQL的日志文件中出現[Z3005] query failed: [1526] Table has no partition for value .."這樣的錯誤。

 

選項1:使用MySQL事件調度器自動管理分區(推薦這種方式)

 

默認情況下,MySQL的事件調度器(計划任務)是禁用的。你需要在配置文件的[mysqld]下設置event_scheduler=ON來啟用它。

 

[mysqld] 
 
event_scheduler = ON

 

如果你不知道該文件位於哪里的話? 如果你看過我的安裝和優化Zabbix教程的話,那么你應該知道MySQL的配置文件(10_my_tweaks.cnf)應該位於/etc/mysql/mariadb.conf.d/ /etc/my.cnf.d/下 ,否則嘗試使用以下命令進行搜索:

 

sudo grep --include=*.cnf -irl / -e "\[mysqld\]"

 

更改配置后,請重新啟動MySQL服務器,以使設置生效!

 

sudo systemctl restart mysql

 

好的,MySQL事件調度器啟用后,讓我們用下面命令來檢查確認一下

 

root@dbserver:~ $ mysql -u 'zabbix' -p'zabbixDBpass' zabbix -e "SHOW VARIABLES LIKE 'event_scheduler';"
+-----------------+-------+
| Variable_name | Value |
+-----------------+-------+
| event_scheduler | ON |
+-----------------+-------+

 

現在,我們可以創建一個事件,該事件每1小時運行一次存儲過程 partition_maintenance_all

 

mysql -u 'zabbix' -p'zabbixDBpass' zabbix -e "CREATE EVENT zbx_partitioning ON SCHEDULE EVERY 3600 SECOND DO CALL partition_maintenance_all('zabbix');"

 

一個小時后,請使用以下命令檢查事件是否已成功執行。

 

mysql -u 'zabbix' -p'zabbixDBpass' zabbix -e "SELECT * FROM INFORMATION_SCHEMA.events\G"
EVENT_CATALOG: def
                   ...
CREATED: 2020-10-24 11:01:07
LAST_ALTERED: 2020-10-24 11:01:07
LAST_EXECUTED: 2020-10-24 11:43:07
                   ...

 

選項2:使用Crontab作業自動管理分區

 

如果您無法使用MySQL事件調度器,則Crontab作業也是一個不錯的選擇。 使用命令sudo crontab -e打開crontab文件,在文件中的任何位置添加以下命令行,在每天03:30 AM對Zabbix數據庫進行分區維護操作:

 

 

30 03 * * * /usr/bin/mysql -u 'zabbix' -p'zabbixDBpass' zabbix -e "CALL partition_maintenance_all('zabbix');" > /tmp/CronDBpartitiong.log 2>&1

 

譯者點評: 這種使用賬號密碼明文的方式,其實不太安全,應該使用mysql_config_editor技術配置賬號密碼,避免在作業或腳本中使用數據庫賬號明文

 

30 03 * * * /usr/bin/mysql --login-path=zabbixdb zabbix -e "CALL partition_maintenance_all('zabbix');" > /tmp/CronDBpartitiong.log 2>&1

 

Crontab作業會執行腳本,通過存儲過程維護分區(刪除掉舊分區和創建新分區),並且將這些操作記錄在日志““/tmp/CronDBpartitiong.log中。

 

 

因此,如果你沒有耐心等待作業運行,也可以從終端運行這個命令

 

root@dbserver:~ $ mysql -u 'zabbix' -p'zabbixDBpass' zabbix -e "CALL partition_maintenance_all('zabbix');"
+-----------------------------------------------------------+
| msg |
+-----------------------------------------------------------+
| partition_create(zabbix,history,p201910150000,1571180400) |
+-----------------------------------------------------------+
+-----------------------------------------------------------+
 ...etc.

 

然后檢查分區狀態,如下所示:

 

root@dbserver:~ $ mysql -u 'zabbix' -p'zabbixDBpass' zabbix -e "show create table history\G"
Table: history
Create Table: CREATE TABLE history (
itemid bigint(20) unsigned NOT NULL,
clock int(11) NOT NULL DEFAULT '0',
value double(16,4) NOT NULL DEFAULT '0.0000',
ns int(11) NOT NULL DEFAULT '0',
KEY history_1 (itemid,clock)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin
/*!50100 PARTITION BY RANGE (clock)
(PARTITION p201910140000 VALUES LESS THAN (1571094000) ENGINE = InnoDB,
PARTITION p201910150000 VALUES LESS THAN (1571180400) ENGINE = InnoDB,
PARTITION p201910160000 VALUES LESS THAN (1571266800) ENGINE = InnoDB) */

 

從上面輸出結果中,你可以看到我們為history和trend表創建了3個分區。

 

譯者點評:運行這些腳本,只會創建當前日期往后的三個分區,所以存在一些弊端,如果history等表中已經有大量歷史數據,那么此時最靠前的一個分區將會變得無比巨大。

 

步驟4: 在Zabbix前端中配置Housekeeping

 

clip_image002

 

如果圖片沒有表述清楚,請按照以下步驟在Zabbix前端上配置Housekeeping

 

轉到Housekeeping部分:管理——>常規(或者是一般的翻譯)——>Housekeeping

取消歷史記錄和趨勢部分下啟用內部整理(Enable internal housekeeping)的復選標記;

歷史記錄和趨勢部分的替代項目趨勢期(Override item trend period)上打勾;

歷史記錄和趨勢部分下,為趨勢和歷史記錄定義數據存儲期的天數(必須與數據庫分區中配置的天數相同-歷史記錄應為7天,趨勢圖應為365天,如果您未更改 腳本中的默認設置);

單擊更新按鈕。

 

步驟5: 修改分區設置(修改history和trends表中的天數)

 

 

 

有時候,你可能最初為history表和trends表設置了太長的時間,因此磁盤空間消耗的太快了,或者相反的情況,你沒有為history和trends表配置足夠的天數,那該怎么辦呢?

 

你無需再次運行該腳本,只需創建一個新過程並將其設置為從crontab作業中運行即可。

 

 

a) 創建新的分區存儲過程

 

連接到MySQL或MariaDB服務器:

 

mysql -u 'zabbix' -p'zabbixDBpass'
 
use zabbix;

 

創建一個新的存儲過程,但是根據你的需要修改history和trends表保留的天數。我一般會為history表保留30天的數據,trends表保留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 ;

 

b)更新MySQL事件調度器或Crontab作業

 

在前面部分(或者翻譯為上一步),我們創建了存儲過程,但是該存儲過程還未調用過。現在,我們必須用的新的存儲過程替換舊的存儲過程,該過程將定期刪除和添加分區。根據您在Zabbix實例上配置的內容,選擇以下兩個選項之一。

 

選項1:更新MySQL事件調度器

 

如果您按照本教程創建了事件調度程序,請使用此命令將舊存儲過程替換為新的存儲過程。

 

mysql -u 'zabbix' -p'zabbixDBpass' zabbix -e "ALTER EVENT zbx_partitioning ON SCHEDULE EVERY 3600 SECOND DO CALL partition_maintenance_all_30and400('zabbix');"

 

選項2:更新Crontab作業

 

 用命令sudo crontab -e"打開crontab文件,注釋掉舊的作業命令,並新增一個新的作業

 

# old procedure, still exists in the database so it can be used if needed
#
# 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

 

 

 

步驟6:關於Zabbix分區腳本的信息

 

本指南中使用的Zabbix分區SQL腳本包含以下分區過程:

 

DELIMITER $$
CREATE PROCEDURE `partition_create`(SCHEMANAME varchar(64), TABLENAME varchar(64), PARTITIONNAME varchar(64), CLOCK int)
BEGIN
        /*
           SCHEMANAME = The DB schema in which to make changes
           TABLENAME = The table with partitions to potentially delete
           PARTITIONNAME = The name of the partition to create
        */
        /*
           Verify that the partition does not already exist
        */
 
        DECLARE RETROWS INT;
        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
                /*
                   1. Print a message indicating that a partition was created.
                   2. Create the SQL to create the partition.
                   3. Execute the SQL from #2.
                */
                SELECT CONCAT( "partition_create(", SCHEMANAME, ",", TABLENAME, ",", PARTITIONNAME, ",", CLOCK, ")" ) AS msg;
                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$$
DELIMITER ;
DELIMITER $$
CREATE PROCEDURE `partition_drop`(SCHEMANAME VARCHAR(64), TABLENAME VARCHAR(64), DELETE_BELOW_PARTITION_DATE BIGINT)
BEGIN
        /*
           SCHEMANAME = The DB schema in which to make changes
           TABLENAME = The table with partitions to potentially delete
           DELETE_BELOW_PARTITION_DATE = Delete any partitions with names that are dates older than this one (yyyy-mm-dd)
        */
        DECLARE done INT DEFAULT FALSE;
        DECLARE drop_part_name VARCHAR(16);
 
        /*
           Get a list of all the partitions that are older than the date
           in DELETE_BELOW_PARTITION_DATE.  All partitions are prefixed with
           a "p", so use SUBSTRING TO get rid of that character.
        */
        DECLARE myCursor CURSOR FOR
                SELECT partition_name
                FROM information_schema.partitions
                WHERE table_schema = SCHEMANAME AND table_name = TABLENAME AND CAST(SUBSTRING(partition_name FROM 2) AS UNSIGNED) < DELETE_BELOW_PARTITION_DATE;
        DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
 
        /*
           Create the basics for when we need to drop the partition.  Also, create
           @drop_partitions to hold a comma-delimited list of all partitions that
           should be deleted.
        */
        SET @alter_header = CONCAT("ALTER TABLE ", SCHEMANAME, ".", TABLENAME, " DROP PARTITION ");
        SET @drop_partitions = "";
 
        /*
           Start looping through all the partitions that are too old.
        */
        OPEN myCursor;
        read_loop: LOOP
                FETCH myCursor INTO drop_part_name;
                IF done THEN
                        LEAVE read_loop;
                END IF;
                SET @drop_partitions = IF(@drop_partitions = "", drop_part_name, CONCAT(@drop_partitions, ",", drop_part_name));
        END LOOP;
        IF @drop_partitions != "" THEN
                /*
                   1. Build the SQL to drop all the necessary partitions.
                   2. Run the SQL to drop the partitions.
                   3. Print out the table partitions that were deleted.
                */
                SET @full_sql = CONCAT(@alter_header, @drop_partitions, ";");
                PREPARE STMT FROM @full_sql;
                EXECUTE STMT;
                DEALLOCATE PREPARE STMT;
 
                SELECT CONCAT(SCHEMANAME, ".", TABLENAME) AS `table`, @drop_partitions AS `partitions_deleted`;
        ELSE
                /*
                   No partitions are being deleted, so print out "N/A" (Not applicable) to indicate
                   that no changes were made.
                */
                SELECT CONCAT(SCHEMANAME, ".", TABLENAME) AS `table`, "N/A" AS `partitions_deleted`;
        END IF;
END$$
DELIMITER ;
DELIMITER $$
CREATE PROCEDURE `partition_maintenance`(SCHEMA_NAME VARCHAR(32), TABLE_NAME VARCHAR(32), KEEP_DATA_DAYS INT, HOURLY_INTERVAL INT, CREATE_NEXT_INTERVALS INT)
BEGIN
        DECLARE OLDER_THAN_PARTITION_DATE VARCHAR(16);
        DECLARE PARTITION_NAME VARCHAR(16);
        DECLARE OLD_PARTITION_NAME VARCHAR(16);
        DECLARE LESS_THAN_TIMESTAMP INT;
        DECLARE CUR_TIME INT;
 
        CALL partition_verify(SCHEMA_NAME, TABLE_NAME, HOURLY_INTERVAL);
        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;
 
        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);
 
END$$
DELIMITER ;
DELIMITER $$
CREATE PROCEDURE `partition_verify`(SCHEMANAME VARCHAR(64), TABLENAME VARCHAR(64), HOURLYINTERVAL INT(11))
BEGIN
        DECLARE PARTITION_NAME VARCHAR(16);
        DECLARE RETROWS INT(11);
        DECLARE FUTURE_TIMESTAMP TIMESTAMP;
 
        /*
         * Check if any partitions exist for the given SCHEMANAME.TABLENAME.
         */
        SELECT COUNT(1) INTO RETROWS
        FROM information_schema.partitions
        WHERE table_schema = SCHEMANAME AND table_name = TABLENAME AND partition_name IS NULL;
 
        /*
         * If partitions do not exist, go ahead and partition the table
         */
        IF RETROWS = 1 THEN
                /*
                 * Take the current date at 00:00:00 and add HOURLYINTERVAL to it.  This is the timestamp below which we will store values.
                 * We begin partitioning based on the beginning of a day.  This is because we don't want to generate a random partition
                 * that won't necessarily fall in line with the desired partition naming (ie: if the hour interval is 24 hours, we could
                 * end up creating a partition now named "p201403270600" when all other partitions will be like "p201403280000").
                 */
                SET FUTURE_TIMESTAMP = TIMESTAMPADD(HOUR, HOURLYINTERVAL, CONCAT(CURDATE(), " ", '00:00:00'));
                SET PARTITION_NAME = DATE_FORMAT(CURDATE(), 'p%Y%m%d%H00');
 
                -- Create the partitioning query
                SET @__PARTITION_SQL = CONCAT("ALTER TABLE ", SCHEMANAME, ".", TABLENAME, " PARTITION BY RANGE(`clock`)");
                SET @__PARTITION_SQL = CONCAT(@__PARTITION_SQL, "(PARTITION ", PARTITION_NAME, " VALUES LESS THAN (", UNIX_TIMESTAMP(FUTURE_TIMESTAMP), "));");
 
                -- Run the partitioning query
                PREPARE STMT FROM @__PARTITION_SQL;
                EXECUTE STMT;
                DEALLOCATE PREPARE STMT;
        END IF;
END$$
DELIMITER ;
DELIMITER $$
CREATE PROCEDURE `partition_maintenance_all`(SCHEMA_NAME VARCHAR(32))
BEGIN
                CALL partition_maintenance(SCHEMA_NAME, 'history', 7, 24, 3);
                CALL partition_maintenance(SCHEMA_NAME, 'history_log', 7, 24, 3);
                CALL partition_maintenance(SCHEMA_NAME, 'history_str', 7, 24, 3);
                CALL partition_maintenance(SCHEMA_NAME, 'history_text', 7, 24, 3);
                CALL partition_maintenance(SCHEMA_NAME, 'history_uint', 7, 24, 3);
                CALL partition_maintenance(SCHEMA_NAME, 'trends', 365, 24, 3);
                CALL partition_maintenance(SCHEMA_NAME, 'trends_uint', 365, 24, 3);
END$$
DELIMITER ;

 

你還需要更多信息嗎? 請觀看有關Zabbix的MySQL數據庫分區的視頻。

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM