2013-09-18 3 views
4

У меня есть приложение, которое должно обновлять узлы в иерархической структуре, вверх от определенного узла, чей идентификатор известен. Я использую следующее заявление MySQL, чтобы сделать это:Почему этот запрос update-with-join mysql настолько медленный?

update node as A 
join node as B 
    on A.lft<=B.lft and A.rgt>=B.rgt 
set A.count=A.count+1 where B.id=? 

Таблица имеет первичный ключ идентификатор, а индексы на МВТ и РТГ. Заявление работает, но я обнаружил, что у него были проблемы с производительностью. Если посмотреть на результаты EXPLAIN для соответствующего оператора select, я увидел, что количество строк, проверенных для таблицы «B», было очень большим (возможно, всей таблицей).

можно легко вытащить запрос распадается на два отдельных из них:

select lft, rgt from node where id=? 
LFT=result.lft 
RGT=result.rgt 
update node set count=count+1 where lft<=LFT and rgt>=RGT 

Но почему первоначальное заявление не так, как ожидалось, и как мне нужно переформулировать его работать лучше?

По желанию, вот сокращенный вариант таблицы создания:

CREATE TABLE `node` ( 
`id` int(11) NOT NULL auto_increment, 
`name` varchar(255) NOT NULL, 
`lft` decimal(64,0) NOT NULL, 
`rgt` decimal(64,0) NOT NULL, 
`count` int(11) NOT NULL default '0', 
PRIMARY KEY (`id`), 
KEY `name` (`name`), 
KEY `location` (`location`(255)), 
KEY `lft` (`lft`), 
KEY `rgt` (`rgt`), 
) ENGINE=InnoDB 

Я не пытался добавить составной индекс (на самом деле, у меня нет уровня доступа, необходимый, чтобы сделать это на месте); но я не понимаю, как это поможет, пытаясь понять, как механизм базы данных попытается решить двойственное неравенство.

+0

Это поможет, если вам может опубликовать как определения таблиц, так и объяснение ... –

+1

Можете ли вы опубликовать оператор 'CREATE TABLE' и вывод' EXPLAIN? –

+0

BTW: условие 'A.lft <= B.lft и A.rgt> = B.rgt' истинно для 'A == B'. Это намеченное поведение? – wildplasser

ответ

7

Вы можете «заставлять» (по крайней мере, до 5.5, версия 5.6 имеет улучшенные алгоритмы оптимизации, которые могут сделать избыточную перезапись). MySQL сначала оценивает условия на таблице B, принимая первую часть вашего разделения в подзапрос, а затем использовать это в качестве производной таблицы и присоединение к таблице а:

UPDATE node AS a 
    JOIN 
    (SELECT lft, rgt 
     FROM node 
     WHERE id = ? 
    ) AS b 
    ON a.lft <= b.lft 
    AND a.rgt >= b.rgt 
SET 
    a.count = a.count + 1 ; 

Эффективность прежнему будет зависеть от какой из двух индексов выбирается так, чтобы ограничить строки, которые будут обновлены. Все еще после использования любого из этих двух индексов, поиск таблицы необходим для проверки другого столбца. Поэтому я предлагаю вам добавить составной индекс на (lft, rgt) и один на (rgt, lft), поэтому для определения того, какие строки должны обновляться, используется только один индекс.

Я полагаю, что вы используете вложенный набор, и эффективность этого обновления не будет большой в большой таблице, так как запрос имеет два условия диапазона и ограничивает эффективность индексов B-дерева.

+0

Спасибо за ваш ответ. Я попытался проделать это через объяснение, и это не выглядит многообещающим. С извинениями за дерьмовое форматирование вывод объяснения: 1 | PRIMARY || system | null | null | null | null | 1 || 1 | PRIMARY | a | ALL | lft, rgt | null | null | null | 999331 | Использование где | 2 | DERIVED | node | const | PRIMARY | PRIMARY | 4 || 1 || - все еще показывает большое количество строк для проверки. – plantrob

+0

С другой стороны, простой запрос «выберите * из узла, где lft <=? and rgt> =?" дает следующее объяснение: 1 | SIMPLE | node | range | lft, rgt | lft | 29 | null | 8 | Использование где | – plantrob

+0

Можете ли вы отредактировать вопрос и обновить информацию? (Оператор CREATE TABLE, движок и т. Д.)? И вы добавили составной индекс (ы)? –

3

Это всего лишь предложение; Я не знаю, будет ли это работать.

Проблема с вашим запросом заключается в том, что у вас есть неравенства на двух столбцах. Это очень затрудняет использование индексов для обоих из них, что в свою очередь делает join очень неэффективным. Эта идея состоит в том, чтобы сделать два объединения, по одному для каждой стороны неравенства, а затем включить id в условия on. Таким образом, только узлы, которые проходят как проникнет через:

UPDATE node a JOIN 
     (SELECT lft, rgt 
     FROM node 
     WHERE id = ? 
    ) l 
     ON a.lft <= l.lft join 
     (SELECT lft, rgt 
     FROM node 
     WHERE id = ? 
    ) r 
     on a.rgt >= r.rgt 
    SET a.count = a.count + 1 ; 

Как я уже говорил, я не знаю, если это будет работать. Но вы должны иметь возможность легко проверить explain на запрос, чтобы увидеть, использует ли план индексы для обоих неравенств.

+0

Необходим ли 'l.id = r.id'? Потому что вы уже положили их равными '?'. – Minoru

+0

@LucasHarada. , , Я думаю, вы правы. Два подзапроса должны получать одну и ту же строку. Я просто пытаюсь разбить его на два явных объединения, чтобы можно было использовать оба индекса. –

4

Я думаю, ваша самая большая проблема с производительностью - это ненужный JOIN, который вы используете. Вы можете сделать это, просто сделав два небольших подзапроса, вместо того, чтобы присоединяться к двум большим таблицам.

Вот пример:

UPDATE node AS a 
SET a.count = a.count+1 
WHERE a.lft <= (SELECT lft FROM node WHERE id = ?) 
AND a.rgt >= (SELECT rgt FROM node WHERE id = ?) 
+0

Это точно такой же запрос, как и удаленный ответ @Guerra. Мне не нравятся два скалярных подзапроса, но mysql, похоже, работает таинственным образом ... – wildplasser

+0

Да, это то же самое от @Guerra. Я не знаю, почему он удалил сообщение, потому что запрос имеет лучшую производительность, чем вопрос; он должен просто объяснить, что псевдонимы не снижают производительность. – Minoru

+1

У него была странная теория о том, что использование корреляционных имен/псевдонимов вызвало медленный результат запроса. – wildplasser

1

Я знаю, что MySQL имеет проблемы со ссылкой на таблицу обновляется, но для меня очевидным решением было бы:

update node A 
set A.count=A.count+1 
WHERE EXISTS (
    SELECT * 
    FROM node B 
    WHERE B.id=? 
    AND A.lft<=B.lft and A.rgt>=B.rgt 
    ); 
Смежные вопросы