Как оптимизировать запрос по нормализованной структуре базы данных? - PullRequest
0 голосов
/ 29 декабря 2018

Я пытаюсь оптимизировать запрос, который в настоящее время занимает 0,00xs в БД MySQL 5.x, чтобы получить данные в системе без нагрузки.

Запрос выглядит следующим образом:

SELECT 
   a.article_id,
   GROUP_CONCAT(attr_f.attr_de) AS functions, 
   GROUP_CONCAT(attr_n.attr_de) AS miscellaneous
FROM `articles_test` a
LEFT JOIN articles_attr AS f ON a.article_id = f.article_id AND f.attr_group_id = 26
LEFT JOIN articles_attr AS attr ON a.article_id = attr.article_id AND attr.attr_group_id = 27
LEFT JOIN cat_attr AS attr_f ON attr_f.attr_id = f.attr_id
LEFT JOIN cat_attr AS attr_n ON attr_n.attr_id = attr.attr_id
WHERE a.article_id = 11

EXPLAIN возвращает

1   SIMPLE  a   
    NULL
    const   article_id  article_id  3   const   1   100.00  
    NULL

1   SIMPLE  f   
    NULL
    ref article_id_2,article_id article_id_2    6   const,const 2   100.00  Using index 
1   SIMPLE  attr    
    NULL
    ref article_id_2,article_id article_id_2    6   const,const 4   100.00  Using index 
1   SIMPLE  attr_f  
    NULL
    ref attr_id attr_id 3   test.f.attr_id  1   100.00  
    NULL

1   SIMPLE  attr_n  
    NULL
    ref attr_id attr_id 3   test.attr.attr_id   1   100.00  
    NULL

Существуют индексы для всех запрашиваемых полей.Есть ли другой способ получить данные с помощью более простого и быстрого запроса?

CREATE TABLE `articles_attr` (
 `date_created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
 `article_id` mediumint(8) unsigned NOT NULL,
 `attr_group_id` mediumint(8) NOT NULL,
 `attr_id` mediumint(8) unsigned DEFAULT NULL,
 `value` varchar(255) DEFAULT NULL,
 UNIQUE KEY `article_id_2` (`article_id`,`attr_group_id`,`attr_id`),
 KEY `article_id` (`article_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 

CREATE TABLE `cat_attr` (
 `attr_id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
 `attr_group_id` mediumint(8) unsigned NOT NULL,
 `sort` tinyint(4) NOT NULL,
 `attr_de` varchar(255) NOT NULL,
 UNIQUE KEY `attr_id` (`attr_id`,`attr_group_id`),
 UNIQUE KEY `attr_group_id` (`attr_group_id`,`attr_de`)
) ENGINE=InnoDB AUTO_INCREMENT=380 DEFAULT CHARSET=utf8

CREATE TABLE `articles_test` (
 `article_id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
 UNIQUE KEY `article_id` (`article_id`),
) ENGINE=InnoDB AUTO_INCREMENT=221614 DEFAULT CHARSET=latin1

Таблица article_attr содержит около 0,5 миллиона строк.

Ответы [ 3 ]

0 голосов
/ 30 декабря 2018

Поскольку в предложении WHERE указано значение article_id, нет реальной необходимости позволять предложению select возвращать его.Лучше удалить его, также потому что он не соответствует стандартам SQL, которые говорят, что если у вас есть агрегирование (group_concat), все неагрегирующие выражения в предложении select должны быть в предложении group by.Но выполнение этого (как в первой версии вашего вопроса) дало бы некоторые издержки.Так что лучше удалите его.

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

Наконец, существует своего рода декартово соединение, поскольку вы комбинируете каждое попадание в attr_f с каждым попаданием в attr_n.Это может привести к появлению некоторых дубликатов в выходных данных group_concat и представляет собой снижение производительности.

Если все эти дубликаты удаляются, то, возможно, вы добьетесь лучшей производительности, разбив запрос на группы:один для выхода function , один для разного выхода.Затем группа формируется из attr_group_id.

. Это также позволит превратить внешние объединения во внутренние объединения.

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

SELECT     attr.attr_group_id, GROUP_CONCAT(cat.attr_de) AS functions
FROM       articles_attr AS attr 
INNER JOIN cat_attr AS cat ON cat.attr_id = attr.attr_id
WHERE      attr.article_id = 11
       AND attr.attr_group_id IN (26, 27) 
GROUP BY   attr.attr_group_id

Итак, теперь вывод будет состоять из двух строк.В том, что в первом столбце указано 26, во втором столбце перечислены функции, а в первом столбце - 27.

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

Если вам нужна сводная версия, используйте выражение case when:

SELECT     GROUP_CONCAT(CASE attr.attr_group_id WHEN 26 THEN cat.attr_de END) AS functions,
           GROUP_CONCAT(CASE attr.attr_group_id WHEN 27 THEN cat.attr_de END) AS miscellaneous
FROM       articles_attr AS attr 
INNER JOIN cat_attr AS cat ON cat.attr_id = attr.attr_id
WHERE      attr.article_id = 11
       AND attr.attr_group_id IN (26, 27) 
0 голосов
/ 30 декабря 2018
`attr_id` mediumint(8) unsigned DEFAULT NULL,

Почему NULL?Тебе не всегда нужен attr?Причина, по которой я это поднимаю, заключается в том, что у вас нет явного PRIMARY KEY на articles_attr.NULL предотвращает продвижение ключа UNIQUE на PK.Измените на NOT NULL и продвиньте UNIQUE на PK.

KEY `article_id` (`article_id`)

Избыточный, отбросьте его.

Структура многих: многие таблицы неоптимальны.Несколько советов: http://mysql.rjweb.org/doc.php/index_cookbook_mysql#many_to_many_mapping_table

Если вам не нужно «много: много», переключитесь на «1: много»;это более эффективно.

Вы, вероятно, можете использовать JOIN вместо LEFT JOIN, поскольку вам нужно пройти весь путь до attr_f и attr_n.

Перемещение соединений дляGroup_concats в SELECT может help:

SELECT  a.article_id, 
        (
        SELECT  GROUP_CONCAT(ca.attr_de)
            FROM  articles_attr AS aa
            JOIN  cat_attr AS ca USING(attr_id)
            WHERE  aa.attr_group_id = 26
              AND  aa.article_id = a.article_id
        ) AS functions, 
        (
        SELECT  GROUP_CONCAT(attr_f.attr_de)
            FROM  ..
            JOIN  ..
            WHERE  .. 
        ) AS miscellaneous
    FROM  `articles_test` a
    WHERE  a.article_id = 11

Но, возможно, самое важное - не допустить ухудшения и без того плохого дизайна схемы EAV путем нормализации атрибутов !.То есть, избавьтесь от таблицы cat_attr и переместите attr_de в articles_attr.Это сократит вдвое число JOINs.

0 голосов
/ 30 декабря 2018

Прежде всего, 9 мс для такого запроса неплохо.Там нет никакого радикального улучшения, которое будет иметься.Возможно, вам удастся выжать еще одну или две миллисекунды из запроса, а может и нет.

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

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

GROUP_CONCAT() имеет здесь смысл.Совершенно верно агрегировать весь набор результатов.Вы можете добавить GROUP BY a.article_id только для ясности;это не повлияет на производительность, поскольку вы уже выбрали только одно значение этого столбца.

В cat_attr, составной индекс в (attr_id, attr_de) может помочь.Но это, очевидно, небольшая таблица, поэтому она не сильно поможет.

Вам нужны операции LEFT JOIN, чтобы соединить articles_attr с cat_attr?Или, согласно структуре ваших данных, каждое значение articles_attr.attr_id гарантированно найдет совпадение в cat_attr.attr_id.Если вы можете изменить эти LEFT JOIN операции на JOIN s, вы можете получить небольшое ускорение.

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