Есть ли способ оптимизировать сортировку по столбцам объединяемых таблиц?


10

Это мой медленный запрос:

SELECT `products_counts`.`cid`
FROM
  `products_counts` `products_counts`

  LEFT OUTER JOIN `products` `products` ON (
  `products_counts`.`product_id` = `products`.`id`
  )
  LEFT OUTER JOIN `trademarks` `trademark` ON (
  `products`.`trademark_id` = `trademark`.`id`
  )
  LEFT OUTER JOIN `suppliers` `supplier` ON (
  `products_counts`.`supplier_id` = `supplier`.`id`
  )
WHERE
  `products_counts`.product_id IN
  (159, 572, 1075, 1102, 1145, 1162, 1660, 2355, 2356, 2357, 3236, 6471, 6472, 6473, 8779, 9043, 9095, 9336, 9337, 9338, 9445, 10198, 10966, 10967, 10974, 11124, 11168, 16387, 16689, 16827, 17689, 17920, 17938, 17946, 17957, 21341, 21352, 21420, 21421, 21429, 21544, 27944, 27988, 30194, 30196, 30230, 30278, 30699, 31306, 31340, 32625, 34021, 34047, 38043, 43743, 48639, 48720, 52453, 55667, 56847, 57478, 58034, 61477, 62301, 65983, 66013, 66181, 66197, 66204, 66407, 66844, 66879, 67308, 68637, 73944, 74037, 74060, 77502, 90963, 101630, 101900, 101977, 101985, 101987, 105906, 108112, 123839, 126316, 135156, 135184, 138903, 142755, 143046, 143193, 143247, 144054, 150164, 150406, 154001, 154546, 157998, 159896, 161695, 163367, 170173, 172257, 172732, 173581, 174001, 175126, 181900, 182168, 182342, 182858, 182976, 183706, 183902, 183936, 184939, 185744, 287831, 362832, 363923, 7083107, 7173092, 7342593, 7342594, 7342595, 7728766)
ORDER BY
  products_counts.inflow ASC,
  supplier.delivery_period ASC,
  trademark.sort DESC,
  trademark.name ASC
LIMIT
  0, 3;

Среднее время запроса составляет 4,5 с в моем наборе данных, и это недопустимо.

Решения, которые я вижу:

Добавьте все столбцы из предложения заказа в products_countsтаблицу. Но у меня есть ~ 10 типов заказов в приложении, поэтому я должен создать много столбцов и индексов. Плюс products_countsочень интенсивно обновляет / вставляет / удаляет, поэтому мне нужно немедленно обновить все столбцы, связанные с заказами (используя триггеры?).

Есть ли другое решение?

Объясните:

+----+-------------+-----------------+--------+---------------------------------------------+------------------------+---------+----------------------------------+------+----------------------------------------------+
| id | select_type | table           | type   | possible_keys                               | key                    | key_len | ref                              | rows | Extra                                        |
+----+-------------+-----------------+--------+---------------------------------------------+------------------------+---------+----------------------------------+------+----------------------------------------------+
|  1 | SIMPLE      | products_counts | range  | product_id_supplier_id,product_id,pid_count | product_id_supplier_id | 4       | NULL                             |  227 | Using where; Using temporary; Using filesort |
|  1 | SIMPLE      | products        | eq_ref | PRIMARY                                     | PRIMARY                | 4       | uaot.products_counts.product_id  |    1 |                                              |
|  1 | SIMPLE      | trademark       | eq_ref | PRIMARY                                     | PRIMARY                | 4       | uaot.products.trademark_id       |    1 |                                              |
|  1 | SIMPLE      | supplier        | eq_ref | PRIMARY                                     | PRIMARY                | 4       | uaot.products_counts.supplier_id |    1 |                                              |
+----+-------------+-----------------+--------+---------------------------------------------+------------------------+---------+----------------------------------+------+----------------------------------------------+

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

CREATE TABLE `products_counts` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `product_id` int(11) unsigned NOT NULL,
  `supplier_id` int(11) unsigned NOT NULL,
  `count` int(11) unsigned NOT NULL,
  `cid` varchar(64) NOT NULL,
  `inflow` varchar(10) NOT NULL,
  `for_delete` tinyint(1) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `cid` (`cid`),
  UNIQUE KEY `product_id_supplier_id` (`product_id`,`supplier_id`),
  KEY `product_id` (`product_id`),
  KEY `count` (`count`),
  KEY `pid_count` (`product_id`,`count`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `products` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `external_id` varchar(36) NOT NULL,
  `name` varchar(255) NOT NULL,
  `category_id` int(11) unsigned NOT NULL,
  `trademark_id` int(11) unsigned NOT NULL,
  `photo` varchar(255) NOT NULL,
  `sort` int(11) unsigned NOT NULL,
  `otech` tinyint(1) unsigned NOT NULL,
  `not_liquid` tinyint(1) unsigned NOT NULL DEFAULT '0',
  `applicable` varchar(255) NOT NULL,
  `code_main` varchar(64) NOT NULL,
  `code_searchable` varchar(128) NOT NULL,
  `total` int(11) unsigned NOT NULL,
  `slider` int(11) unsigned NOT NULL,
  `slider_title` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `external_id` (`external_id`),
  KEY `category_id` (`category_id`),
  KEY `trademark_id` (`trademark_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `trademarks` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `external_id` varchar(36) NOT NULL,
  `name` varchar(255) NOT NULL,
  `country_id` int(11) NOT NULL,
  `sort` int(11) unsigned NOT NULL DEFAULT '0',
  `sort_list` int(10) unsigned NOT NULL DEFAULT '0',
  `is_featured` tinyint(1) unsigned NOT NULL,
  `is_direct` tinyint(1) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `external_id` (`external_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `suppliers` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `external_id` varchar(36) NOT NULL,
  `code` varchar(64) NOT NULL,
  `name` varchar(255) NOT NULL,
  `delivery_period` tinyint(1) unsigned NOT NULL,
  `is_default` tinyint(1) unsigned NOT NULL,
  PRIMARY KEY (`id`),
  KEY `external_id` (`external_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Информация о сервере MySQL:

mysqld  Ver 5.5.45-1+deb.sury.org~trusty+1 for debian-linux-gnu on i686 ((Ubuntu))

3
Можете ли вы предоставить SQL Fiddle с индексами, схемами таблиц и тестовыми данными? Также, каково ваше целевое время? Вы хотите получить его за 3 секунды, 1 секунду, 50 миллисекунд? Сколько записей у вас в разных таблицах 1k, 100k, 100M?
Эрик

Если те поля, по которым вы сортируете, не проиндексированы, а набор данных действительно большой, не могли бы вы рассмотреть проблему sort_buffer_size? Вы можете попробовать изменить значение в сеансе и запустить запрос, чтобы увидеть, улучшится ли он.
Брайан Эфтинг

Вы пытались добавить индекс (inflow, product_id)?
ypercubeᵀᴹ

Убедитесь, что у вас есть приличный innodb_buffer_pool_size. Обычно около 70% доступной оперативной памяти - это хорошо.
Рик Джеймс

Ответы:


6

Просмотр определений таблиц показывает, что у вас есть индексы, совпадающие для всех задействованных таблиц. Это должно привести к тому, что объединения происходят как можно быстрее в рамках MySQL'sлогики объединения.

Однако сортировка из нескольких таблиц является более сложной.

В 2007 году Сергей Петруня описал 3 MySQLалгоритма сортировки в порядке скорости по MySQLадресу: http://s.petrunia.net/blog/?m=201407

  1. Используйте метод доступа на основе индекса, который производит упорядоченный вывод
  2. Используйте filesort()на 1-й непостоянной таблице
  3. Положите результат объединения во временную таблицу и используйте filesort()ее

Из приведенных выше определений и объединений таблиц видно, что вы никогда не получите самую быструю сортировку . Это означает, что вы будете зависеть от filesort()критериев сортировки, которые вы используете.

Однако, если вы разработаете и используете материализованное представление, вы сможете использовать самый быстрый алгоритм сортировки.

Чтобы увидеть подробности, определенные для MySQL 5.5методов сортировки, смотрите: http://dev.mysql.com/doc/refman/5.5/en/order-by-optimization.html

Чтобы MySQL 5.5(в этом примере) увеличить ORDER BYскорость, если вы не можете MySQLиспользовать индексы вместо дополнительной фазы сортировки, попробуйте следующие стратегии:

• Увеличьте sort_buffer_sizeзначение переменной.

• Увеличьте read_rnd_buffer_sizeзначение переменной.

• Используйте меньше ОЗУ на строку, объявляя столбцы настолько большими, насколько это необходимо для хранения фактических значений. [Например, уменьшить varchar (256) до varchar (ActualLongestString)]

• Измените tmpdirсистемную переменную так, чтобы она указывала на выделенную файловую систему с большим количеством свободного места. (Другие детали предлагаются по ссылке выше.)

В документации приведена более подробная MySQL 5.7информация об увеличении ORDERскорости, некоторые из которых могут быть слегка обновлены :

http://dev.mysql.com/doc/refman/5.7/en/order-by-optimization.html

Материализованные представления - другой подход к сортировке объединенных таблиц

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

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

Поскольку вы создаете таблицу, которая будет иметь индекс , то при выполнении запроса в Materialized View может использоваться самый быстрый метод сортировки : используйте метод доступа на основе индекса, который производит упорядоченный вывод

Поскольку MySQL 5.5для поддержки Материализованного представления используются триггеры , вам также потребуется процесс, сценарий или хранимая процедура для создания исходного Материализованного представления .

Но это, очевидно, слишком тяжелый процесс для запуска после каждого обновления базовых таблиц, в которых вы управляете данными. Именно здесь вступают в действие триггеры, чтобы обновлять данные по мере внесения изменений. Таким образом, каждый insert, updateи deleteбудет распространять свои изменения, используя ваши триггеры, в Материализованное представление .

У организации FROMDUAL по адресу http://www.fromdual.com/ есть пример кода для поддержки материализованного представления . Поэтому вместо того, чтобы писать свои собственные образцы, я укажу вам их образцы:

http://www.fromdual.com/mysql-materialized-views

Пример 1: Построение материализованного представления

DROP TABLE sales_mv;
CREATE TABLE sales_mv (
    product_name VARCHAR(128)  NOT NULL
  , price_sum    DECIMAL(10,2) NOT NULL
  , amount_sum   INT           NOT NULL
  , price_avg    FLOAT         NOT NULL
  , amount_avg   FLOAT         NOT NULL
  , sales_cnt    INT           NOT NULL
  , UNIQUE INDEX product (product_name)
);

INSERT INTO sales_mv
SELECT product_name
    , SUM(product_price), SUM(product_amount)
    , AVG(product_price), AVG(product_amount)
    , COUNT(*)
  FROM sales
GROUP BY product_name;

Это дает вам материализованное представление в момент обновления. Однако, поскольку у вас есть быстро движущаяся база данных, вы также хотите поддерживать этот вид как можно более актуальным.

Поэтому для затронутых таблиц базовых данных необходимо наличие триггеров для распространения изменений из базовой таблицы в таблицу с материализованным представлением . В качестве одного примера:

Пример 2. Вставка новых данных в материализованное представление

DELIMITER $$

CREATE TRIGGER sales_ins
AFTER INSERT ON sales
FOR EACH ROW
BEGIN

  SET @old_price_sum = 0;
  SET @old_amount_sum = 0;
  SET @old_price_avg = 0;
  SET @old_amount_avg = 0;
  SET @old_sales_cnt = 0;

  SELECT IFNULL(price_sum, 0), IFNULL(amount_sum, 0), IFNULL(price_avg, 0)
       , IFNULL(amount_avg, 0), IFNULL(sales_cnt, 0)
    FROM sales_mv
   WHERE product_name = NEW.product_name
    INTO @old_price_sum, @old_amount_sum, @old_price_avg
       , @old_amount_avg, @old_sales_cnt
  ;

  SET @new_price_sum = @old_price_sum + NEW.product_price;
  SET @new_amount_sum = @old_amount_sum + NEW.product_amount;
  SET @new_sales_cnt = @old_sales_cnt + 1;
  SET @new_price_avg = @new_price_sum / @new_sales_cnt;
  SET @new_amount_avg = @new_amount_sum / @new_sales_cnt;

  REPLACE INTO sales_mv
  VALUES(NEW.product_name, @new_price_sum, @new_amount_sum, @new_price_avg
       , @new_amount_avg, @new_sales_cnt)
  ;

END;
$$
DELIMITER ;

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

ПОСЛЕДНЕЕ: Как это делает сортировку соединенных таблиц быстрее?

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

Если затраты на обслуживание данных не слишком велики, то вы тратите некоторые ресурсы (CPU / IO / и т. Д.) На каждое соответствующее изменение данных, чтобы поддерживать материализованное представление, и, таким образом, данные индекса являются актуальными и легко доступными. Поэтому выбор будет быстрее, так как вы:

  1. Уже потрачены дополнительные ресурсы процессора и ввода-вывода, чтобы подготовить данные для вашего SELECT.
  2. Индекс в Материализованном Представлении может использовать самый быстрый метод сортировки, доступный для MySQL, а именно Использовать метод доступа на основе индекса, который производит упорядоченный вывод .

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

Примечание. В Microsoft SQL Server материализованных представлениях упоминаются индексированные представления и они автоматически обновляются на основе метаданных индексированного представления .


6

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

  1. Вы используете UTF8
  2. Вы используете несколько больших полей varchar (255) для сортировки

Это означает, что ваша временная таблица и файл сортировки могут быть довольно большими, так как при создании временной таблицы поля создаются с длиной MAX, а при сортировке все записи имеют длину MAX (а UTF8 составляет 3 байта на символ). Это также, вероятно, исключает использование временной таблицы в памяти. Для получения дополнительной информации см. Детали внутренних временных таблиц .

LIMIT также не приносит нам пользы, так как нам нужно материализовать и упорядочить весь набор результатов, прежде чем мы узнаем, каковы первые 3 строки.

Вы пытались переместить ваш tmpdir в файловую систему tmpfs ? Если / tmp еще не использует tmpfs (MySQL tmpdir=/tmpпо умолчанию использует * nix), то вы можете использовать / dev / shm напрямую. В вашем файле my.cnf:

[mysqld]
...
tmpdir=/dev/shm  

Тогда вам нужно будет перезапустить mysqld.

Это может иметь огромное значение. Если вы, вероятно, испытываете нехватку памяти в системе, вы, возможно, захотите ограничить размер (обычно linux distros cap tmpfs на 50% от общего объема ОЗУ по умолчанию), чтобы избежать выгрузки сегментов памяти на диск, или даже хуже ситуации с OOM . Вы можете сделать это, отредактировав строку в /etc/fstab:

tmpfs                   /dev/shm                tmpfs   rw,size=2G,noexec,nodev,noatime,nodiratime        0 0

Вы можете изменить его размер "онлайн" тоже. Например:

mount -o remount,size=2G,noexec,nodev,noatime,nodiratime /dev/shm

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

Удачи!


Спасибо за Ваш ответ. Перемещение tmpdir в tmpfs дало хороший прирост производительности.
Станислав Гамаюнов
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.