How to repair a corrupted MPTT tree (nested set) in the database using SQL?

前端 未结 4 608
我在风中等你
我在风中等你 2020-12-23 22:12

I have an MPTT tree of over 100,000 records stored in MySQL using lft, rght and parent_id columns. Now the left/right values became co

4条回答
  •  南笙
    南笙 (楼主)
    2020-12-23 22:56

    You are rescue me!!! I use mixed tree model, so when the day is coming, my tree (30000+) was corrupted. I learn from both of your tech, but is not recovery, just fully rebuild with lost all of sorting and reverse tree... I think, need to keep in mind older cat_left.... Just for possible integrity. So, it maybe looks like...

    DROP PROCEDURE IF EXISTS tree_recover;
    
    DELIMITER |
    
    CREATE PROCEDURE tree_recover ()
    MODIFIES SQL DATA
    BEGIN
    
        DECLARE currentId, currentParentId  CHAR(36);
        DECLARE currentLeft                 INT;
        DECLARE startId                     INT DEFAULT 1;
    
        # Determines the max size for MEMORY tables.
        SET max_heap_table_size = 1024 * 1024 * 512;
    
        START TRANSACTION;
    
        # Temporary MEMORY table to do all the heavy lifting in,
        # otherwise performance is simply abysmal.
        DROP TABLE IF EXISTS `tmp_cat`;
        CREATE TABLE `tmp_cat` (
            `cat_id`        char(36) NOT NULL DEFAULT '',
            `cat_parent` char(36)          DEFAULT NULL,
            `cat_left`       int(11)  unsigned DEFAULT NULL,
            `cat_right`      int(11)  unsigned DEFAULT NULL,
            `cat_left_old`   int(11)  unsigned DEFAULT NULL,
            PRIMARY KEY      (`cat_id`),
            INDEX USING HASH (`cat_parent`),
            INDEX USING HASH (`cat_left`),
            INDEX USING HASH (`cat_right`),
        INDEX USING HASH (`cat_left_old`)
        ) ENGINE = MEMORY
        SELECT `cat_id`,
               `cat_parent`,
               `cat_left`,
               `cat_right`,
           `cat_left` as cat_left_old
        FROM   `catalog`;
    
        # Leveling the playing field.
        UPDATE  `tmp_cat`
        SET     `cat_left`  = NULL,
                `cat_right` = NULL;
    
        # Establishing starting numbers for all root elements.
        WHILE EXISTS (SELECT * FROM `tmp_cat` WHERE `cat_parent` IS NULL AND `cat_left` IS NULL AND `cat_right` IS NULL ORDER BY cat_left_old LIMIT 1) DO
    
            UPDATE `tmp_cat`
            SET    `cat_left`  = startId,
                   `cat_right` = startId + 1
            WHERE  `cat_parent` IS NULL
              AND  `cat_left`       IS NULL
              AND  `cat_right`      IS NULL
            LIMIT  1;
    
            SET startId = startId + 2;
    
        END WHILE;
    
        # Switching the indexes for the cat_left/rght columns to B-Trees to speed up the next section, which uses range queries.
        DROP INDEX `cat_left`  ON `tmp_cat`;
        DROP INDEX `cat_right` ON `tmp_cat`;
        DROP INDEX `cat_left_old` ON `tmp_cat`;
    
        CREATE INDEX `cat_left`  USING BTREE ON `tmp_cat` (`cat_left`);
        CREATE INDEX `cat_right` USING BTREE ON `tmp_cat` (`cat_right`);
        CREATE INDEX `cat_left_old` USING BTREE ON `tmp_cat` (`cat_left_old`);
    
        # Numbering all child elements
        WHILE EXISTS (SELECT * FROM `tmp_cat` WHERE `cat_left` IS NULL ORDER BY cat_left_old LIMIT 1) DO
    
            # Picking an unprocessed element which has a processed parent.
            SELECT     `tmp_cat`.`cat_id`
              INTO     currentId
            FROM       `tmp_cat`
            INNER JOIN `tmp_cat` AS `parents`
                    ON `tmp_cat`.`cat_parent` = `parents`.`cat_id`
            WHERE      `tmp_cat`.`cat_left` IS NULL
              AND      `parents`.`cat_left`  IS NOT NULL
        ORDER BY `tmp_cat`.cat_left_old DESC
            LIMIT      1;
    
            # Finding the element's parent.
            SELECT  `cat_parent`
              INTO  currentParentId
            FROM    `tmp_cat`
            WHERE   `cat_id` = currentId;
    
            # Finding the parent's cat_left value.
            SELECT  `cat_left`
              INTO  currentLeft
            FROM    `tmp_cat`
            WHERE   `cat_id` = currentParentId;
    
            # Shifting all elements to the right of the current element 2 to the right.
            UPDATE `tmp_cat`
            SET    `cat_right` = `cat_right` + 2
            WHERE  `cat_right` > currentLeft;
    
            UPDATE `tmp_cat`
            SET    `cat_left` = `cat_left` + 2
            WHERE  `cat_left` > currentLeft;
    
            # Setting cat_left and rght values for current element.
            UPDATE `tmp_cat`
            SET    `cat_left`  = currentLeft + 1,
                   `cat_right` = currentLeft + 2
            WHERE  `cat_id`   = currentId;
    
        END WHILE;
    
        # Writing calculated values back to physical table.
        UPDATE `catalog`, `tmp_cat`
        SET    `catalog`.`cat_left`  = `tmp_cat`.`cat_left`,
               `catalog`.`cat_right` = `tmp_cat`.`cat_right`
        WHERE  `catalog`.`cat_id`   = `tmp_cat`.`cat_id`;
    
        COMMIT;
    
        DROP TABLE IF EXISTS `tmp_cat`;
    
    END|
    

提交回复
热议问题