【问题标题】:MySQL nested set sub category calculation slowMySQL嵌套集子类别计算慢
【发布时间】:2017-07-11 05:09:15
【问题描述】:

我有一个带有嵌套集合模型的类别表。每行应包含其子类别的数量以及其中有多少文章,如果没有,则为“0”。

我已经搜索了 arround 并找到了两种可能的解决方案,但它们都不起作用:
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 for 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 这是一个大文件!
感谢您的帮助和提示!

【问题讨论】:

  • 最好在 dba.stackexchange 上询问?
  • 也许你是对的,但其他人问过类似的情况,所以如果有人想迁移它,请随时这样做 =)
  • 顺便提一下,INT这个词无论出现在哪里,后面的数字都是毫无意义的
  • 那我应该改用什么?最初 lft 和 rgt 列是 INT,所以我将其更改为 smallint,因此 mysql 不必使用完整的 INT-scale
  • 我会始终坚持使用 INT - 但无论哪种方式,我怀疑它对性能有很大影响。您选择的 VARCHAR id(对于组和父级)可能会略微影响性能,但可能不会很大。

标签: mysql join indexing nested


【解决方案1】:

对此的解释是什么样的?

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

【讨论】:

  • 您的查询比我的要快,但也运行了大约 10 秒。你可以在这里找到解释:pastebin.com/ZH7cTCGn
  • 我想我会尝试在 lft,rgt 上使用覆盖索引。我看不出您还能做多少其他事情 - 但其他人可能会提供更多帮助
  • 通过添加 2 列索引嵌套集进行测试,如上所示。解释仍然没有显示使用这个新键,但查询快了 1 秒。无论如何,这是一个很好的改进 80 > 10!
  • 因为没有任何其他解决方案可以“解决”这个问题,所以您的解决方案是最好的并且提供了改进 =) 谢谢!
  • @UnskilledFreak 您是否删除了 lft 上的单独索引?我不明白为什么查询没有使用 lft-rgt 索引。
【解决方案2】:

左右树是一种可爱的“教科书”技术。但是,正如您所发现的,它不适用于“现实世界”。

EXPLAIN 表明它正在扫描所有b,然后对于每个这样的行,它正在扫描所有a。这是 Order(N^2) -- 5000*5000 = 2500 万次操作。

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

优化器在寻找“中间性”方面确实不能做得更好,因为缺少一点信息:范围是否重叠。

您的任务可能可以通过切换到分层架构并在应用代码或存储例程中“遍历”树来更好地完成。

使用 MariaDB 10.2 或 MySQL 8.0,您可以编写一个“递归 CTE”,在单个但复杂的查询中遍历树。

【讨论】:

  • 是的,我认为它的主要“问题”是中间性。我已经在 php (parent->groups 等) 中测试了一个递归函数,但这甚至更慢。也许你可以提供一个示例查询吗?
  • 范围怎么可能重叠!?!?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-09-10
  • 1970-01-01
  • 1970-01-01
  • 2012-02-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-27
相关资源
最近更新 更多