Как оптимизировать запрос на выборку с помощью инструкций case? - PullRequest
2 голосов
/ 20 июня 2019

У меня есть 3 таблицы более 1 000 000+ записей.Мой запрос на выбор выполняется в течение нескольких часов.Как это оптимизировать?Я новичок.

Я попытался добавить индекс для name, но загрузка заняла несколько часов.

Вот так,

ALTER TABLE table2 ADD INDEX(name);

и вот так же,

CREATE INDEX INDEX1 table2(name);

SELECT MS.*, P.Counts FROM 
(SELECT M.*, 
TIMESTAMPDIFF(YEAR, M.date, CURDATE()) AS age,               
CASE V.name 
WHEN 'text' THEN  M.name 
WHEN V.name IS NULL THEN M.name 
ELSE V.name 
END col1  
FROM table1 M 
LEFT JOIN table2 V ON M.id=V.id) AS MS
LEFT JOIN 
(select E.id, count(E.id) Counts 
from table3 E
where E.field2 = 'value1' 
group by E.id) AS P
ON MS.id=P.id;

Explain <above query>; 

вывод:

+----+-------------+------------+------------+-------+---------------------------------------------+------------------+---------+------------------------+---------+----------+-----------------------------------------------------------------+
| id | select_type | table      | partitions | type  | possible_keys                               | key              | key_len | ref                    | rows    | filtered | Extra                                                           |
+----+-------------+------------+------------+-------+---------------------------------------------+------------------+---------+------------------------+---------+----------+-----------------------------------------------------------------+
|  1 | PRIMARY     | M          | NULL       | ALL   | NULL                                        | NULL             | NULL    | NULL                   |  344763 |   100.00 | NULL                                                            |
|  1 | PRIMARY     | <derived3> | NULL       | ref   | <auto_key0>                                 | <auto_key0>      | 8       | CP.M.id |      10 |   100.00 | NULL                                                            |
|  1 | PRIMARY     | V          | NULL       | index | NULL                                        | INDEX1           | 411     | NULL                   | 1411083 |   100.00 | Using where; Using index; Using join buffer (Block Nested Loop) |
|  3 | DERIVED     | E          | NULL       | ref   | PRIMARY,f2,f3                 | f2| 43      | const                  |  966442 |   100.00 | Using index                                                     |
+----+-------------+------------+------------+-------+---------------------------------------------+------------------+---------+------------------------+---------+----------+-----------------------------------------------------------------+

Я ожидаю получить результат менее чем за 1 мин.

Запрос с отступом для ясности.

SELECT MS.*, P.Counts
  FROM  (
           SELECT M.*, 
                  TIMESTAMPDIFF(YEAR, M.date, CURDATE()) AS age,               
             CASE V.name 
                  WHEN 'text' THEN  M.name 
                  WHEN V.name IS NULL THEN M.name 
                  ELSE V.name 
                  END col1  
             FROM table1 M 
             LEFT JOIN table2 V ON M.id=V.id
      ) AS MS
  LEFT JOIN ( 
                  select E.id, count(E.id) Counts 
                   from table3 E
                   where E.field2 = 'value1' 
                   group by E.id
    ) AS P ON MS.id=P.id;

Ответы [ 3 ]

1 голос
/ 20 июня 2019

Ваш запрос не имеет предиката фильтрации, поэтому он по существу извлекает все строки. Это более 1 000 000 строк из table1. Затем он соединяется с table2, а затем с другим табличным выражением / производной таблицей.

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

Может быть, вам нужно переосмыслить процесс. Вам действительно нужно обрабатывать миллионы строк одновременно в интерактивном режиме? Будет ли пользователь читать миллион строк на веб-странице?

0 голосов
/ 20 июня 2019

Подзапросы не всегда хорошо оптимизированы.

Я думаю, вы можете сгладить что-то вроде:

SELECT  M.*, V.*,
        TIMESTAMPDIFF(YEAR, M.date, CURDATE()) AS age,
        CASE V.name WHEN 'text'          THEN M.name
                    WHEN V.name IS NULL  THEN M.name
                                         ELSE V.name  END col1,
        ( SELECT COUNT(*) FROM table3 WHERE field2 = 'value1' AND id = x.id
        ) AS Counts
    FROM table1 AS M
    LEFT JOIN table2 AS V  ON M.id = V.id

У меня могут быть некоторые части, не совсем правильные; посмотрим, сможете ли вы заставить эту формулировку работать.

0 голосов
/ 20 июня 2019

Для начала вы возвращаете тот же результат для 'col1' в случае, если v.name имеет значение null или v.name! = 'Text'. Тем не менее, вы можете включить это дополнительное условие в соединение с таблицей 2 и использовать функцию IFNULL.

Если вы фильтруете table3 по field2, вы, вероятно, могли бы создать индекс по таблице 3, который включает field2.

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

Кроме того, я не понимаю, почему вам нужно объединить первое объединение в 'MS', вы можете легко выполнить все объединения за один раз:

SELECT 
    M.*, 
    TIMESTAMPDIFF(YEAR, M.date, CURDATE()) AS age,               
    IFNULL(V.name, M.name) as col1,
    P.Counts 
FROM table1 M 
LEFT JOIN table2 V ON M.id=V.id AND V.name <> 'text'
LEFT JOIN 
(SELECT 
    E.id, 
    COUNT(E.id) Counts 
FROM table3 E
WHERE E.field2 = 'value1' 
GROUP BY E.id) AS P ON M.id=P.id;

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

Я включил эту дополнительную информацию после вашего комментария.

Я упомянул кластерный индекс, но согласно официальной документации по индексам здесь

Когда вы определяете ПЕРВИЧНЫЙ КЛЮЧ в своей таблице, InnoDB использует его в качестве кластеризованного индекса. Поэтому, если у вас уже есть первичный ключ, вам больше ничего не нужно делать. В документации также указано, что вы должны определить первичный ключ для каждой создаваемой таблицы.

Если у вас нет первичного ключа. Вот фрагмент кода, который вы запросили.

ALTER TABLE table1 ADD CONSTRAINT pk_table1
 PRIMARY KEY CLUSTERED (id);

ВНИМАНИЕ: Имейте в виду, что создание кластеризованного индекса является большой операцией для таблиц, подобных вашей, с тонами данных. Это не то, что вы хотите сделать без планирования на рабочем сервере. Эта операция также займет много времени, и таблица будет заблокирована во время процесса.

...