Можно ли сделать этот подзапрос для использования индекса? - PullRequest
0 голосов
/ 17 февраля 2019

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

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

mysql> describe name_servers;
+-----------------------+------------------+------+-----+---------+----------------+
| Field                 | Type             | Null | Key | Default | Extra          |
+-----------------------+------------------+------+-----+---------+----------------+
| server_name           | varchar(255)     | YES  |     | NULL    |                |
| file_date             | int(10) unsigned | YES  |     | NULL    |                |
| file_size             | int(10) unsigned | YES  |     | NULL    |                |
| time                  | int(10) unsigned | YES  | MUL | NULL    |                |
| poll_id               | int(11)          | NO   | PRI | NULL    | auto_increment |
+-----------------------+------------------+------+-----+---------+----------------+
5 rows in set (0.01 sec)


mysql> show index from name_servers;
+--------------+------------+--------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table        | Non_unique | Key_name                 | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+--------------+------------+--------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| name_servers |          0 | PRIMARY                  |            1 | poll_id     | A         |     3523218 |     NULL | NULL   |      | BTREE      |         |               |
| name_servers |          0 | index_time_servername    |            1 | time        | A         |      503316 |     NULL | NULL   | YES  | BTREE      |         |               |
| name_servers |          0 | index_time_servername    |            2 | server_name | A         |     3523218 |     NULL | NULL   | YES  | BTREE      |         |               |
+--------------+------------+--------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
3 rows in set (0.00 sec)

Я должен отслеживать изменения размера файла, чтобы определить, уменьшается ли файл на> 20% в течение любого 48-часового периода.Обычно я пытался бы сделать это с функциями MySQL Window, но они не поддерживаются версией MySQL на моем сервере (5.6.37 - которую я не контролирую, так как сервер не управляется моей командой).В настоящее время я получаю текущий размер и максимальный размер (за последние 48 часов) с помощью внешнего запроса, который находит размер файла в текущей строке, и внутреннего подзапроса, который находит самый большой размер файла за предыдущие 48 часов (172 800 секунд).) количество строк:

mysql> select name_servers_outside.server_name,
    -> name_servers_outside.file_size,
    -> name_servers_outside.file_date,
    -> name_servers_outside.time,
    -> (select max(file_size) from name_servers where time > (name_servers_outside.time - 172800) and server_name = 'example_server') as max_file_size
    -> from name_servers as name_servers_outside
    -> where name_servers_outside.server_name = 'example_server'
    -> and name_servers_outside.time > (UNIX_TIMESTAMP() - 172800)
    -> limit 10;
+-------------------+-------------------+-------------------+------------+-----------------------+
| server_name       | file_size         | file_date         | time       | max_file_size         |
+-------------------+-------------------+-------------------+------------+-----------------------+
| example_server    |           1159544 |        1550382945 | 1550382985 |               1159580 |
| example_server    |           1159544 |        1550382945 | 1550383195 |               1159580 |
| example_server    |           1159544 |        1550382945 | 1550383255 |               1159580 |
| example_server    |           1159544 |        1550382945 | 1550383316 |               1159580 |
| example_server    |           1159544 |        1550382945 | 1550383376 |               1159580 |
| example_server    |           1159544 |        1550382945 | 1550383435 |               1159580 |
| example_server    |           1159544 |        1550382945 | 1550383496 |               1159580 |
| example_server    |           1159544 |        1550382945 | 1550383555 |               1159580 |
| example_server    |           1159544 |        1550382945 | 1550383616 |               1159580 |
| example_server    |           1159544 |        1550382945 | 1550383676 |               1159580 |
+-------------------+-------------------+-------------------+------------+-----------------------+
10 rows in set (16.11 sec)

Простое извлечение этих 10 строк заняло 16 секунд, и в производственном процессе этот запрос должен будет получить более 150 строк.Внутренний запрос выполняет полное сканирование всех 3 миллионов + строк таблицы с сообщением «Диапазон проверен для каждой записи (карта индекса: 0x2)»:

mysql> explain
    -> select name_servers_outside.server_name,
    -> name_servers_outside.file_size,
    -> name_servers_outside.file_date,
    -> name_servers_outside.time,
    -> (select max(file_size) from name_servers where time > (name_servers_outside.time - 172800) and server_name = 'example_server') as max_file_size
    -> from name_servers as name_servers_outside
    -> where name_servers_outside.server_name = 'example_server'
    -> and name_servers_outside.time > (UNIX_TIMESTAMP() - 172800);
+----+--------------------+----------------------+-------+--------------------------+--------------------------+---------+------+---------+------------------------------------------------+
| id | select_type        | table                | type  | possible_keys            | key                      | key_len | ref  | rows    | Extra                                          |
+----+--------------------+----------------------+-------+--------------------------+--------------------------+---------+------+---------+------------------------------------------------+
|  1 | PRIMARY            | name_servers_outside | range | index_time_servername    | index_time_servername    | 5       | NULL |   47302 | Using index condition; Using MRR               |
|  2 | DEPENDENT SUBQUERY | name_servers         | ALL   | index_time_servername    | NULL                     | NULL    | NULL | 3533883 | Range checked for each record (index map: 0x2) |
+----+--------------------+----------------------+-------+--------------------------+--------------------------+---------+------+---------+------------------------------------------------+
2 rows in set (0.01 sec)

Проблемная частьКажется, что это:

time > (name_servers_outside.time - 172800)

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

time > (UNIX_TIMESTAMP() - 172800)

Модифицированный запрос:

mysql> select name_servers_outside.server_name,
    -> name_servers_outside.file_size,
    -> name_servers_outside.file_date,
    -> name_servers_outside.time,
    -> (select max(file_size) from name_servers where time > (UNIX_TIMESTAMP() - 172800) and server_name = 'example_server') as max_file_size
    -> from name_servers as name_servers_outside
    -> where name_servers_outside.server_name = 'example_server'
    -> and name_servers_outside.time > (UNIX_TIMESTAMP() - 172800)
    -> limit 10;
+--------------------+-------------------+-------------------+------------+-----------------------+
| server_name        | file_size         | file_date         | time       | max_file_size         |
+--------------------+-------------------+-------------------+------------+-----------------------+
| example_server     |           1159544 |        1550382945 | 1550382985 |               1159580 |
| example_server     |           1159544 |        1550382945 | 1550383195 |               1159580 |
| example_server     |           1159544 |        1550382945 | 1550383255 |               1159580 |
| example_server     |           1159544 |        1550382945 | 1550383316 |               1159580 |
| example_server     |           1159544 |        1550382945 | 1550383376 |               1159580 |
| example_server     |           1159544 |        1550382945 | 1550383435 |               1159580 |
| example_server     |           1159544 |        1550382945 | 1550383496 |               1159580 |
| example_server     |           1159544 |        1550382945 | 1550383555 |               1159580 |
| example_server     |           1159544 |        1550382945 | 1550383616 |               1159580 |
| example_server     |           1159544 |        1550382945 | 1550383676 |               1159580 |
+--------------------+-------------------+-------------------+------------+-----------------------+
10 rows in set (0.01 sec)


mysql> explain
    -> select name_servers_outside.server_name,
    -> name_servers_outside.file_size,
    -> name_servers_outside.file_date,
    -> name_servers_outside.time,
    -> (select max(file_size) from name_servers where time > (UNIX_TIMESTAMP() - 172800) and server_name = 'example_server') as max_file_size
    -> from name_servers as name_servers_outside
    -> where name_servers_outside.server_name = 'example_server'
    -> and name_servers_outside.time > (UNIX_TIMESTAMP() - 172800)
    -> limit 10;
+----+-------------+----------------------+-------+--------------------------+--------------------------+---------+------+-------+----------------------------------+
| id | select_type | table                | type  | possible_keys            | key                      | key_len | ref  | rows  | Extra                            |
+----+-------------+----------------------+-------+--------------------------+--------------------------+---------+------+-------+----------------------------------+
|  1 | PRIMARY     | name_servers_outside | range | index_time_servername    | index_time_servername    | 5       | NULL | 49042 | Using index condition; Using MRR |
|  2 | SUBQUERY    | name_servers         | range | index_time_servername    | index_time_servername    | 5       | NULL | 49042 | Using index condition; Using MRR |
+----+-------------+----------------------+-------+--------------------------+--------------------------+---------+------+-------+----------------------------------+
2 rows in set (0.00 sec)

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

Теперь проблема, которую я пытаюсь решить, заключается в том, что мне нужно получитьнаибольшее значение file_size за 48 часов, предшествующих каждой строке.Поэтому каждая строка имеет свой уникальный временной диапазон для вычисления "max (file_size)".Затем он будет использоваться для расчета процентного изменения размера файла.Как упоминалось выше, я обычно хочу использовать оконные функции для этого, но они не поддерживаются моей версией MySQL (5.6.37), и я не могу обновиться до 8.0, так как я не являюсь владельцем этого сервера.

Как всегда, любые предложения приветствуются.Спасибо за чтение!

Ответы [ 2 ]

0 голосов
/ 27 февраля 2019

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

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

Вот что решение в итоге привело к:

  1. По предложению Dazz Knowles, я заменил подзапросс внутренним соединением, и это очистило код и упростило понимание.
  2. Как предложил Progman, я изменил свой индекс на индекс одного столбца в поле "имя_сервера".
  3. Я переместил строки, включенные в этот запрос, в их собственную таблицу, упрощая тем самым рабочий набор столбцов.
  4. Я уменьшил частоту выборки приложения, записывающего строки в таблицу, с 1 точки данных (1 строки) в минутудо 1 точки данных (1 строка) в час, тем самым сокращая рабочий набор строк до 1/60 предыдущего количества.Благодаря комбинированным эффектам 1-4 время выполнения запроса сократилось с нескольких минут до нескольких миллисекунд.
  5. Ранее я пытался вычислить «max_file_size» во время выполнения, когда клиент приложения отправлял запрос.на сервер MySQL одновременно для ~ 100 разных серверов и 3 разных файлов на каждом сервере (~ 300 экземпляров запроса, выполняемого каждый раз при обновлении приложения).Это сохраняло центральный процессор сервера MySQL на уровне 100%, что нецелесообразно для реального использования, особенно для нескольких конечных пользователей, одновременно использующих клиентское приложение.Я переключился на выполнение запроса только из сценариев на стороне сервера и только при добавлении новых строк.Таким образом, запрос выполняется один раз в час, вычисляя ~ 300 значений max_file_size за несколько миллисекунд.Затем он записывает max_file_size как статический столбец в таблицу MySQL.Ни одно из значений, от которых зависит max_file_size, никогда не должно изменяться, поэтому я не беспокоюсь о необходимости повторного запуска запроса для обновления max_file_size после его записи для определенной строки.Клиентская часть приложения теперь только читает данные из MySQL;он больше не пытается отправить запрос для вычисления max_file_size.Оглядываясь назад, кажется, что этот подход должен был быть очевиден с самого начала, но иногда сначала нужно сделать это неправильно, чтобы понять, что делает правильный подход правильным.
0 голосов
/ 17 февраля 2019

Я бы сначала попытался добавить file_size к вашему индексу index_time_servername, но я подозреваю, что реальная проблема заключается в том, что вам нужно использовать name_servers_outside.time внутри вашего подзапроса, который из другого псевдонима, вероятно, сбивает с толку планировщика запросов.

Итак, как насчет потери подзапроса и присоединения таблицы к себе, где время находится между временем и временем - 48 часов назад?

Что-то вроде ...

SELECT
  name_servers_outside.server_name,
  name_servers_outside.file_size,
  name_servers_outside.file_date,
  name_servers_outside.time,
  MAX(previous.file_size) AS max_file_size
FROM
   name_servers AS ns
INNER JOIN name_servers AS previous 
   ON previous.time BETWEEN (ns.time - 172800) AND (ns.time - 1)
WHERE 
   ns.server_name = 'example_server'
   AND ns.time > (UNIX_TIMESTAMP() - 172800)
GROUP BY
   ns.server_name,
   ns.file_size,
   ns.file_date,
   ns.time
LIMIT 10;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...