Избегайте сканирования таблицы и используйте индекс в запросе - PullRequest
0 голосов
/ 28 мая 2019

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

Я сделал много изменений в своем запросе, но не нашел способа устранить выполнение полной таблицысканировать и вместо этого использовать индекс.Когда я использую инструмент, похожий на EXPLAIN, в моем запросе я вижу следующее:

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

Таблица:

CREATE TABLE `indexTable` (
  `id` int(10) unsigned NOT NULL,
  `userId` int(10) unsigned NOT NULL,
  `col1` varbinary(320) NOT NULL,
  `col2` tinyint(3) unsigned NOT NULL,
  `col3` tinyint(3) unsigned NOT NULL,
  `createdAt` bigint(20) unsigned NOT NULL,
  `updatedAt` bigint(20) unsigned NOT NULL,
  `metadata` json NOT NULL,
  PRIMARY KEY (`id`,`userId`,`col1`,`col2`,`col3`),
  KEY `createdAt` (`createdAt`),
  KEY `id_userId_col1_col2_createdAt` (`id`,`userId`,`col1`,`col2`,`createdAt`),
  KEY `col1_col2_createdAt` (`col1`,`col2`,`createdAt`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8

Запрос:

SELECT t1.id, t1.userId, t1.col1, t1.col2, t1.col3, t1.metadata
FROM indexTable as t1
INNER JOIN(
    SELECT col1, col2, MAX(createdAt) AS maxCreatedAt
    FROM indexTable
    WHERE id = ? AND userId = ?
    GROUP BY col1, col2
    ORDER BY maxCreatedAt
    LIMIT 10 OFFSET 0) AS sub
ON t1.col1 = sub.col1
AND t1.col2 = sub.col2
AND t1.createdAt = sub.maxCreatedAt
WHERE t1.id = ? AND t1.userId = ?
ORDER BY t1.createdAt;

PK: id, userId, col1, col2, col3Индекс: createdAt

Объяснение:

{
  "query_block": {
    "select_id": 1,
    "cost_info": {
      "query_cost": "34.50"
    },
    "ordering_operation": {
      "using_temporary_table": true,
      "using_filesort": true,
      "cost_info": {
        "sort_cost": "10.00"
      },
      "nested_loop": [
        {
          "table": {
            "table_name": "sub",
            "access_type": "ALL",
            "rows_examined_per_scan": 10,
            "rows_produced_per_join": 10,
            "filtered": "100.00",
            "cost_info": {
              "read_cost": "10.50",
              "eval_cost": "2.00",
              "prefix_cost": "12.50",
              "data_read_per_join": "3K"
            },
            "used_columns": [
              "col1",
              "col2",
              "maxCreatedAt"
            ],
            "attached_condition": "(`sub`.`maxCreatedAt` is not null)",
            "materialized_from_subquery": {
              "using_temporary_table": true,
              "dependent": false,
              "cacheable": true,
              "query_block": {
                "select_id": 2,
                "cost_info": {
                  "query_cost": "10.27"
                },
                "ordering_operation": {
                  "using_filesort": true,
                  "grouping_operation": {
                    "using_temporary_table": true,
                    "using_filesort": false,
                    "table": {
                      "table_name": "indexTable",
                      "access_type": "ref",
                      "possible_keys": [
                        "PRIMARY",
                        "createdAt",
                        "id_userId_col1_col2_createdAt",
                        "col1_col2_createdAt"
                      ],
                      "key": "PRIMARY",
                      "used_key_parts": [
                        "id",
                        "userId"
                      ],
                      "key_length": "8",
                      "ref": [
                        "const",
                        "const"
                      ],
                      "rows_examined_per_scan": 46,
                      "rows_produced_per_join": 46,
                      "filtered": "100.00",
                      "cost_info": {
                        "read_cost": "1.07",
                        "eval_cost": "9.20",
                        "prefix_cost": "10.27",
                        "data_read_per_join": "16K"
                      },
                      "used_columns": [
                        "id",
                        "userId",
                        "createdAt",
                        "col1",
                        "col2",
                        "col3"
                      ],
                      "attached_condition": "((`MyDB`.`indexTable`.`id` <=> 53) and (`MyDB`.`indexTable`.`userId` <=> 549814))"
                    }
                  }
                }
              }
            }
          }
        },
        {
          "table": {
            "table_name": "t1",
            "access_type": "ref",
            "possible_keys": [
              "PRIMARY",
              "createdAt",
              "id_userId_col1_col2_createdAt",
              "col1_col2_createdAt"
            ],
            "key": "id_userId_col1_col2_createdAt",
            "used_key_parts": [
              "id",
              "userId",
              "col1",
              "col2",
              "createdAt"
            ],
            "key_length": "339",
            "ref": [
              "const",
              "const",
              "sub.col1",
              "sub.col2",
              "sub.maxCreatedAt"
            ],
            "rows_examined_per_scan": 1,
            "rows_produced_per_join": 10,
            "filtered": "100.00",
            "cost_info": {
              "read_cost": "10.00",
              "eval_cost": "2.00",
              "prefix_cost": "24.50",
              "data_read_per_join": "3K"
            },
            "used_columns": [
              "id",
              "userId",
              "createdAt",
              "updatedAt",
              "col1",
              "col2",
              "col3",
              "metadata",
            ]
          }
        }
      ]
    }
  }
}

Этот запрос находит самую последнюю запись в группе col1 и col2, упорядочивает по createdAt и ограничиваетзаписи до 10.

Ответы [ 2 ]

0 голосов
/ 29 мая 2019

Я смог решить эту проблему, обновив свой запрос, добавив id и userId в GROUP BY.Затем я смог объединить два дополнительных столбца и по какой-то причине заставил MySQL использовать правильный индекс.

0 голосов
/ 29 мая 2019

"Производная" таблица (подзапрос) нуждается в этом составном индексе:

INDEX(id, userid,  -- in either order
      col1, col2,  -- in this order
      createdAt)   -- to make it "covering"

С этим индексом , вероятно, не будет выполнять полное сканирование таблицы.Однако, будет включать файловую сортировку.Это связано с тем, что ORDER BY не совпадает с GROUP BY и является совокупностью.

t1 нужно

INDEX(col1, col2,  -- in either order
      createdAt)

sub,maxCreatedAt - опечатка ??

ORDER BY t1.createdAt - еще один необходимый filesort.

Не остерегайтесь файловых сортировок.Особенно, когда есть только 10 строк (как во втором случае).

Не видя SHOW CREATE TABLE, я не могу сказать, касались ли «файловая сортировка» и «временная таблица» диск или это было сделанов ОЗУ.

FORCE INDEX почти всегда плохая идея - даже если это поможет сегодня, это может повредить завтра.

Оптимизатор намеренно (и правильно) будет использовать сканирование таблицы, еслислишком большая часть таблицы должна быть просмотрена - это быстрее, чем подпрыгивание между индексом и данными.

...