MySQL嵌套集子类计算速度慢

时间:2017-02-21 14:11:55

标签: mysql join indexing nested

我有一个带有嵌套集模型的类别表。每行应包含其子类别的数量以及这些中的文章数量或者' 0' 0如果没有。

我已经搜查了周围并发现了两种可能的解决方案,但它们没有任何作用:
MySQL & nested set: slow JOIN (not using index)
Why isn't MySQL using any of these possible keys?

创建表类别:

CREATE TABLE `categories` (
  `GROUP_ID` varchar(255) CHARACTER SET utf8 NOT NULL,
  `GROUP_NAME` varchar(255) CHARACTER SET utf8 NOT NULL,
  `PARENT_ID` varchar(255) CHARACTER SET utf8 NOT NULL,
  `TYPE` enum('root','node','leaf') CHARACTER SET utf8 NOT NULL DEFAULT 'node',
  `LEVEL` tinyint(2) NOT NULL DEFAULT '0',
  `GROUP_ORDER` int(11) NOT NULL,
  `GROUP_DESCRIPTION` text CHARACTER SET utf8 NOT NULL,
  `total_articles` int(11) unsigned NOT NULL DEFAULT '0',
  `total_cats` int(11) unsigned NOT NULL DEFAULT '0',
  `lft` smallint(5) unsigned NOT NULL DEFAULT '0',
  `rgt` smallint(5) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`GROUP_ID`),
  KEY `PARENT_ID` (`PARENT_ID`),
  KEY `lft` (`lft`),
  KEY `rgt` (`rgt`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

total_cats是行树中的子类别数量 以下查询将完全符合我的要求:所有子类别和文章计数。 但它很慢。在~5000个类别和约40000篇文章上执行需要超过80秒。 total_articles的计算已由另一个脚本完成。 (如果没有任何文章,则0

的所有行都应保留total_articles

查询:

SELECT a.GROUP_ID,a.PARENT_ID,COUNT(b.GROUP_ID) as total_cats,(
   SELECT SUM(c.total_articles)
   FROM categories c
   WHERE c.PARENT_ID = a.GROUP_ID) as total_articles
FROM categories as b
   INNER JOIN categories as a
     ON a.lft < b.lft AND a.rgt > b.rgt
GROUP BY a.GROUP_ID

结果如下:

+-------------------------------------------+-------------------------------------+------------+----------------+
| GROUP_ID                                  | PARENT_ID                           | total_cats | total_articles |
+-------------------------------------------+-------------------------------------+------------+----------------+
| 69_69_1                                   | 69_69_0                             |       4252 |              0 |
| 69_69_Abfall__Wertstoffsammler___zubehoer | 69_69_NWEAB290h001                  |          5 |             20 |
| 69_69_Abisolierzangen                     | 69_69_NWAAA458h001                  |          4 |             56 |
| 69_69_Abzieher_2                          | 69_69_NWAAB944h001                  |         23 |            476 |
| 69_69_Abziehvorrichtung                   | 69_69_Abzieher_2                    |          3 |             18 |
| 69_69_Aexte                               | 69_69_NWEAA615h001                  |          6 |             45 |
| 69_69_Alarmgeraete_Melder                 | 69_69_Sicherungstechnik__Heimschutz |          3 |              4 |
| 69_69_Allgemeiner_Industriebedarf         | 69_69_Industrieausruestung          |          8 |             21 |
| 69_69_Allgemeines_Schweisszubehoer        | 69_69_NWEAB113h001                  |         27 |             97 |
| 69_69_Anker__Befestigungstechnik__1       | 69_69_Befestigungstechnik           |          5 |            163 |

解释是否有帮助:

+----+--------------------+-------+------+---------------+-----------+---------+------+------+------------------------------------------------+
| id | select_type        | table | type | possible_keys | key       | key_len | ref  | rows | Extra                                          |
+----+--------------------+-------+------+---------------+-----------+---------+------+------+------------------------------------------------+
|  1 | PRIMARY            | b     | ALL  | lft,rgt       | NULL      | NULL    | NULL | 4253 | Using temporary; Using filesort                |
|  1 | PRIMARY            | a     | ALL  | lft,rgt       | NULL      | NULL    | NULL | 4253 | Range checked for each record (index map: 0xC) |
|  2 | DEPENDENT SUBQUERY | c     | ref  | PARENT_ID     | PARENT_ID | 767     | func |    7 | NULL                                           |
+----+--------------------+-------+------+---------------+-----------+---------+------+------+------------------------------------------------+

如您所见,它不使用索引。如果我将FORCE INDEX (lft,rgt)放在查询执行的JOIN旁边,但没有任何变化。还尝试在lft和right两列上添加索引:

ALTER TABLE `categories` ADD INDEX `nestedset` (`lft`, `rgt`);

但这根本没有帮助。查询仍然很慢。

有趣的是:如果类别表只填充少量行,则查询速度非常快,例如260.但如果它达到1000+,它将变得越来越慢。

包含~4000个类别的示例数据:http://pastebin.com/BsViwFM5 是一个大文件!
感谢您的帮助和提示!

2 个答案:

答案 0 :(得分:1)

EXPLAIN对此有什么看法?

SELECT a.GROUP_ID
     , a.PARENT_ID
     , COUNT(b.GROUP_ID) total_cats
     , c.total_articles
  FROM categories b
  JOIN categories a
    ON a.lft < b.lft 
   AND a.rgt > b.rgt
  JOIN 
     ( SELECT parent_id 
           , SUM(total_articles) total_articles
        FROM categories 
       GROUP 
          BY parent_id
     ) c
    ON c.parent_id = a.GROUP_ID
 GROUP 
    BY a.GROUP_ID

答案 1 :(得分:0)

左右树是一本可爱的教科书&#34;技术。但是,正如你所发现的那样,它并不适用于现实世界。

EXPLAIN显示它扫描了所有b,然后对于每个这样的行,它会扫描所有a。该命令(N ^ 2) - 5000 * 5000 = 2500万次操作。

实际上,这个相对较新的操作(Range checked for each record (index map: 0xC))意味着它并不是那么糟糕。

优化工具在找到“介于两者之间”方面确实做得更好。因为缺少一些信息:范围是否重叠。

通过切换到分层架构,可以更好地实现 任务&#34; walk&#34;树,应用程序代码或存储例程。

使用MariaDB 10.2或MySQL 8.0,您可以编写一个&#34;递归CTE&#34;在一个单一但复杂的查询中走树。