Чи є спосіб оптимізувати сортування за стовпцями об'єднаних таблиць?


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()критеріїв сортування, якими ви користуєтесь.

Однак, якщо ви проектуєте та використовуєте Materialized View, ви зможете використовувати алгоритм швидкого сортування.

Щоб переглянути деталі, визначені для MySQL 5.5методів сортування, див: http://dev.mysql.com/doc/refman/5.5/uk/order-by-optimization.html

Щоб MySQL 5.5(у цьому прикладі) збільшити ORDER BYшвидкість, якщо ви не можете MySQLвикористовувати індекси, а не додаткову фазу сортування, спробуйте наступні стратегії:

• Збільшити sort_buffer_sizeзначення змінної.

• Збільшити read_rnd_buffer_sizeзначення змінної.

• Використовуйте менше оперативної пам’яті на рядок, оголошуючи стовпці лише настільки великими, скільки потрібно для фактичних значень для зберігання. [Наприклад, Зменшіть варчар (256) до варчара (ActualLongestString)]

• Змініть tmpdirсистемну змінну, щоб вказати на виділену файлову систему з великою кількістю вільного місця. (Інші деталі пропонуються за посиланням вище.)

Більш детально надано в MySQL 5.7документації для збільшення ORDERшвидкості, деякі з яких можуть бути трохи покращені :

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

Матеріалізовані погляди - різний підхід до сортування приєднаних таблиць

Ви нагадали на Матеріалізовані перегляди своїм запитанням про використання тригерів. MySQL не має вбудованої функції для створення Materialized View, але у вас є необхідні інструменти. Використовуючи тригери для поширення навантаження, ви можете підтримувати Матеріалізований вигляд до цього моменту.

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

Оскільки ви будуєте таблицю з індексом , то Матеріалізований вигляд при запиті може використовувати метод найшвидшого сортування : Використовувати метод доступу на основі індексу, який виробляє впорядкований вихід

Оскільки MySQL 5.5використовує тригери для підтримання Materialized View , вам також знадобиться процес, сценарій або збережена процедура для створення початкового Materialized View .

Але це, очевидно, занадто важкий процес, який запускається після кожного оновлення до базових таблиць, де ви керуєте даними. Саме тут активізуються тригери для оновлення даних у міру внесення змін. Таким чином , кожен 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. Ви вже витратили додатковий процесор та IO, щоб підготувати дані до вашого SELECT.
  2. В індексі Materialized View можна використовувати метод найшвидшого сортування, доступний MySQL, а саме використовувати метод доступу на основі індексу, який виробляє впорядкований вихід .

Залежно від ваших обставин і того, як ви ставитесь до загального процесу, ви можете хотіти щомісяця перебудовувати Матеріалізовані погляди протягом повільного періоду.

Примітка: у Microsoft SQL Server Матеріалізованих представленнях посилаються на індексовані представлення та автоматично оновлюються на основі метаданих індексованого перегляду .


6

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

  1. Ви використовуєте UTF8
  2. Для сортування ви використовуєте великі поля varchar (255)

Це означає, що ваш тимчасовий файл таблиці та сортування може бути досить великим, оскільки при створенні тимчасової таблиці поля створюються на довжину MAX, а при сортуванні записів усі мають довжину MAX (а UTF8 - 3 байти на символ). Вони також, ймовірно, виключають використання тимчасової таблиці в пам'яті. Для отримання додаткової інформації див. Внутрішні таблиці темп .

Границя також нам не приносить користі, оскільки нам потрібно матеріалізувати і замовити весь набір результатів, перш ніж ми дізнаємося, що таке перші 3 ряди.

Ви намагалися перемістити свій tmpdir до файлової системи tmpfs ? Якщо / tmp вже не використовує tmpfs (MySQL tmpdir=/tmpза замовчуванням використовує * nix), ви можете використовувати / dev / shm безпосередньо. У вашому файлі my.cnf:

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

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

Це може призвести до величезних змін. Якщо ви, ймовірно, потрапите під тиск пам'яті в системі, ви, мабуть, хочете обмежити розмір, хоча (як правило, Linux дистрибутує обмеження tmpfs на 50% від загальної оперативної пам’яті за замовчуванням), щоб уникнути заміни сегментів пам'яті на диск, або навіть гірша ситуація з ООМ . Це можна зробити, відредагувавши рядок у /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.