Тормоза при двойном связывании таблицы

Slon747

Новичок
Тормоза при двойном связывании таблицы

MySQL 5.0.77

Есть таблица номенклатуры (items) и таблица цен (prices).
Цены хранятся в разрезе характеристик.
Чтобы получить номенклатуру с ценами двух типов (без учета характеристик), я дважды связываю эти таблицы, после чего группирую по items.Id.
Поля, по которым идет связывание и отбор, проиндексированы.
Код:
SELECT
  items.Id, MAX(prices.Price), MAX(prices_2.Price)
FROM
  items
  INNER JOIN prices ON prices.Item = items.Id AND prices.PriceType = 1
  INNER JOIN prices prices_2 ON prices_2.Item = items.Id AND prices_2.PriceType = 2
WHERE
  items.Parent = '113'
GROUP BY
  items.Id
При двух соединениях запрос выполняется в 25 раз дольше, чем при одном.
В чем может быть дело и как это исправить?



Код:
CREATE TABLE items( 
  Id VARCHAR(32) DEFAULT '0', 
  Parent INT(11) NOT NULL, 
  PRIMARY KEY (Id),
  INDEX iParent (Parent)
) 


CREATE TABLE prices( 
  Id INT(6) UNSIGNED NOT NULL AUTO_INCREMENT, 
  PriceType INT(4) NOT NULL DEFAULT 0, 
  Item CHAR(32) NOT NULL DEFAULT '0', 
  Description CHAR(32) NOT NULL,
  Price DOUBLE (9, 2) NOT NULL DEFAULT 0.00, 
  PRIMARY KEY (Id),
  INDEX IX_prices_Item (Item)
)
 

svetasmirnova

маленький монстрик
Скорее всего locking issue, посмотри в третьем соединении вывод SHOW PROCESSLIST.
 

zerkms

TDD infected
Команда форума
Поля, по которым идет связывание и отбор, проиндексированы.
да ну?

где составной индекс PriceType + Item в табле prices?

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

если не поможет - то я посоветовал бы вывернуть запрос и выполнять его относительно prices, а не items. навскидку - вот так:
[sql]
SELECT MAX(price), item, priceType
FROM prices
WHERE item IN (SELECT id FROM items WHERE parent = 113) AND priceType IN (1,2)
GROUP BY item, priceType
[/sql]
 

Slon747

Новичок
Автор оригинала: svetasmirnova
Скорее всего locking issue, посмотри в третьем соединении вывод SHOW PROCESSLIST.
Я не понял. А где у меня третье связывание?
Пакетно выполнил свой запрос и следом "SHOW PROCESSLIST;".
Он мне вернул:
PHP:
=================================================================================================================================
|      Id       |     User      |     Host      |      db       |    Command    |     Time      |     State     |     Info      |
=================================================================================================================================
|      116      |     User      |my_host:       |    shopdb     |     Sleep     |      138      |               |     null      |
|               |               |     1637      |               |               |               |               |               |
---------------------------------------------------------------------------------------------------------------------------------
|      117      |     User      |my_host:       |    shopdb     |     Query     |       0       |     null      |     SHOW      |
|               |               |     1638      |               |               |               |               |  PROCESSLIST  |
---------------------------------------------------------------------------------------------------------------------------------

Автор оригинала: zerkms
да ну?
где составной индекс PriceType + Item в табле prices?
добавляем индекс, убираем кавычки вокруг 113, показываем после этого EXPLAIN.
Добавил составной индекс по Item+PriceType. Хотя у меня и до этого был уникальный индекс по Item+Description+PriceType (каюсь, решил упростить описание таблицы для наглядности).
Полное описание таблицы prices:
PHP:
CREATE TABLE shopdb.prices(
  Id INT(6) UNSIGNED NOT NULL,
  PriceType INT(4) NOT NULL DEFAULT 0,
  Item CHAR(32) NOT NULL DEFAULT '0',
  Description CHAR(32) NOT NULL,
  Price DOUBLE (9, 2) NOT NULL DEFAULT 0.00,
  PRIMARY KEY (Id),
  UNIQUE INDEX iItemPriceDescr (Item, PriceType, Description),
  INDEX IX_prices (Item, PriceType),
  INDEX IX_prices_Item (Item)
)
ENGINE = MYISAM
Убрал кавычки и выполнил с EXPLAIN:

PHP:
=================================================================================================================================================================
|      id       |  select_type  |     table     |     type      | possible_keys |      key      |    key_len    |      ref      |     rows      |     Extra     |
=================================================================================================================================================================
|       1       |    SIMPLE     |     items     |      ref      |PRIMARY,iParent|    iParent    |       5       |     const     |      506      | Using where;  |
|               |               |               |               |               |               |               |               |               |    Using      |
|               |               |               |               |               |               |               |               |               |  temporary;   |
|               |               |               |               |               |               |               |               |               |Using filesort |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
|       1       |    SIMPLE     |    prices     |      ref      |iItemPriceDescr|   IX_prices   |      36       |shopdb.items.Id|       1       |  Using where  |
|               |               |               |               |,IX_prices_Item|               |               |    ,const     |               |               |
|               |               |               |               |  ,IX_prices   |               |               |               |               |               |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
Автор оригинала: zerkms
если не поможет - то я посоветовал бы вывернуть запрос и выполнять его относительно prices, а не items. навскидку - вот так:
Так выполняется еще в несколько раз дольше :(
 

zerkms

TDD infected
Команда форума
UNIQUE INDEX iItemPriceDescr (Item, PriceType, Description),
INDEX IX_prices (Item, PriceType),
если есть первый индекс - то второй лишний уже.

ну вообще план вполне ожидаемый для такой ситуации - лучше уже не будет. сколько записей в табле и как долго запрос выполняется?

плюс покажи плиз план от моего варианта запроса? (он данные похожие выдаёт?)
 

Slon747

Новичок
Автор оригинала: zerkms
Сколько записей в табле и как долго запрос выполняется?
Размер items: 100.000 строк
Размер prices: 310.000 строк

Реальный запрос посложнее. Я упрощал запрос для поиска узкого места.
Запрос, показанный мной выполняется 0.7 сек, а если убрать одно из двух связываний, то 0.03 сек.

В реальности еще соединяю с остатками и сортирую по наименованию.
В результате, если отбираются элементы по items.Parent, у которых много характеристик (много строк таблицы prices с одинаковымм Item и
разными Description), то запрос выполняется 3-5 сек.

Автор оригинала: zerkms
плюс покажи плиз план от моего варианта запроса? (он данные похожие выдаёт?)
PHP:
===================================================================================================================================================================================================================
|         id         |    select_type     |       table        |        type        |   possible_keys    |        key         |      key_len       |        ref         |        rows        |       Extra        |
===================================================================================================================================================================================================================
|         1          |      PRIMARY       |       prices       |        ALL         |        null        |        null        |        null        |        null        |       311963       |Using where; Using  |
|                    |                    |                    |                    |                    |                    |                    |                    |                    | temporary; Using   |
|                    |                    |                    |                    |                    |                    |                    |                    |                    |      filesort      |
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|         2          | DEPENDENT SUBQUERY |       items        |  unique_subquery   |  PRIMARY,iParent   |      PRIMARY       |         34         |        func        |         1          |    Using where     |
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 

Gas

может по одной?
я бы сначала попробовал переписать запрос zerkms'а на такой:

[SQL]
SELECT MAX( price ) , item, priceType
FROM
(
SELECT id
FROM items
WHERE parent = 113
) AS i
JOIN prices AS p ON i.id = p.item
WHERE priceType IN ( 1, 2 )
GROUP BY item
[/SQL]

и попробовал бы вариант с union'ом: первый запрос по priceType =1 UNION ALL второй запрос по priceType =2
 

Wicked

Новичок
почему бы не выгребать двумя запросами?

а даже если и одним, то имхо нужно следующее:
1) привести типы полей items.id и prices.item к одному и тому же. Лучше всего, если там на практике хранятся числа - то переделать оба в integer.
2) items: составной индекс на (parent, id)
3) prices: индекс на (item) или на (item, price_type) или на (item, price_type, price) - не принципиально который именно
4) запрос сделать в виде
Код:
SELECT
  items.Id, MAX(prices.Price), MAX(prices_2.Price)
FROM
  items
  INNER JOIN prices ON prices.Item = items.Id AND prices.PriceType = 1
  INNER JOIN prices prices_2 ON prices_2.Item = items.Id AND prices_2.PriceType = 2
WHERE
  items.Parent = '113'
GROUP BY
  items.Parent, items.Id /* <- тут */
explain и замеры времени в студию, пожалуйста

-~{}~ 19.05.10 13:33:

4 fun: SELECT items.Id, MAX(if(prices.PriceType = 1, prices.Price, 0)), MAX(if(prices.PriceType = 2, prices.Price, 0)) и всего лишь один джоин :)
 

Slon747

Новичок
Автор оригинала: Gas
я бы сначала попробовал переписать запрос zerkms'а на такой:
и попробовал бы вариант с union'ом: первый запрос по priceType =1 UNION ALL второй запрос по priceType =2
Можно. Но у меня этот запрос используется во многих местах с небольшими изменениями. Не хотелось бы усложнять, т.к. он еще и собирается в зависимости от ситуации.

Автор оригинала: Wicked
почему бы не выгребать двумя запросами?
По этой же причине. Хочется попроще.

Автор оригинала: Wicked
1) привести типы полей items.id и prices.item к одному и тому же. Лучше всего, если там на практике хранятся числа - то переделать оба в integer.
Они оба CHAR (гуиды) и переделать в INT не могу.

Автор оригинала: Wicked
2) items: составной индекс на (parent, id)
Создал. Действительно, скорость запроса выросла в 2 раза. Это уже лучше :).
Что интересно, если индекс (id, parent), то это ничего не дает.

Автор оригинала: Wicked
4) запрос сделать в виде
Никакой разницы в скорости нет, так что explain я не вывожу.

Автор оригинала: Wicked
4 fun: SELECT items.Id, MAX(if(prices.PriceType = 1, prices.Price, 0)), MAX(if(prices.PriceType = 2, prices.Price, 0)) и всего лишь один джоин
Вот так сделал:
Код:
 SELECT
   items.Id, MAX(IF(prices.PriceType = 1, prices.Price, 0)), MAX(IF(prices.PriceType = 2, prices.Price, 0))
 FROM
   items
   INNER JOIN prices ON prices.Item = items.Id AND (prices.PriceType = 1 OR prices.PriceType = 2)
 WHERE
   items.Parent = 113
 GROUP BY
   items.id
В результате скорость выросла в еще в 7.5 раз.
Спасибо. Буду думать.
 

Wicked

Новичок
Никакой разницы в скорости нет, так что explain я не вывожу.
тем не менее, пункт 2-то помог :) для него и хотелось бы поглядеть.
К тому же, explain - более ценная информация от тебя, чем та, во сколько раз ускорился запрос. Explain отвечает на вопрос "почему?".

Что интересно, если индекс (id, parent), то это ничего не дает.
что довольно логично, ибо они совсем разные :)
 

Slon747

Новичок
Автор оригинала: Wicked
тем не менее, пункт 2-то помог :) для него и хотелось бы поглядеть.
Вот после создания индекса таблицы на (parent, id):
Код:
===================================================================================================================================================================================================================
|         id         |    select_type     |       table        |        type        |   possible_keys    |        key         |      key_len       |        ref         |        rows        |       Extra        |
===================================================================================================================================================================================================================
|         1          |       SIMPLE       |       items        |        ref         |PRIMARY,iParent,IX_i|      IX_items      |         5          |       const        |        475         |Using where; Using  |
|                    |                    |                    |                    |        tems        |                    |                    |                    |                    |       index        |
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|         1          |       SIMPLE       |       prices       |        ref         |iItemPriceDescr,IX_p|   IX_prices_Item   |         32         |  shopdb.items.Id   |         3          |    Using where     |
|                    |                    |                    |                    |rices_Item,IX_prices|                    |                    |                    |                    |                    |
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 

svetasmirnova

маленький монстрик
> Я не понял. А где у меня третье связывание?

Я ничего не говорила про третье связывание

> Пакетно выполнил свой запрос и следом "SHOW PROCESSLIST;".

Не так. Надо открыть ещё одно соединение пользователем с привилегией SUPER (root) и в тот момент когда проблема проявляется, а именно 2 пользователя ждут завершения запроса по 25 секунд успеть набрать "SHOW PROCESSLIST;" в этом новом соединении.

Меня то, что время выполнения для 2-ух и более одновременных пользователей увеличивается, заинтересовало.

Впрочем, проблема, насколько я поняла, решена уже.
 

Slon747

Новичок
Автор оригинала: svetasmirnova
> Я не понял. А где у меня третье связывание?

Я ничего не говорила про третье связывание

> Пакетно выполнил свой запрос и следом "SHOW PROCESSLIST;".

Не так. Надо открыть ещё одно соединение пользователем с привилегией SUPER (root) и в тот момент когда проблема проявляется, а именно 2 пользователя ждут завершения запроса по 25 секунд успеть набрать "SHOW PROCESSLIST;" в этом новом соединении.

Меня то, что время выполнения для 2-ух и более одновременных пользователей увеличивается, заинтересовало.

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