Оптимизация запроса. EXPLAIN прилагается.

PRO

Новичок
Оптимизация запроса. EXPLAIN прилагается.

Запрос:
SELECT lots . * , users.login, (

SELECT CAST( GROUP_CONCAT( images.img_id, '_', images.server_id, '_', images.ext, '_', images.img_date
SEPARATOR ';' ) AS CHAR )
FROM images
WHERE images.lot_id = lots.lot_id
AND images.img_active =1
GROUP BY images.lot_id
) AS images_str, users_bid . * , count( users_bid.lot_id )
FROM lots
LEFT JOIN users ON users.user_id = lots.owner
LEFT JOIN (

SELECT bids . * , u2.login AS lider_login
FROM bids
LEFT JOIN users u2 ON u2.user_id = bids.user_id
WHERE bids.b_see =1
ORDER BY summ DESC , b_type
) AS users_bid ON users_bid.lot_id = lots.lot_id
INNER JOIN nominal ON nominal.nominal_id = lots.nominal_id
LEFT JOIN category ON category.category_id = lots.category_id
WHERE lots.ltype =0
AND lots.auk_id =14
GROUP BY lots.lot_id
ORDER BY category.weight, nominal.weight

EXPLAIN :
Код:
id 	select_type 	table 	type 	possible_keys 	key 	key_len 	ref 	rows 	Extra 
1	PRIMARY	lots	ref	nominal_id,auk_id_2	auk_id_2	6	const,const	3	Using temporary; Using filesort
1	PRIMARY	users	eq_ref	PRIMARY	PRIMARY	8	aucoins.lots.owner	1	 
1	PRIMARY	<derived3>	ALL	NULL	NULL	NULL	NULL	8	 
1	PRIMARY	category	eq_ref	PRIMARY	PRIMARY	2	aucoins.lots.category_id	1	 
1	PRIMARY	nominal	eq_ref	PRIMARY	PRIMARY	2	aucoins.lots.nominal_id	1	Using where
3	DERIVED	bids	ALL	b_see	b_see	1	 	7	Using filesort
3	DERIVED	u2	eq_ref	PRIMARY	PRIMARY	8	aucoins.bids.user_id	1	 
2	DEPENDENT SUBQUERY	images	ref	lot_id_2	lot_id_2	10	func,const	2	Using where
Смущают строки с таблицами bids и <derived3>.
Может кто поможет советом в плане оптимизации.
Заранее спасибо.
 

Gas

может по одной?
Смущают строки с таблицами bids и <derived3>.
derived3 смущать не должен, результат вложенного запроса не быть приджойнен по индексам, по-этому ALL

bids - ну а если есть индекс на поле b_see, то либо мало записей в таблице, либо слишком большое количество значений b_see =1, что mysql не использует индекс.

а кинь ещё create table для lots.
 

PRO

Новичок
CREATE TABLE `lots` (
`lot_id` bigint(20) NOT NULL auto_increment,
`owner` smallint(6) NOT NULL,
`category_id` smallint(6) NOT NULL,
`nominal_id` int(11) NOT NULL,
`auk_id` int(11) NOT NULL,
`lactive` smallint(1) NOT NULL,
`ltype` smallint(1) NOT NULL,
PRIMARY KEY (`lot_id`),
KEY `owner` (`owner`),
KEY `lactive` (`lactive`),
KEY `category_id` (`category_id`),
KEY `nominal_id` (`nominal_id`),
KEY `auk_id_2` (`auk_id`,`ltype`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ;

По поводу b_see - слишком большое количество значений b_see =1.
 

Gas

может по одной?
Ещё смущает "ORDER BY summ DESC , b_type", зачем оно там? Добавляет filesort, а смысла я не вижу из-за группировки итогового result set'а.
 

Gas

может по одной?
Я так понимаю что вложенный запрос (там где ORDER BY summ ...) нужен для подсчёта количества ставок в лоте и определения пользователя, который поставил самую большую ставку? Если да, то вроде ему там не место, щас подумаю где :)
 

PRO

Новичок
Gas
Как всегда прав.
Для этого я там его и сделал :)

Я не могу придумать других вариантов.
В подзапрос не сунеш.
 

Gas

может по одной?
Сразу я новый запрос увидел так:

Код:
SELECT lots . * , users.login, users_bid.*,
  (
    SELECT CAST( GROUP_CONCAT( images.img_id, '_', images.server_id, '_', images.ext, '_', 
                         images.img_date SEPARATOR ';' ) AS CHAR ) 
    FROM images
    WHERE images.lot_id = lots.lot_id
    AND images.img_active =1
    GROUP BY images.lot_id
  ) AS images_str  
FROM lots
INNER JOIN nominal ON nominal.nominal_id = lots.nominal_id  
LEFT JOIN users ON users.user_id = lots.owner
LEFT JOIN category ON category.category_id = lots.category_id
LEFT JOIN 
(
  SELECT b1.*, u2.login AS lider_login, t1.cnt AS cnt
  FROM
  (
    SELECT MAX(summ) AS summ, lot_id, b_see, COUNT(*) AS cnt
    FROM bids FORCE INDEX (lot_id_see_summ)
    WHERE b_see=1
    GROUP BY lot_id
  ) AS t1
  JOIN bids AS b1 FORCE INDEX (lot_id_see_summ) 
       ON b1.lot_id=t1.lot_id AND b1.summ=t1.summ AND b1.b_see=t1.b_see
  JOIN users AS u2 ON u2.user_id = b1.user_id
) AS users_bid 
ON users_bid.lot_id = lots.lot_id  
WHERE lots.ltype = 0 AND lots.auk_id = 14  
ORDER BY category.weight, nominal.weight
lot_id_see_summ - составной индекс (lot_id, b_see, summ)
но, я когда думал не учёл что нужно ещё количество ставок к каждому лоту, из-за этого всё равно будет группироваться вся таблица bids (правда только индексы), а не сразу доставаться максимальный summ для каждой группы ("Using index for group-by" в extra explain'а), а без этого на больших объёмах будут тормоза (количество можно отдельным полем вынести). Так-же заметил только сейчас что у тебя идёт сортировка по b_type, не знаю что это и у меня такого нет.

p.s. запрос я конечно не запускал, может в нём какие-то бока :)
 

PRO

Новичок
Cортировка по b_type нужна потому, что при одинаковом summ главнее ставка с меньшим b_type.

Только что проверил запрос.
Чтобы он работал правильнее в конце все равно нужен group (GROUP BY lots.lot_id ORDER BY category.weight, nominal.weight), иначе при совпадении summ(вопрос о b_type) он выведет обе строки.
 

Gas

может по одной?
а ты не гонял свой запрос на тестовых данных, ну там 1K лотов и 50K ставок ? Там сразу будут видны проблемы, но вот навскидку мне кажется что твой вариант получения максимальной ставки будет слишком ресурсоёмким (полный скан таблицы ставок, которая по идее будет самой большой), предложенный мной вариант можно допилять чтоб учитывалась и b_type, хотя красивым он и не будет, но работать должен быстрее.

В общем, рекомендую протестировать на более-менее каком-то количестве данных свой вариант, не устроит скорость - будем думать.

И ещё вопрос, ты будешь выводить список всех ставок, лимита в запросе у тебя не будет ?
 

PRO

Новичок
Ставки все не нужны, только лидирующая.
Список лотов будет с лимитом, т.е в конце запроса добавится LIMIT.
Про полный скан согласен, будет медленно.

-~{}~ 06.11.08 17:28:

В итоге
SELECT lots . * , users.login, users_bid.*,
(
SELECT CAST( GROUP_CONCAT( images.img_id, '_', images.server_id, '_', images.ext, '_',
images.img_date SEPARATOR ';' ) AS CHAR )
FROM images
WHERE images.lot_id = lots.lot_id
AND images.img_active =1
GROUP BY images.lot_id
) AS images_str
FROM lots
INNER JOIN nominal ON nominal.nominal_id = lots.nominal_id
LEFT JOIN users ON users.user_id = lots.owner
LEFT JOIN category ON category.category_id = lots.category_id
LEFT JOIN
(
SELECT b1.*, u2.login AS lider_login, t1.cnt AS cnt
FROM
(
SELECT MAX(summ) AS summ, lot_id, b_see, COUNT(*) AS cnt
FROM bids FORCE INDEX (lot_id_see_summ)
WHERE b_see=1
GROUP BY lot_id
) AS t1
JOIN bids AS b1 FORCE INDEX (lot_id_see_summ)
ON b1.lot_id=t1.lot_id AND b1.summ=t1.summ AND b1.b_see=t1.b_see
LEFT JOIN users AS u2 ON u2.user_id = b1.user_id
ORDER BY b1.b_type
) AS users_bid
ON users_bid.lot_id = lots.lot_id
WHERE lots.ltype = 0 AND lots.auk_id = 14
GROUP BY lots.lot_id
ORDER BY category.weight, nominal.weight

EXPLAIN
Код:
1	PRIMARY	lots	ref	nominal_id,auk_id_2	auk_id_2	6	const,const	3	Using temporary; Using filesort
1	PRIMARY	users	eq_ref	PRIMARY	PRIMARY	8	aucoins.lots.owner	1	 
1	PRIMARY	category	eq_ref	PRIMARY	PRIMARY	2	aucoins.lots.category_id	1	 
1	PRIMARY	<derived3>	ALL	NULL	NULL	NULL	NULL	4	 
1	PRIMARY	nominal	eq_ref	PRIMARY	PRIMARY	2	aucoins.lots.nominal_id	1	Using where
3	DERIVED	<derived4>	ALL	NULL	NULL	NULL	NULL	3	Using temporary; Using filesort
3	DERIVED	b1	ref	lot_id_see_summ	lot_id_see_summ	9	t1.lot_id,t1.b_see,t1.summ	1	 
3	DERIVED	u2	eq_ref	PRIMARY	PRIMARY	8	aucoins.b1.user_id	1	 
4	DERIVED	bids	index	NULL	lot_id_see_summ	9	NULL	8	Using where; Using index
2	DEPENDENT SUBQUERY	images	ref	lot_id_2	lot_id_2	10	func,const	2	Using where
Gas что скажешь?
 

Gas

может по одной?
как и говорил, чисто теоритически этот вариант (который ты привёл выше) должен быть лучше чем твой первый (чем больше данных, тем больше должен быть выигрыш).
Но, тут сам подход не правильный - если будет доставаться всего лишь 10 лотов, а в запросе идёт полный скан индекса всей таблицы bids (допустим что у нас 1000 лотов с 20 ставками на каждом). Не правильно это.

Я бы предложил так:

хранить количество ставок в отлельном поле у лота, а выбор максимальной ставки сделать подзапросом, как у тебя картинки вытягиваются:

Код:
SELECT tmp.*, bids.*, u2.login AS lider_login
FROM
(
  SELECT lots.* , users.login,
  (
    SELECT CAST( GROUP_CONCAT( images.img_id, '_', images.server_id, '_', images.ext, '_', 
    images.img_date SEPARATOR ';' ) AS CHAR ) 
    FROM images
    WHERE images.lot_id = lots.lot_id
    AND images.img_active =1
    GROUP BY images.lot_id
  ) AS images_str,
  (
    SELECT t.id FROM
    (
      SELECT id, summ, b_type 
      FROM bids
      WHERE lot_id=lots.lot_id AND b_see=1
      ORDER BY summ DESC
      LIMIT 5
    ) AS t
    ORDER BY summ DESC, b_type ASC
    LIMIT 1
  ) AS bid_max_id 
  FROM lots
  INNER JOIN nominal ON nominal.nominal_id = lots.nominal_id 
  LEFT JOIN users ON users.user_id = lots.owner
  LEFT JOIN category ON category.category_id = lots.category_id
  WHERE lots.ltype = 0 AND lots.auk_id = 14 
  ORDER BY category.weight, nominal.weight
  LIMIT 10
) AS tmp
LEFT JOIN bids ON bids.id=tmp.bid_max_id
LEFT JOIN users AS u2 ON u2.user_id = bids.user_id
на таблице bids нужен составной индекс (lot_id,b_see,summ) и предпологается что есть поле id. Индекс lot_id_see_summ уже не нужен.
Если пока хочется в запросе считать количество ставок для лота - можно сюда втулить либо ещё один подзапрос, либо джойн+группировка.

Этот момент:
Код:
SELECT t.id FROM
(
      SELECT id, summ, b_type 
      FROM bids
      WHERE lot_id=lots.id AND b_see=1
      ORDER BY summ DESC
      LIMIT 5
) AS t
ORDER BY summ DESC, b_type ASC
LIMIT 1
сделан так хитрожопо, для перестраховки - без вложенного запроса будет filesort (ORDER BY summ DESC, b_type ASC) и если ставок на лотах много, то это лишние ресурсы, а так будет использоваться на полную составной индекс (lot_id,b_see,summ), вытягиваться по нему сразу 5 записей без зависимости от общего числа, а потом они уже будут сортироваться с filesort'ом, но это роли не играет.
 

PRO

Новичок
Запустил запрос -
#1054 - Unknown column 'lots.lot_id' in 'where clause'

-~{}~ 06.11.08 22:54:

Или пока так

SELECT tmp. * , bids. * , u2.login AS lider_login, (

SELECT count( * )
FROM bids
WHERE bids.lot_id = tmp.lot_id
)
FROM (

SELECT lots. * , users.login, (

SELECT CAST( GROUP_CONCAT( images.img_id, '_', images.server_id, '_', images.ext, '_', images.img_date
SEPARATOR ';' ) AS CHAR )
FROM images
WHERE images.lot_id = lots.lot_id
AND images.img_active =1
GROUP BY images.lot_id
) AS images_str, (

SELECT bid_id
FROM bids
WHERE lot_id = lots.lot_id
AND b_see =1
ORDER BY summ DESC , b_type ASC
LIMIT 1
) AS bid_max_id
FROM lots
INNER JOIN nominal ON nominal.nominal_id = lots.nominal_id
LEFT JOIN users ON users.user_id = lots.owner
LEFT JOIN category ON category.category_id = lots.category_id
WHERE lots.ltype =0
AND lots.auk_id =14
ORDER BY category.weight, nominal.weight
LIMIT 0 , 2
) AS tmp
LEFT JOIN bids ON bids.bid_id = tmp.bid_max_id
LEFT JOIN users AS u2 ON u2.user_id = bids.user_id

EXPLAIN

Код:
id 	select_type 	table 	type 	possible_keys 	key 	key_len 	ref 	rows 	Extra 
1	PRIMARY	<derived3>	ALL	NULL	NULL	NULL	NULL	2	 
1	PRIMARY	bids	eq_ref	PRIMARY	PRIMARY	8	tmp.bid_max_id	1	 
1	PRIMARY	u2	eq_ref	PRIMARY	PRIMARY	8	aucoins.bids.user_id	1	 
3	DERIVED	lots	ref	nominal_id,auk_id_2	auk_id_2	6	 	3	Using temporary; Using filesort
3	DERIVED	users	eq_ref	PRIMARY	PRIMARY	8	aucoins.lots.owner	1	 
3	DERIVED	category	eq_ref	PRIMARY	PRIMARY	2	aucoins.lots.category_id	1	 
3	DERIVED	nominal	eq_ref	PRIMARY	PRIMARY	2	aucoins.lots.nominal_id	1	Using where
5	DEPENDENT SUBQUERY	bids	ALL	lot_id_see_summ	lot_id_see_summ	5	aucoins.lots.lot_id	3	Using filesort
4	DEPENDENT SUBQUERY	images	ref	lot_id_2	lot_id_2	10	aucoins.lots.lot_id	2	Using where
2	DEPENDENT SUBQUERY	bids	ref	lot_id_see_summ	lot_id_see_summ	4	tmp.lot_id	3	Using where; Using index
 

Gas

может по одной?
единственно тут не очень красиво
5 DEPENDENT SUBQUERY bids ALL lot_id_see_summ lot_id_see_summ 5 aucoins.lots.lot_id 3 Using filesort
ALL - скорее всего из-за малого количества данных, а вот filesort не очень хорошо, но негативные последствия будут только если у лотов много ставок. Кстати, в случае ORDER BY summ DESC , b_type ASC, лучше сократить индекс (lot_id,b_see,summ) до (lot_id,b_see) - всё равно сортировка будет без его использования.

Я бы лучше заменил этот подзапрос
Код:
(
  SELECT bid_id
  FROM bids
  WHERE lot_id = lots.lot_id AND b_see =1
  ORDER BY summ DESC , b_type ASC 
  LIMIT 1 
) AS bid_max_id
на

Код:
(
   SELECT b1.bid_id
   FROM
   (
       SELECT MAX(summ) AS summ, lot_id, b_see
       FROM bids FORCE INDEX (lot_id_see_summ)
       WHERE lot_id = lots.lot_id  AND b_see=1
       GROUP BY lot_id
    ) AS t1
    JOIN bids AS b1 FORCE INDEX (lot_id_see_summ) 
    ON b1.lot_id=t1.lot_id AND b1.summ=t1.summ AND b1.b_see=t1.b_see
    ORDER BY b1.b_type
    LIMIT 1
) AS bid_max_id
как писал выше, для него нужен индекс (lot_id,b_see,summ).
У этого подзапроса намного меньше зависимость скорости работы от количества ставок у лотов, так как по индексу выбираются только ставки с максимальной ставкой (наверное обычно это будет всего одна запись).

Дальше теоретизировать особого смысла нет, нужно сгенерить "приличный" набор тестовых данных (хотя бы 1000 лотов, тыщ 20-50 ставок и чтоб распределение данных было реальным) и уже смотреть время выполнения.

Так-же можно вынести join для получения владельца лота во вне, потому что раз у тебя filesort лотов (технически это не обойти), то таблица users будет дёргаться лишнее количество раз, а не то что указано в limit'е.

В общем итоговый запрос, который бы я тестил:
Код:
SELECT tmp. * , bids. * , u2.login AS lider_login, u.login, 
           (
              SELECT count( * ) 
              FROM bids
              WHERE lot_id = tmp.lot_id AND b_see=1
           )
FROM (
    SELECT lots. * ,
             (
                SELECT CAST( GROUP_CONCAT( images.img_id, '_', images.server_id, '_', images.ext, '_', images.img_date
                             SEPARATOR ';' ) AS CHAR ) 
                FROM images
                WHERE images.lot_id = lots.lot_id AND images.img_active =1
                GROUP BY images.lot_id
            ) AS images_str, 
            (
               SELECT b1.bid_id
               FROM
               (
                  SELECT MAX(summ) AS summ, lot_id, b_see
                  FROM bids FORCE INDEX (lot_id_see_summ)
                  WHERE lot_id = lots.lot_id  AND b_see=1
                  GROUP BY lot_id
               ) AS t1
               JOIN bids AS b1 FORCE INDEX (lot_id_see_summ) 
               ON b1.lot_id=t1.lot_id AND b1.summ=t1.summ AND b1.b_see=t1.b_see
               ORDER BY b1.b_type
               LIMIT 1
            ) AS bid_max_id
   FROM lots
   INNER JOIN nominal ON nominal.nominal_id = lots.nominal_id
   LEFT JOIN category ON category.category_id = lots.category_id
   WHERE lots.ltype =0 AND lots.auk_id =14
   ORDER BY category.weight, nominal.weight
   LIMIT 0 , 2
) AS tmp
LEFT JOIN bids ON bids.bid_id = tmp.bid_max_id
LEFT JOIN users AS u2 ON u2.user_id = bids.user_id
LEFT JOIN users AS u ON u.user_id = lots.owner
 

PRO

Новичок
Ok.
Большое спасибо Gas за помощь.
Оставлю пока предложенный вариант, если потом возникнут проблемы, тогда нужно будет оптимизировать.

-~{}~ 07.11.08 13:23:

Unknown column 'lots.lot_id' in 'where clause'
Mysql ругается на эту строчку:
WHERE lot_id = lots.lot_id AND b_see=1
 

Gas

может по одной?
Алиас не помог.
нда, нет в жизни счастья.
тогда вернись к оптимизации если тесты на реальных данных покажут плохой результат, а пока пользуй свой последний вариант (ну можешь в нём вынести вверх join с получения lots.owner, о чём я писал выше).
 
Сверху