MySQL и вложенный набор: медленно JOIN (не использует индекс) - PullRequest
7 голосов
/ 03 января 2012

У меня есть две таблицы:

локации:

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;

Таблица локаций реализована как вложенный набор.

Теперь, когда пользователь принадлежит какой-то местности (через некоторое местоположение), он также принадлежит всем своим предкам (локациям более высокого уровня).Мне нужен запрос, который выберет все представления, к которым принадлежат все пользователи, в представлении.

Вот моя попытка:

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)

Проблема в том, что для этого требуется слишком многовремя выполнения.

Я обратился к EXPLAIN:

+----+-------------+-------+--------+---------------------------------+---------+---------+----------------------------------+-------+----------+-----------------+
| 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 index, как я ожидаю.Я в отчаянии.

ОБНОВЛЕНИЕ: После добавления условия, как предложено @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";

Обновлено EXAPLAIN:

+----+-------------+-------+--------+-----------------------------------------+-----------------------------+---------+---------------------------------+-------+----------+-------------------------------------------------+
| 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) |
+----+-------------+-------+--------+-----------------------------------------+-----------------------------+---------+---------------------------------+-------+----------+-------------------------------------------------+

Любая помощь приветствуется.

Ответы [ 3 ]

2 голосов
/ 03 января 2012

попробуйте поэкспериментировать с форсированием индекса - http://dev.mysql.com/doc/refman/5.1/en/index-hints.html, возможно, это просто проблема оптимизатора.

2 голосов
/ 03 января 2012

Ах, это только что пришло мне в голову.

Так как вы запрашиваете все в таблице, mysql решает использовать вместо этого полное сканирование таблицы, так как считает его более эффективным.

Чтобы получить некоторое использование ключа, добавьте несколько фильтров, чтобы в любом случае ограничить поиск каждой строки во всех таблицах.

Обновление ответа:

Ваш второй запрос не имеет смысла. Вы оставляете присоединение к lca, но у вас есть фильтр, который отменяет левое соединение само по себе. Кроме того, вы ищете данные на последнем шаге запроса, то есть вам придется просмотреть все lt, lc и lca, чтобы найти ваши данные. Кроме того, у вас нет индекса с самым левым столбцом «тип» по местоположениям, поэтому вам все еще нужно полное сканирование таблицы, чтобы найти ваши данные.

Если бы у вас были примеры данных и пример того, чего вы пытаетесь достичь, вам, возможно, будет легче помочь.

0 голосов
/ 01 мая 2014

Похоже, вы хотите, чтобы родители единого результата.

По словам человека, которому приписывают определение «Вложенных наборов в SQL», Джо Селко по адресу http://www.ibase.ru/devinfo/DBMSTrees/sqltrees.html «Эта модель - естественный способ показать взрыв деталей, потому что окончательная сборка состоит из физически вложенных сборок, которые разрушаются на отдельные части. "

Другими словами, вложенные наборы используются для эффективной фильтрации дочерних элементов по произвольному количеству независимых уровней в одной коллекции. У вас есть две таблицы, но я не вижу, где свойства набора "locatings" не могут быть нормализованы в "localities"?

Если в таблице локальностей имеется столбец геометрии, не могу ли я найти местоположение one из "местоположения", а затем выбрать одну таблицу с помощью одного фильтра: parent.lft <= row.left AND parent.rgt> = 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
...