SQLinfo.ru - Все о MySQL Webew.ru: теория и практика веб-технологий

Форум пользователей MySQL

Задавайте вопросы, мы ответим

Вы не зашли.

#1 21.11.2008 19:25:41

elgato
Участник
Зарегистрирован: 21.11.2008
Сообщений: 11

Структура таблиц и запросы

Добрый вечер,
Уже намучался с запросами... Задача такая: есть категории товарные, есть товары, есть свойства, есть значения свойств, есть таблица привязки. (Проще говоря, у каждой категории есть свойства, а у них есть вырианты выбора - selectvalues). Надо получить запросом список значений который есть у товаров этой папки, допустим
ID свойства    ID выбранного значения     кол-во товаров с этим значением
1     1    10
1    2    20
2    3    30

Вот структура таблиц (на ключи можно не смотреть, это уже создавалось что угодно для экспериментов)

товары (parent_id - ID папки):

CREATE TABLE `shop_products` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `parent_id` smallint(2) NOT NULL default '0',
  `name` varchar(255) NOT NULL default '',
  PRIMARY KEY  (`id`),
  KEY `parent_id_2` (`parent_id`,`id`),
  KEY `id` (`id`,`parent_id`)
) ENGINE=MyISAM AUTO_INCREMENT=159220 DEFAULT CHARSET=utf8

свойства (parent_id - ID папки):

CREATE TABLE `shop_properties` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `parent_id` smallint(2) NOT NULL default '0',
  `name` varchar(255) NOT NULL default '',
  `is_flag5` tinyint(1) NOT NULL default '0',
  `is_deleted` tinyint(1) NOT NULL default '0',
  `ordering` int(11) NOT NULL default '0',
  PRIMARY KEY  (`id`),
  KEY `parent_id` (`parent_id`,`is_deleted`,`is_flag5`),
  KEY `parent_id_2` (`parent_id`,`is_deleted`,`is_flag5`,`name`)
) ENGINE=MyISAM AUTO_INCREMENT=3061 DEFAULT CHARSET=utf8

значения свойств:

CREATE TABLE `shop_selectvalues` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `property_id` int(10) unsigned NOT NULL default '0',
  `value` char(255) NOT NULL default '',
  PRIMARY KEY  (`id`),
  KEY `del_prop_ord` (`is_deleted`,`property_id`,`ordering`),
  KEY `property_id` (`property_id`,`value`)
) ENGINE=MyISAM AUTO_INCREMENT=82242 DEFAULT CHARSET=utf8

таблица связки (parent_id - добавлен в качестве эксперимента):
CREATE TABLE `shop_selected` (
  `product_id` int(11) unsigned NOT NULL default '0',
  `select_id` int(11) unsigned NOT NULL default '0',
  `property_id` int(11) unsigned NOT NULL default '0',
  `parent_id` smallint(6) NOT NULL default '0',
  PRIMARY KEY  (`product_id`,`select_id`),
  KEY `product_id` (`product_id`,`property_id`),
  KEY `product_id_2` (`product_id`,`select_id`,`property_id`),
  KEY `parent_id` (`parent_id`,`select_id`,`property_id`),
  KEY `select_id_2` (`select_id`,`product_id`),
  KEY `parent_id_2` (`parent_id`,`product_id`,`select_id`,`property_id`),
  KEY `product_id_3` (`product_id`,`property_id`,`select_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8


Вот к какому запросу я пришел в итоге:
SELECT SQL_NO_CACHE s.select_id, p.id, p.name, sl.value, count(*) FROM shop_selectvalues AS sl RIGHT JOIN shop_selected AS s ON (sl.id = s.select_id) LEFT JOIN shop_products AS g ON g.id = s.product_id AND g.parent_id = 103, shop_properties AS p WHERE p.id = s.property_id AND p.is_deleted = 0 AND p.is_flag5 = 1 GROUP BY s.select_id;
Выполняется за 0.50сек, в таблице shop_products 150тыс записей примерно, в shop_selected - 1.5млн, в остальных мало

Вот explain:
mysql> explain SELECT SQL_NO_CACHE s.select_id, p.id, p.name, sl.value, count(*) FROM shop_selectvalues AS sl RIGHT JOIN shop_selected AS s ON (sl.id = s.select_id) LEFT JOIN shop_products AS g ON g.id = s.product_id AND g.parent_id = 103, shop_properties AS p WHERE p.id = s.property_id AND p.is_deleted = 0 AND p.is_flag5 = 1 GROUP BY s.select_id;
+----+-------------+-------+--------+----------------------------------------------+--------------+---------+------------------------+---------+----------------------------------------------+
| id | select_type | table | type   | possible_keys                                | key          | key_len | ref                    | rows    | Extra                                        |
+----+-------------+-------+--------+----------------------------------------------+--------------+---------+------------------------+---------+----------------------------------------------+
|  1 | SIMPLE      | s     | index  | NULL                                         | product_id_2 | 12      | NULL                   | 1230452 | Using index; Using temporary; Using filesort |
|  1 | SIMPLE      | p     | eq_ref | PRIMARY,is_deleted,is_deleted_2,id           | PRIMARY      | 4       | ava_main.s.property_id |       1 | Using where                                  |
|  1 | SIMPLE      | sl    | eq_ref | PRIMARY                                      | PRIMARY      | 4       | ava_main.s.select_id   |       1 |                                              |
|  1 | SIMPLE      | g     | eq_ref | PRIMARY,parent_id,parent_id_2,parent_id_3,id | PRIMARY      | 4       | ava_main.s.product_id  |       1 |                                              |
+----+-------------+-------+--------+----------------------------------------------+--------------+---------+------------------------+---------+----------------------------------------------+
4 rows in set (0.04 sec)



Всё вроде хорошо, в принципе, но любой order работает больше 20 секунд, а очень желательно чтоб выводились результаты в порядке свойств...  плюс тормозит таки если любой order поставить. уже всякие варианты с right join, left join, join, straight_join, use index, пробовал, результат никакой.. Уже 2 дня долблюсь с консолью)

Неактивен

 

#2 21.11.2008 21:16:39

paulus
Администратор
MySQL Authorized Developer and DBA
Зарегистрирован: 22.01.2007
Сообщений: 6757

Re: Структура таблиц и запросы

Ух smile
Данных у меня нету, поэтому будем в интерактивном режиме пытаться разобраться smile

1. Понять, почему тормозит.
Тормозит, потому что нет нужных индексов, а есть какие-то ненужные. Индексы нужно сделать нужные.

2. Попытаться разобраться в том, что есть.
Для того, чтобы выбрать "товары этой папки", нужно ограничение  на parent_id. Я его не вижу sad

3. Упростить задачу.
Попытайтесь делать выборки по методу приближений. Чем больше индекс отрезает ненужного, тем лучше.
Запишите структуру в простом виде, тогда легче будет понимать, что происходит. Я бы сделал это как-то так:

prod (id, parent, ...)
prop (id, parent, ...)
val (id, prop, ...)
seld (prod, val, prop)

Тут сразу видно, что лишнее поле val.prop wink

"Надо получить запросом список значений который есть у товаров этой папки", т.е. ограничить по parent.
+ мы выяснили, что табличка val содержит ненужное поле, соответственно, она вообще не нужна в запросе,
который вытаскивает id.

Итого как-то так:

SELECT prop.id, seld.val, count(*)
FROM prod, prop, seld
WHERE
  (prod.id = seld.prod) AND (prop.id = seld.prop) AND     -- связи таблиц
  (prod.parent = 1) AND (prop.parent = 1)                 -- 1 - нужный id папки
GROUP BY prop.id, seld.val

Индексы нужно использовать по максимуму, поэтому я бы делал следующие индексы:
1. (id) на всех табличках с полем id
2. (parent) на prod
3. (parent,id) на prop
4. на seld отдельные ключики по всем трем полям

Неактивен

 

#3 21.11.2008 21:17:24

elgato
Участник
Зарегистрирован: 21.11.2008
Сообщений: 11

Re: Структура таблиц и запросы

единственное что пришло в голову это промежуточная таблица в которой будут все данные которые мне надо выбрать, и в выборке делать просто связку shop_products и shop_thismegatable, а для её хранения делать триггер на все 3 таблицы чтоб данные править... вот

Неактивен

 

#4 21.11.2008 21:34:50

paulus
Администратор
MySQL Authorized Developer and DBA
Зарегистрирован: 22.01.2007
Сообщений: 6757

Re: Структура таблиц и запросы

Нужно для начала выкинуть из запроса все лишнее, а потом уже смотреть, как денормализовать, если все равно данных
слишком много. Денормализовать изначально не нормализованные данные плохо, по опыту.

Неактивен

 

#5 21.11.2008 21:39:25

elgato
Участник
Зарегистрирован: 21.11.2008
Сообщений: 11

Re: Структура таблиц и запросы

paulus написал:

Ух smile
Данных у меня нету, поэтому будем в интерактивном режиме пытаться разобраться smile

1. Понять, почему тормозит.
Тормозит, потому что нет нужных индексов, а есть какие-то ненужные. Индексы нужно сделать нужные.

2. Попытаться разобраться в том, что есть.
Для того, чтобы выбрать "товары этой папки", нужно ограничение  на parent_id. Я его не вижу sad

3. Упростить задачу.
Попытайтесь делать выборки по методу приближений. Чем больше индекс отрезает ненужного, тем лучше.
Запишите структуру в простом виде, тогда легче будет понимать, что происходит. Я бы сделал это как-то так:

prod (id, parent, ...)
prop (id, parent, ...)
val (id, prop, ...)
seld (prod, val, prop)

Тут сразу видно, что лишнее поле val.prop wink

"Надо получить запросом список значений который есть у товаров этой папки", т.е. ограничить по parent.
+ мы выяснили, что табличка val содержит ненужное поле, соответственно, она вообще не нужна в запросе,
который вытаскивает id.

Итого как-то так:

SELECT prop.id, seld.val, count(*)
FROM prod, prop, seld
WHERE
  (prod.id = seld.prod) AND (prop.id = seld.prop) AND     -- связи таблиц
  (prod.parent = 1) AND (prop.parent = 1)                 -- 1 - нужный id папки
GROUP BY prop.id, seld.val

Вот как получилось:

mysql> explain SELECT SQL_NO_CACHE p.id, s.select_id, count(*) FROM shop_products AS g, shop_properties AS p, shop_selected AS s WHERE (g.id = s.product_id) AND (p.id = s.property_id) AND (g.parent_id = 103) AND (p.parent_id = 103) GROUP BY p.id, s.select_id;
+----+-------------+-------+--------+----------------------------------------------------------+--------------+---------+------------------------+------+----------------------------------------------+
| id | select_type | table | type   | possible_keys                                            | key          | key_len | ref                    | rows | Extra                                        |
+----+-------------+-------+--------+----------------------------------------------------------+--------------+---------+------------------------+------+----------------------------------------------+
|  1 | SIMPLE      | g     | ref    | PRIMARY,parent_id,parent_id_2,parent_id_3,id             | parent_id_2  | 2       | const                  | 4551 | Using index; Using temporary; Using filesort |
|  1 | SIMPLE      | s     | ref    | PRIMARY,product_id,product_id_2,product_id_3,property_id | product_id_2 | 4       | ava_main.g.id          |   14 | Using index                                  |
|  1 | SIMPLE      | p     | eq_ref | PRIMARY,parent_id,parent_id_2,id,parent_id_3             | PRIMARY      | 4       | ava_main.s.property_id |    1 | Using where                                  |
+----+-------------+-------+--------+----------------------------------------------------------+--------------+---------+------------------------+------+----------------------------------------------+
3 rows in set (0.00 sec)

Выполняется где-то 4 секунды
Ключи:
g.parent_id_2 - `parent_id_2` (`parent_id`,`id`)
s.product_id_2 - `product_id_2` (`product_id`,`select_id`,`property_id`)
p - берет зараза праймари

paulus написал:

Индексы нужно использовать по максимуму, поэтому я бы делал следующие индексы:
1. (id) на всех табличках с полем id
2. (parent) на prod
3. (parent,id) на prop
4. на seld отдельные ключики по всем трем полям

1. везде id - primary
2. есть (parent_id, id)
3. сделал, оно все равно берет PRIMARY, сейчас попробую еще принудительно ему индекс дать
4. тоже есть

а будет ли резон сделать таблицу типа?
id
product_id
property_id
property_name
select_id
select_name
чтоб обойтись одним join по g.id = product_id

Неактивен

 

#6 21.11.2008 21:47:34

elgato
Участник
Зарегистрирован: 21.11.2008
Сообщений: 11

Re: Структура таблиц и запросы

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

Неактивен

 

#7 21.11.2008 22:08:46

elgato
Участник
Зарегистрирован: 21.11.2008
Сообщений: 11

Re: Структура таблиц и запросы

вот что получилось после ковыряний

mysql> EXPLAIN SELECT SQL_NO_CACHE p.id, p.name, sl.value, s.select_id, count(*) FROM shop_products AS g, shop_properties AS p, shop_selected AS s, shop_selectvalues AS sl WHERE (p.id = s.property_id) AND (g.id = s.product_id) AND (sl.id = s.select_id) AND (g.parent_id = 103) AND (p.parent_id = 103) AND (p.is_deleted=0) AND (p.is_flag5=1) GROUP BY s.property_id, s.select_id ORDER BY p.id, sl.value;
+----+-------------+-------+--------+----------------------------------------------------------------------------------------------+---------------+---------+-----------------------+------+---------------------------------+
| id | select_type | table | type   | possible_keys                                                                                | key           | key_len | ref                   | rows | Extra                           |
+----+-------------+-------+--------+----------------------------------------------------------------------------------------------+---------------+---------+-----------------------+------+---------------------------------+
|  1 | SIMPLE      | p     | ref    | PRIMARY,parent_id,parent_id_2,id_2,is_deleted_3,id_3  | is_deleted_3  | 4       | const,const,const     |    3 | Using temporary; Using filesort |
|  1 | SIMPLE      | s     | ref    | PRIMARY,product_id,select_id,property_id,property_id_2 | property_id_2 | 4       | ava_main.p.id         |  442 | Using index                     |
|  1 | SIMPLE      | sl    | eq_ref | PRIMARY,id                                                                                   | PRIMARY       | 4       | ava_main.s.select_id  |    1 |                                 |
|  1 | SIMPLE      | g     | eq_ref | PRIMARY,parent_id,parent_id_2,parent_id_3,id                                                 | PRIMARY       | 4       | ava_main.s.product_id |    1 | Using where                     |
+----+-------------+-------+--------+----------------------------------------------------------------------------------------------+---------------+---------+-----------------------+------+---------------------------------+
4 rows in set (0.00 sec)

Отредактированно elgato (21.11.2008 22:10:27)

Неактивен

 

#8 24.11.2008 13:17:20

paulus
Администратор
MySQL Authorized Developer and DBA
Зарегистрирован: 22.01.2007
Сообщений: 6757

Re: Структура таблиц и запросы

А сколько выполняется по времени? 1200 строк - это нормально для такого запроса?

Неактивен

 

#9 24.11.2008 13:26:25

elgato
Участник
Зарегистрирован: 21.11.2008
Сообщений: 11

Re: Структура таблиц и запросы

paulus написал:

А сколько выполняется по времени? 1200 строк - это нормально для такого запроса?

4500 товаров примерно, это самая обьемная папка с товарами. 0.30 сек выполняется запрос. сейчас всё множественными запросами реализовано, список свойств, для каждого взять вариант, соответственно при 5 свойствах и в сумме 50 значений это 55 запросов, вот и захотелось уменьшить.. но те запросы как бы и выполняются за сотые доли секунды, но их много.. в результате получается что-то вроде

свойство 1:
значение 1 - 10 товаров
значение 2 - 20 товаров
значение 3 - 25 товаров

свойство 2:
значение 4 - 5 товаров
значение 5 - 10 товаров
значение 6 - 20 товаров
значение 7 - 5 товаров

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

Неактивен

 

#10 24.11.2008 13:37:26

paulus
Администратор
MySQL Authorized Developer and DBA
Зарегистрирован: 22.01.2007
Сообщений: 6757

Re: Структура таблиц и запросы

Ну, оптимизировать запрос, который достает все строки из индекса вряд ли получится.
0.3 секунды на 1200 строк - это не так плохо. Разве что Вас спасет LIMIT в конце,
потому как вряд ли Вам нужны все 1200 строк сразу.

Неактивен

 

#11 24.11.2008 13:42:53

elgato
Участник
Зарегистрирован: 21.11.2008
Сообщений: 11

Re: Структура таблиц и запросы

paulus написал:

Ну, оптимизировать запрос, который достает все строки из индекса вряд ли получится.
0.3 секунды на 1200 строк - это не так плохо. Разве что Вас спасет LIMIT в конце,
потому как вряд ли Вам нужны все 1200 строк сразу.

как раз таки надо) построить блок где список всех фильтров, но там 1200 не будет конечно, но 100 - легко sad всё равно что-то не додумал я,, ведь если выбрано значение массовое, под которое подходит допустим 2000 товаров из 4000, то есть 2 варианта:
выбрать с учетов g.id IN () или сделать запрос по тому значению и програмно отделять, что тоже не ахти sad

Неактивен

 

#12 24.11.2008 18:16:56

paulus
Администратор
MySQL Authorized Developer and DBA
Зарегистрирован: 22.01.2007
Сообщений: 6757

Re: Структура таблиц и запросы

Если будет выбор по g.id, то на него нужен ключик smile
100 значений будут отрабатывать быстрее, чем 1200 в среднем в 12 раз wink

Ну, конечно, бывают подковырки, но на Ваших запросах должно быть как-то так.

Неактивен

 

Board footer

Работает на PunBB
© Copyright 2002–2008 Rickard Andersson