Улучшение производительности SQL-запросов - PullRequest
0 голосов
/ 08 января 2019

У меня сложный запрос, который выполняется на моей машине 700 мс. Я обнаружил, что узким местом является предложение ORDER BY at_firstname.value , но как я могу использовать индексы для улучшения этого?

SELECT 
    `e`.*
    , `at_default_billing`.`value` AS `default_billing`
    , `at_billing_postcode`.`value` AS `billing_postcode`
    , `at_billing_city`.`value` AS `billing_city`
    , `at_billing_region`.`value` AS `billing_region`
    , `at_billing_country_id`.`value` AS `billing_country_id`
    , `at_company`.`value` AS `company`
    , `at_firstname`.`value` AS `firstname`
    , `at_lastname`.`value` AS `lastname`
    , CONCAT(at_firstname.value
    , " "
    , at_lastname.value) AS `full_name`
    , `at_phone`.`value` AS `phone`
    , IFNULL(at_phone.value,"N/A") AS `telephone`
    , `e`.`entity_id` AS `id` 
FROM 
    `customer_entity` AS `e`  
LEFT JOIN 
    `customer_entity_int` AS `at_default_billing` 
    ON (`at_default_billing`.`entity_id` = `e`.`entity_id`) 
    AND (`at_default_billing`.`attribute_id` = '13')  
LEFT JOIN 
    `customer_address_entity_varchar` AS `at_billing_postcode` 
    ON (`at_billing_postcode`.`entity_id` = `at_default_billing`.`value`)        
    AND (`at_billing_postcode`.`attribute_id` = '30')  
LEFT JOIN 
    `customer_address_entity_varchar` AS `at_billing_city` 
    ON (`at_billing_city`.`entity_id` = `at_default_billing`.`value`) 
    AND (`at_billing_city`.`attribute_id` = '26')  
LEFT JOIN 
    `customer_address_entity_varchar` AS `at_billing_region` 
    ON (`at_billing_region`.`entity_id` = `at_default_billing`.`value`) 
    AND (`at_billing_region`.`attribute_id` = '28')  
LEFT JOIN 
    `customer_address_entity_varchar` AS `at_billing_country_id` 
    ON (`at_billing_country_id`.`entity_id` = `at_default_billing`.`value`) 
    AND (`at_billing_country_id`.`attribute_id` = '27')  
LEFT JOIN 
    `customer_address_entity_varchar` AS `at_company` 
    ON (`at_company`.`entity_id` = `at_default_billing`.`value`) 
    AND (`at_company`.`attribute_id` = '24')  
LEFT JOIN 
    `customer_entity_varchar` AS `at_firstname` 
    ON (`at_firstname`.`entity_id` = `e`.`entity_id`) 
    AND (`at_firstname`.`attribute_id` = '5')  
LEFT JOIN 
    `customer_entity_varchar` AS `at_lastname` 
    ON (`at_lastname`.`entity_id` = `e`.`entity_id`) 
    AND (`at_lastname`.`attribute_id` = '7')  
LEFT JOIN 
    `customer_entity_varchar` AS `at_phone` 
    ON (`at_phone`.`entity_id` = `e`.`entity_id`) 
    AND (`at_phone`.`attribute_id` = '136')  
ORDER BY 
    `at_firstname`.`value` ASC LIMIT 20

Это план выполнения: enter image description here

ОБЪЯСНЕНИЕ Запроса:

'1','SIMPLE','e',NULL,'ALL',NULL,NULL,NULL,NULL,'19951','100.00','Using temporary; Using filesort'
'1','SIMPLE','at_default_billing',NULL,'eq_ref','UNQ_CUSTOMER_ENTITY_INT_ENTITY_ID_ATTRIBUTE_ID,IDX_CUSTOMER_ENTITY_INT_ATTRIBUTE_ID,IDX_CUSTOMER_ENTITY_INT_ENTITY_ID,IDX_CUSTOMER_ENTITY_INT_ENTITY_ID_ATTRIBUTE_ID_VALUE','UNQ_CUSTOMER_ENTITY_INT_ENTITY_ID_ATTRIBUTE_ID','6','lazurd.e.entity_id,const','1','100.00',NULL
'1','SIMPLE','at_billing_postcode',NULL,'eq_ref','UNQ_CUSTOMER_ADDRESS_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID,IDX_CUSTOMER_ADDRESS_ENTITY_VARCHAR_ATTRIBUTE_ID,IDX_CUSTOMER_ADDRESS_ENTITY_VARCHAR_ENTITY_ID,IDX_CUSTOMER_ADDRESS_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID_VALUE','UNQ_CUSTOMER_ADDRESS_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID','6','lazurd.at_default_billing.value,const','1','100.00','Using where'
'1','SIMPLE','at_billing_city',NULL,'eq_ref','UNQ_CUSTOMER_ADDRESS_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID,IDX_CUSTOMER_ADDRESS_ENTITY_VARCHAR_ATTRIBUTE_ID,IDX_CUSTOMER_ADDRESS_ENTITY_VARCHAR_ENTITY_ID,IDX_CUSTOMER_ADDRESS_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID_VALUE','UNQ_CUSTOMER_ADDRESS_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID','6','lazurd.at_default_billing.value,const','1','100.00','Using where'
'1','SIMPLE','at_billing_region',NULL,'eq_ref','UNQ_CUSTOMER_ADDRESS_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID,IDX_CUSTOMER_ADDRESS_ENTITY_VARCHAR_ATTRIBUTE_ID,IDX_CUSTOMER_ADDRESS_ENTITY_VARCHAR_ENTITY_ID,IDX_CUSTOMER_ADDRESS_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID_VALUE','UNQ_CUSTOMER_ADDRESS_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID','6','lazurd.at_default_billing.value,const','1','100.00','Using where'
'1','SIMPLE','at_billing_country_id',NULL,'eq_ref','UNQ_CUSTOMER_ADDRESS_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID,IDX_CUSTOMER_ADDRESS_ENTITY_VARCHAR_ATTRIBUTE_ID,IDX_CUSTOMER_ADDRESS_ENTITY_VARCHAR_ENTITY_ID,IDX_CUSTOMER_ADDRESS_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID_VALUE','UNQ_CUSTOMER_ADDRESS_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID','6','lazurd.at_default_billing.value,const','1','100.00','Using where'
'1','SIMPLE','at_company',NULL,'eq_ref','UNQ_CUSTOMER_ADDRESS_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID,IDX_CUSTOMER_ADDRESS_ENTITY_VARCHAR_ATTRIBUTE_ID,IDX_CUSTOMER_ADDRESS_ENTITY_VARCHAR_ENTITY_ID,IDX_CUSTOMER_ADDRESS_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID_VALUE','UNQ_CUSTOMER_ADDRESS_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID','6','lazurd.at_default_billing.value,const','1','100.00','Using where'
'1','SIMPLE','at_firstname',NULL,'eq_ref','UNQ_CUSTOMER_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID,IDX_CUSTOMER_ENTITY_VARCHAR_ATTRIBUTE_ID,IDX_CUSTOMER_ENTITY_VARCHAR_ENTITY_ID,IDX_CUSTOMER_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID_VALUE','UNQ_CUSTOMER_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID','6','lazurd.e.entity_id,const','1','100.00',NULL
'1','SIMPLE','at_lastname',NULL,'eq_ref','UNQ_CUSTOMER_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID,IDX_CUSTOMER_ENTITY_VARCHAR_ATTRIBUTE_ID,IDX_CUSTOMER_ENTITY_VARCHAR_ENTITY_ID,IDX_CUSTOMER_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID_VALUE','UNQ_CUSTOMER_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID','6','lazurd.e.entity_id,const','1','100.00',NULL
'1','SIMPLE','at_phone',NULL,'eq_ref','UNQ_CUSTOMER_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID,IDX_CUSTOMER_ENTITY_VARCHAR_ATTRIBUTE_ID,IDX_CUSTOMER_ENTITY_VARCHAR_ENTITY_ID,IDX_CUSTOMER_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID_VALUE','UNQ_CUSTOMER_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID','6','lazurd.e.entity_id,const','1','100.00',NULL

Структура таблицы:

CREATE TABLE `customer_entity_varchar` (
  `value_id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Value Id',
  `entity_type_id` smallint(5) unsigned NOT NULL DEFAULT '0' COMMENT 'Entity Type Id',
  `attribute_id` smallint(5) unsigned NOT NULL DEFAULT '0' COMMENT 'Attribute Id',
  `entity_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'Entity Id',
  `value` varchar(255) DEFAULT NULL COMMENT 'Value',
  PRIMARY KEY (`value_id`),
  UNIQUE KEY `UNQ_CUSTOMER_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID` (`entity_id`,`attribute_id`),
  KEY `IDX_CUSTOMER_ENTITY_VARCHAR_ENTITY_TYPE_ID` (`entity_type_id`),
  KEY `IDX_CUSTOMER_ENTITY_VARCHAR_ATTRIBUTE_ID` (`attribute_id`),
  KEY `IDX_CUSTOMER_ENTITY_VARCHAR_ENTITY_ID` (`entity_id`),
  KEY `IDX_CUSTOMER_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID_VALUE` (`entity_id`,`attribute_id`,`value`),
  CONSTRAINT `FK_CSTR_ENTT_VCHR_ATTR_ID_EAV_ATTR_ATTR_ID` FOREIGN KEY (`attribute_id`) REFERENCES `eav_attribute` (`attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `FK_CSTR_ENTT_VCHR_ENTT_TYPE_ID_EAV_ENTT_TYPE_ENTT_TYPE_ID` FOREIGN KEY (`entity_type_id`) REFERENCES `eav_entity_type` (`entity_type_id`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `FK_CUSTOMER_ENTITY_VARCHAR_ENTITY_ID_CUSTOMER_ENTITY_ENTITY_ID` FOREIGN KEY (`entity_id`) REFERENCES `customer_entity` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=131094 DEFAULT CHARSET=utf8 COMMENT='Customer Entity Varchar';

Ответы [ 3 ]

0 голосов
/ 08 января 2019

К сожалению, SELECT whole_mess_of_rows FROM many_tables ORDER BY one_col LIMIT small_number является печально известным антипаттерном. Зачем? Потому что он сортирует большой набор результатов, просто чтобы отбросить большую его часть.

Хитрость заключается в том, чтобы дешево выяснить, какие строки находятся в этом LIMIT small_number, а затем извлечь только те строки из запроса большего размера.

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

           SELECT customer_entity.entity_id
             FROM customer_entity
             LEFT JOIN customer_entity_varchar AS at_firstname 
                       ON (at_firstname.entity_id = e.entity_id) 
                      AND (at_firstname.attribute_id = '5') 
            ORDER BY at_firstname.value ASC
            LIMIT 20

Это должно дать двадцать соответствующих значений entity_id. Попробуй это. Посмотрите на его план выполнения. При необходимости добавьте соответствующий индекс к customer_entity. Этот индекс может быть (firstname_attribute_id, firstname_entity_id, firstname_value) Но я предполагаю.

Затем вы можете поместить это в конец основного запроса, прямо перед ORDER BY.

 WHERE e.entity_id IN (
           SELECT customer_entity.entity_id
             FROM customer_entity
             LEFT JOIN customer_entity_varchar AS at_firstname 
                       ON (at_firstname.entity_id = e.entity_id) 
                      AND (at_firstname.attribute_id = '5') 
            ORDER BY at_firstname.value ASC
            LIMIT 20
      )

и все должно быть немного быстрее.

0 голосов
/ 09 января 2019

Я согласен с предыдущими Ответами, но хочу подчеркнуть больше антипаттерна: чрезмерная норализация.

Ваша схема является любопытным (и неэффективным) вариантом и без того плохого шаблона схемы EAV.

Существует небольшое преимущество и некоторые недостатки в разделении customer_address_entity_varchar на 5 таблиц. Аналогично для customer_entity_varchar.

Адрес должен (обычно) храниться в виде нескольких столбцов в одной таблице; нет JOINs для других таблиц.

Аналогично для имени + фамилия.

Phone может быть другой проблемой, поскольку у человека / компании / организации может быть несколько телефонных номеров (сотовый, домашний, рабочий, факс и т. Д.). Но это другая история.

0 голосов
/ 08 января 2019

На данный момент ваш запрос:

  1. Сначала выполняется ВСЕ левые внешние соединения.
  2. Затем ORDER в строках.
  3. Затем LIMIT в строках.

Сначала я выполняю строго необходимые внешние объединения, затем упорядочиваю и ограничиваю (чтобы уменьшить до 20 строк), и, наконец, я выполняю все остальные внешние объединения. Короче я бы сделал:

  1. Выполнение минимального левого внешнего соединения первым. То есть только две таблицы.
  2. Затем ORDER в строках.
  3. Затем LIMIT строк. Это дает максимум 20 строк.
  4. Выполните все остальные внешние соединения. На данный момент это уже не тысячи строк, а только 20.

Это изменение должно значительно сократить выполнение «уникального поиска ключа». Измененный запрос будет выглядеть так:

select
  e.*
  , `at_default_billing`.`value` AS `default_billing`
  , `at_billing_postcode`.`value` AS `billing_postcode`
  , `at_billing_city`.`value` AS `billing_city`
  , `at_billing_region`.`value` AS `billing_region`
  , `at_billing_country_id`.`value` AS `billing_country_id`
  , `at_company`.`value` AS `company`
  , `at_lastname`.`value` AS `lastname`
  , CONCAT(firstname
  , " "
  , at_lastname.value) AS `full_name`
  , `at_phone`.`value` AS `phone`
  , IFNULL(at_phone.value,"N/A") AS `telephone`
from ( -- Step #1: joining customer_entity with customer_entity_varchar
SELECT 
    `e`.*
    , `at_firstname`.`value` AS `firstname`
    , `e`.`entity_id` AS `id` 
FROM 
    `customer_entity` AS `e`  
LEFT JOIN 
    `customer_entity_varchar` AS `at_firstname` 
    ON (`at_firstname`.`entity_id` = `e`.`entity_id`) 
    AND (`at_firstname`.`attribute_id` = '5')  
ORDER BY -- Step #2: Sorting (the bare minimum)
    `at_firstname`.`value` ASC 
LIMIT 20 -- Step #3: Limiting (to 20 rows)
) e
LEFT JOIN -- Step #4: Performing all the rest of outer joins (only few rows now)
    `customer_entity_int` AS `at_default_billing` 
    ON (`at_default_billing`.`entity_id` = `e`.`entity_id`) 
    AND (`at_default_billing`.`attribute_id` = '13')  
LEFT JOIN 
    `customer_address_entity_varchar` AS `at_billing_postcode` 
    ON (`at_billing_postcode`.`entity_id` = `at_default_billing`.`value`)        
    AND (`at_billing_postcode`.`attribute_id` = '30')  
LEFT JOIN 
    `customer_address_entity_varchar` AS `at_billing_city` 
    ON (`at_billing_city`.`entity_id` = `at_default_billing`.`value`) 
    AND (`at_billing_city`.`attribute_id` = '26')  
LEFT JOIN 
    `customer_address_entity_varchar` AS `at_billing_region` 
    ON (`at_billing_region`.`entity_id` = `at_default_billing`.`value`) 
    AND (`at_billing_region`.`attribute_id` = '28')  
LEFT JOIN 
    `customer_address_entity_varchar` AS `at_billing_country_id` 
    ON (`at_billing_country_id`.`entity_id` = `at_default_billing`.`value`) 
    AND (`at_billing_country_id`.`attribute_id` = '27')  
LEFT JOIN 
    `customer_address_entity_varchar` AS `at_company` 
    ON (`at_company`.`entity_id` = `at_default_billing`.`value`) 
    AND (`at_company`.`attribute_id` = '24')  
LEFT JOIN 
    `customer_entity_varchar` AS `at_lastname` 
    ON (`at_lastname`.`entity_id` = `e`.`entity_id`) 
    AND (`at_lastname`.`attribute_id` = '7')  
LEFT JOIN 
    `customer_entity_varchar` AS `at_phone` 
    ON (`at_phone`.`entity_id` = `e`.`entity_id`) 
    AND (`at_phone`.`attribute_id` = '136')  
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...