【问题标题】:MySQL & nested set: slow JOIN (not using index)MySQL & 嵌套集:慢 JOIN(不使用索引)
【发布时间】:2012-02-01 18:13:47
【问题描述】:

我有两张桌子:

地区:

CREATE TABLE `localities` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) NOT NULL,
  `type` varchar(30) NOT NULL,
  `parent_id` int(11) DEFAULT NULL,
  `lft` int(11) DEFAULT NULL,
  `rgt` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `index_localities_on_parent_id_and_type` (`parent_id`,`type`),
  KEY `index_localities_on_name` (`name`),
  KEY `index_localities_on_lft_and_rgt` (`lft`,`rgt`)
) ENGINE=InnoDB;

地点:

CREATE TABLE `locatings` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `localizable_id` int(11) DEFAULT NULL,
  `localizable_type` varchar(255) DEFAULT NULL,
  `locality_id` int(11) NOT NULL,
  `category` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `index_locatings_on_locality_id` (`locality_id`),
  KEY `localizable_and_category_index` (`localizable_type`,`localizable_id`,`category`),
  KEY `index_locatings_on_category` (`category`)
) ENGINE=InnoDB;

localities 表被实现为嵌套集。

现在,当用户属于某个地方(通过某个定位)时,他也属于它的所有祖先(更高级别的地方)。我需要一个查询,它将所有用户所属的所有地点都选择到一个视图中。

这是我的尝试:

select distinct lca.*, lt.localizable_type, lt.localizable_id 
from locatings lt
join localities lc on lc.id = lt.locality_id
left join localities lca on (lca.lft <= lc.lft and lca.rgt >= lc.rgt)

这里的问题是执行时间太长了。

我咨询了解释:

+----+-------------+-------+--------+---------------------------------+---------+---------+----------------------------------+-------+----------+-----------------+
| id | select_type | table | type   | possible_keys                   | key     | key_len | ref                              | rows  | filtered | Extra           |
+----+-------------+-------+--------+---------------------------------+---------+---------+----------------------------------+-------+----------+-----------------+
|  1 | SIMPLE      | lt    | ALL    | index_locatings_on_locality_id  | NULL    | NULL    | NULL                             |  4926 |   100.00 | Using temporary |
|  1 | SIMPLE      | lc    | eq_ref | PRIMARY                         | PRIMARY | 4       | bzzik_development.lt.locality_id |     1 |   100.00 |                 |
|  1 | SIMPLE      | lca   | ALL    | index_localities_on_lft_and_rgt | NULL    | NULL    | NULL                             | 11439 |   100.00 |                 |
+----+-------------+-------+--------+---------------------------------+---------+---------+----------------------------------+-------+----------+-----------------+
3 rows in set, 1 warning (0.00 sec)

最后一个连接显然没有像我期望的那样使用 lft, rgt 索引。我很绝望。

更新: 按照@cairnz 的建议添加条件后,查询仍然需要太多时间来处理。

更新 2:列名而不是星号

更新查询:

SELECT DISTINCT lca.id, lt.`localizable_id`, lt.`localizable_type` 
FROM locatings lt FORCE INDEX(index_locatings_on_category)
JOIN localities lc
    ON lc.id = lt.locality_id
INNER JOIN localities lca
    ON lca.lft <= lc.lft AND lca.rgt >= lc.rgt
WHERE lt.`category` != "Unknown";

更新解释:

+----+-------------+-------+--------+-----------------------------------------+-----------------------------+---------+---------------------------------+-------+----------+-------------------------------------------------+
| id | select_type | table | type   | possible_keys                           | key                         | key_len | ref                             | rows  | filtered | Extra                                           |
+----+-------------+-------+--------+-----------------------------------------+-----------------------------+---------+---------------------------------+-------+----------+-------------------------------------------------+
|  1 | SIMPLE      | lt    | range  | index_locatings_on_category             | index_locatings_on_category | 153     | NULL                            |  2545 |   100.00 | Using where; Using temporary                    |
|  1 | SIMPLE      | lc    | eq_ref | PRIMARY,index_localities_on_lft_and_rgt | PRIMARY                     | 4       | bzzik_production.lt.locality_id |     1 |   100.00 |                                                 |
|  1 | SIMPLE      | lca   | ALL    | index_localities_on_lft_and_rgt         | NULL                        | NULL    | NULL                            | 11570 |   100.00 | Range checked for each record (index map: 0x10) |
+----+-------------+-------+--------+-----------------------------------------+-----------------------------+---------+---------------------------------+-------+----------+-------------------------------------------------+

任何帮助表示赞赏。

【问题讨论】:

  • 你试过不在同一个索引中包含 lft 和 rft 吗? (一个用于 lft,一个用于 rft)
  • 根据您的更新更新答案。

标签: mysql sql performance indexing nested-sets


【解决方案1】:

啊,我突然想到了。

由于您要查询表中的所有内容,mysql 决定改为使用全表扫描,因为它认为这样更有效。

为了获得一些关键用途,添加一些过滤器来限制查找所有表中的每一行。

更新答案:

您的第二个查询没有意义。你离开加入 lca 但你有一个过滤器,这本身否定了左加入。此外,您正在查询的最后一步查找数据,这意味着您必须查看所有 lt、lc 和 lca 才能找到您的数据。此外,您在位置上没有最左侧列“类型”的索引,因此您仍然需要进行全表扫描才能找到您的数据。

如果您有一些示例数据和示例来说明您正在努力实现的目标,那么可能会更容易提供帮助。

【讨论】:

  • 谢谢,查询要快得多,但仍然需要太多。我用新的查询和解释更新了我的问题。
  • 抱歉,这可能是个愚蠢的问题,但是你添加过滤器是什么意思?
  • 您的查询必须处理 lt 表,加入 lc,加入 lca。您拥有的过滤器位于 lca 中,即查询的最后一个“步骤”。然后它可以扫描 lca 表以查找匹配 type != "Unknown" 的行,但为了达到这一点,它已经必须读取 lt 和 lc,如果这有意义的话。你也有那个表的左连接,这意味着你可以在那里有 NULL 记录,但是你在 WHERE 子句中过滤它,删除所有 NULL 记录(等于内部连接)。也许您的意思是您的过滤器位于 lc 或 lt 上。如果您对 lt 表进行过滤,则它在 lc 和 lca 中要扫描的行数更少。
  • 你的意思是这样的... WHERE lt.category != "Unknown";我们在 localities 表中有大约 12 000 条记录,在 locatings 中有大约 4 000 条记录,查询需要半分钟来处理。这不正常,是吗?
  • 听起来更像。如果您现在在 lt.category 上有一个索引,它应该会加快搜索速度。然后更有可能在 lc 和 lca 表上使用以下索引。当您添加此类过滤器和索引时,EXPLAIN 会说什么。请记住,您不能使用 KEY localizable_and_category_index (localizable_type,localizable_id,category) 仅搜索类别,因为它不是索引中最左侧的列。
【解决方案2】:

尝试使用强制索引 - http://dev.mysql.com/doc/refman/5.1/en/index-hints.html,可能只是优化器问题。

【讨论】:

  • 还将DISTINCT 替换为GROUP BY
  • @FrancisAvila 用 GROUP BY 替换 DISTINCT 没有任何区别。
【解决方案3】:

您似乎想要单一结果的父母。

根据在 SQL 中定义嵌套集的功劳,Joe Celkohttp://www.ibase.ru/devinfo/DBMSTrees/sqltrees.html“这个模型是展示零件爆炸的自然方式,因为最终装配是由分解成单独零件的物理嵌套装配组成的。”

换句话说,嵌套集用于有效地将子级过滤到单个集合中的任意数量的独立级别。你有两张表,但我看不出集合“定位”的属性在哪里不能反规范化为“地点”?

如果位置表有一个几何列,我能否从“定位”中找到 一个 位置,然后使用单个过滤器在一个表上进行选择:parent.lft = row.rgt ?

更新

在这个答案https://stackoverflow.com/a/1743952/3018894 中,有一个来自http://explainextended.com/2009/09/29/adjacency-list-vs-nested-sets-mysql/ 的示例,其中以下示例将所有祖先获取到任意深度100000:

SELECT  hp.id, hp.parent, hp.lft, hp.rgt, hp.data
FROM    (
    SELECT  @r AS _id,
            @level := @level + 1 AS level,
            (
            SELECT  @r := NULLIF(parent, 0)
            FROM    t_hierarchy hn
            WHERE   id = _id
            )
    FROM    (
            SELECT  @r := 1000000,
                    @level := 0
            ) vars,
            t_hierarchy hc
    WHERE   @r IS NOT NULL
    ) hc
JOIN    t_hierarchy hp
ON      hp.id = hc._id
ORDER BY
    level DESC

【讨论】:

    猜你喜欢
    • 2016-12-04
    • 1970-01-01
    • 2016-11-18
    • 2011-09-14
    • 2022-07-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-11
    相关资源
    最近更新 更多