我有一个带有嵌套集模型的类别表。每行应包含其子类别的数量以及这些中的文章数量或者' 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 是一个大文件!
感谢您的帮助和提示!
答案 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;在一个单一但复杂的查询中走树。