Search code examples
mysqldatabase-partitioning

Dynamic Partition Creation in mysql using stored procedure and event scheduler


I have a table on which I would like to create an auto-partitioning scheme by first partitioning the table by month and then sub-partitioning by id.

I recently learned that mysql does not support auto-partitioning and the only way to create dynamic partitions is by stored-procedures and event schedulers.

My requirement is that new entries are automatically created into new partitions and after a year,the old records are deleted automatically.

How do I do this?

Please suggest me some methods on how to implement stored procedures and event schedulers to support dynamic partitioning and also to delete the old records automatically.

This is the schema:

CREATE TABLE `ORDER_HISTORY` (
  `Id` bigint(20) NOT NULL,
  `Invoice_Number` varchar(16) NOT NULL,
  `User_Id` int(10) NOT NULL,
  `Store_ID` mediumint(6) NOT NULL,
  `Store_Entity_Id` mediumint(8) NOT NULL,
  `Item_List` blob NOT NULL,
  `Order_Time` datetime NOT NULL,
  `Payment_Time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `Payment_Type` tinyint(2) NOT NULL,
  `Payment_Retry_Attempts` tinyint(1) NOT NULL,
  `Payment_TransactionID` varchar(32) NOT NULL,
  `Sub_Total_Amount` decimal(10,2) NOT NULL DEFAULT '0.00',
  `CGST_Tax_Amount` decimal(6,2) NOT NULL DEFAULT '0.00',
  `SGST_Tax_Amount` decimal(6,2) NOT NULL DEFAULT '0.00',
  `Other_Tax_Amount` decimal(6,2) NOT NULL DEFAULT '0.00',
  `Service_Fee` decimal(6,2) NOT NULL DEFAULT '0.00',
  `Earned_Cashback_Amount` decimal(4,2) NOT NULL DEFAULT '0.00',
  `Used_Cashback_Amount` decimal(4,2) NOT NULL DEFAULT '0.00',
  `Used_Coupon` mediumint(8) DEFAULT NULL,
  `Used_Coupon_Discount` decimal(4,2) NOT NULL DEFAULT '0.00',
  `Grand_Total_Amount` decimal(10,2) NOT NULL DEFAULT '0.00',
  `Status` tinyint(3) NOT NULL,
  `Manager_Id` smallint(5) DEFAULT NULL,
  `Store_Name` varchar(32) DEFAULT NULL,
  `User_Name` varchar(32) DEFAULT NULL,
  `User_Phone_Number` varchar(10) DEFAULT NULL,
  `Manager_Phone_Number` varchar(10) NOT NULL,
  `Manager_Name` varchar(32) DEFAULT NULL,
  PRIMARY KEY (`Id`),
  UNIQUE KEY `Id_UNIQUE` (`Id`),
  KEY `Store_ID_idx` (`Store_ID`),
  KEY `Table_ID_idx` (`Store_Entity_Id`),
  KEY `Phone_Number_idx` (`User_Id`),
  KEY `oh_payment_type_id_fk_idx` (`Payment_Type`),
  KEY `oh_coupon_id_fk_idx` (`Used_Coupon`),
  KEY `oh_status_id_fk_idx` (`Status`),
  KEY `oh_manager_id_fk` (`Manager_Id`),
  CONSTRAINT `oh_coupon_id_fk` FOREIGN KEY (`Used_Coupon`) REFERENCES `COUPONS` (`Id`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `oh_manager_id_fk` FOREIGN KEY (`Manager_Id`) REFERENCES `MANAGER` (`Id`),
  CONSTRAINT `oh_payment_type_id_fk` FOREIGN KEY (`Payment_Type`) REFERENCES `PAYMENT_TYPES` (`Id`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `oh_status_id_fk` FOREIGN KEY (`Status`) REFERENCES `STATUS` (`Id`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `oh_store_entity_id_fk` FOREIGN KEY (`Store_Entity_Id`) REFERENCES `STORE_ENTITY` (`Id`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `oh_store_id_fk` FOREIGN KEY (`Store_ID`) REFERENCES `STORE` (`Id`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `oh_user_id_fk` FOREIGN KEY (`User_Id`) REFERENCES `USERS` (`Id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1

Thanks in advance!


Solution

  • First of all partitioning is the best use case for the issue which you faced. The reason is the storage engine which you use innodb is transaction based engine. So it records all the actions done in a log results in higher I/O resource. And for delete even it requires more resource. But partition with sub-partition is overkill because you need to clean the table data yearly once so you can use RANGE COLUMN clause with cron job which run yearly once.

    You can check your MySQL supports partition by executing the following command:

    SHOW PLUGINS;
    

    This will show a tabular data in that please check you have partition in name column with ACTIVE in the Status column.

    Partition Types:

    There are two type of partition. They are vertical partition (partition by column) which is not supported by MySQL and the second one is horizontal partition (partition by row).

    Partition Definition:

            PARTITION partition_name
            [VALUES
                {LESS THAN {(expr | value_list) | MAXVALUE}
                |
                IN (value_list)}]
            [[STORAGE] ENGINE [=] engine_name]
            [COMMENT [=] 'string' ]
            [DATA DIRECTORY [=] 'data_dir']
            [INDEX DIRECTORY [=] 'index_dir']
            [MAX_ROWS [=] max_number_of_rows]
            [MIN_ROWS [=] min_number_of_rows]
            [TABLESPACE [=] tablespace_name]
            [(subpartition_definition [, subpartition_definition] ...)]
    

    SOLUTION:

    Step 1: Alter the table schema with partioning like this (Reference link 3)

    CREATE TABLE members (
        firstname VARCHAR(25) NOT NULL,
        lastname VARCHAR(25) NOT NULL,
        username VARCHAR(16) NOT NULL,
        email VARCHAR(35),
        joined DATE NOT NULL
    )
    PARTITION BY RANGE COLUMNS(joined) (
        /*PARTITION y2013 VALUES LESS THAN ('2014-01-01'),
        PARTITION y2014 VALUES LESS THAN ('2015-01-01'),
        PARTITION y2015 VALUES LESS THAN ('2016-01-01'),
        PARTITION y2016 VALUES LESS THAN ('2017-01-01'),*/ #This are older partition it may deleted before 2017 itself
        PARTITION y2017 VALUES LESS THAN ('2018-01-01'),
        PARTITION future VALUES LESS THAN MAXVALUE
    );
    

    Step 2: You can use any programming language like PHP, Ruby, Python, Perl to create this query dynamically. Consider this is running in 01 January 2019 using cron.

    ALTER TABLE members 
        DROP PARTITION y2017;
    ALTER TABLE members 
        REORGANIZE PARTITION future INTO
            y2018 VALUES LESS THAN ('2019-01-01'),
            future VALUES LESS THAN MAXVALUE;
    

    Query to get previous years with format:

    SELECT concat('Y', YEAR(DATE_SUB(CURDATE(), INTERVAL 1 YEAR))) as prev_year
    

    change the interval to get further older year.

    Step 3: Execute the above query yearly once using any scheduler like cron. For e.g.

    @yearly /home/meenu/ubuntu/bin/annual-maintenance.sh
    

    REFERENCES:

    More On Partition-ing:

    1. Create table partitioning

    2. Partitioning official document

    3. Partitioning range

    Cron:

    1. Cron examples with explanation

    2. Running MySQL query periodically

    Big Deletes:

    1. Big deletes various alternatives

    Partition Maintenance:

    1. Partition Maintenance