Медленный MySQL-запрос с использованием индексов - PullRequest
0 голосов
/ 24 июня 2019

У меня есть таблица поставщиков, которая выглядит следующим образом:

| id  | lastName | firstName | middleName |
| --- | -------- | --------- | ---------- |

со следующими индексами:

  • Providers_lastName
  • Providers_firstName
  • Providers_lastName_firstName
  • Providers_lastName_firstName_middleName

Во всех моих запросах используются конечные символы подстановки в значениях lastName и firstName:

SELECT * FROM Providers
WHERE lastName LIKE 'smi%'
ORDER BY lastName ASC, firstName ASC, middleName
LIMIT 0, 50
SELECT * FROM Providers
WHERE firstName LIKE 'mar%'
ORDER BY lastName ASC, firstName ASC, middleName
LIMIT 0, 50

У меня около 7 миллионов строк в этомТаблица.Мои запросы по lastName очень быстрые.Тем не менее, по имени firstName очень медленно.Здесь я что-то не так делаю?Какой еще индекс я могу добавить, чтобы повысить производительность моих запросов только для firstName без изменения или удаления порядка?

Редактировать 1:

EXPLAIN вывод для lastName запрос:

{
  "query_block": {
    "select_id": 1,
    "cost_info": {
      "query_cost": "69901.30"
    },
    "ordering_operation": {
      "using_filesort": false,
      "table": {
        "table_name": "Providers",
        "access_type": "range",
        "possible_keys": [
          "Providers_lastName",
          "Providers_lastName_firstName",
          "Providers_lastName_firstName_middleName"
        ],
        "key": "Providers_lastName_firstName_middleName",
        "used_key_parts": [
          "lastName"
        ],
        "key_length": "143",
        "rows_examined_per_scan": 59008,
        "rows_produced_per_join": 59008,
        "filtered": "100.00",
        "index_condition": "(`db_name`.`providers`.`lastName` like 'smi%')",
        "cost_info": {
          "read_cost": "64000.51",
          "eval_cost": "5900.80",
          "prefix_cost": "69901.31",
          "data_read_per_join": "158M"
        },
        "used_columns": [
          "id",
          "firstName",
          "middleName",
          "lastName",
          // OTHER COLUMNS
        ]
      }
    }
  }
}

EXPLAIN вывод для firstName запрос:

{
  "query_block": {
    "select_id": 1,
    "cost_info": {
      "query_cost": "390813.95"
    },
    "ordering_operation": {
      "using_filesort": false,
      "table": {
        "table_name": "Providers",
        "access_type": "index",
        "possible_keys": [
          "Providers_firstName"
        ],
        "key": "Providers_lastName_firstName_middleName",
        "used_key_parts": [
          "lastName",
          "firstName",
          "middleName"
        ],
        "key_length": "309",
        "rows_examined_per_scan": 948,
        "rows_produced_per_join": 329914,
        "filtered": "5.27",
        "cost_info": {
          "read_cost": "357822.55",
          "eval_cost": "32991.40",
          "prefix_cost": "390813.95",
          "data_read_per_join": "883M"
        },
        "used_columns": [
          "id",
          "firstName",
          "middleName",
          "lastName",
          // OTHER COLUMNS
        ],
        "attached_condition": "(`db_name`.`providers`.`firstName` like 'mar%')"
      }
    }
  }
}

SHOW CREATE TABLE:

CREATE TABLE `Providers` (
  `id` varchar(10) NOT NULL,
  `firstName` varchar(20) DEFAULT NULL,
  `middleName` varchar(20) DEFAULT NULL,
  `lastName` varchar(35) DEFAULT NULL,
  /* Other columns */
  PRIMARY KEY (`id`),
  KEY `Providers_firstName` (`firstName`),
  KEY `Providers_lastName` (`lastName`),
  KEY `Providers_lastName_firstName` (`lastName`,`firstName`),
  KEY `Providers_lastName_firstName_middleName` (`lastName`,`firstName`,`middleName`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

Редактировать 2:

Вывод SHOW SESSION STATUS LIKE 'Handler%' после выполнения FLUSH STATUS:

Запрос 1 (firstName):

{
    "data":
    [
        {
            "Variable_name": "Handler_commit",
            "Value": "1"
        },
        {
            "Variable_name": "Handler_delete",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_discover",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_external_lock",
            "Value": "2"
        },
        {
            "Variable_name": "Handler_mrr_init",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_prepare",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_first",
            "Value": "1"
        },
        {
            "Variable_name": "Handler_read_key",
            "Value": "1"
        },
        {
            "Variable_name": "Handler_read_last",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_next",
            "Value": "1487176"
        },
        {
            "Variable_name": "Handler_read_prev",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_rnd",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_rnd_next",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_rollback",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_savepoint",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_savepoint_rollback",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_update",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_write",
            "Value": "0"
        }
    ]
}

Запрос 2 (lastName):

{
    "data":
    [
        {
            "Variable_name": "Handler_commit",
            "Value": "1"
        },
        {
            "Variable_name": "Handler_delete",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_discover",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_external_lock",
            "Value": "2"
        },
        {
            "Variable_name": "Handler_mrr_init",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_prepare",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_first",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_key",
            "Value": "1"
        },
        {
            "Variable_name": "Handler_read_last",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_next",
            "Value": "49"
        },
        {
            "Variable_name": "Handler_read_prev",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_rnd",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_rnd_next",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_rollback",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_savepoint",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_savepoint_rollback",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_update",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_write",
            "Value": "0"
        }
    ]
}

Редактировать 3

Используя FORCE_INDEX(Providers_firstName):

EXPLAIN вывод для firstName запроса:

{
    "query_block": {
      "select_id": 1,
      "cost_info": {
        "query_cost": "389514.60"
      },
    "ordering_operation": {
        "using_filesort": true,
        "table": {
          "table_name": "Providers",
          "access_type": "range",
          "possible_keys": [
            "Providers_firstName"
          ],
          "key": "Providers_firstName",
          "used_key_parts": [
            "firstName"
          ],
          "key_length": "83",
          "rows_examined_per_scan": 329914,
          "rows_produced_per_join": 329914,
          "filtered": "100.00",
          "index_condition": "(`db_name`.`providers`.`firstName` like 'mar%')",
          "cost_info": {
            "read_cost": "356523.20",
            "eval_cost": "32991.40",
            "prefix_cost": "389514.60",
            "data_read_per_join": "883M"
          },
        "used_columns": [
            "id",
            "firstName",
            "middleName",
            "lastName",
            // Other columns
          ]
      }
    }
  }
}

Количество обработчиков:

{
    "data":
    [
        {
            "Variable_name": "Handler_commit",
            "Value": "1"
        },
        {
            "Variable_name": "Handler_delete",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_discover",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_external_lock",
            "Value": "2"
        },
        {
            "Variable_name": "Handler_mrr_init",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_prepare",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_first",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_key",
            "Value": "51"
        },
        {
            "Variable_name": "Handler_read_last",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_next",
            "Value": "168497"
        },
        {
            "Variable_name": "Handler_read_prev",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_rnd",
            "Value": "50"
        },
        {
            "Variable_name": "Handler_read_rnd_next",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_rollback",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_savepoint",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_savepoint_rollback",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_update",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_write",
            "Value": "0"
        }
    ]
}

1 Ответ

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

Запрос 1

WHERE lastName LIKE 'smi%'
ORDER BY lastName ASC, firstName ASC, middleName

, вероятно, использует этот индекс.(Пожалуйста, укажите EXPLAIN...):

Providers_lastName_firstName_middleName

Это работает относительно эффективно, потому что может проходить через smi... часть индекса.

Я предполагаю, что SELECT * извлекаеттолько 4 столбца, а id это PRIMARY KEY ??И что Providers_lastName_firstName_middleName равно INDEX(lastName, firstName, middleName), с неявным id, прикрепленным к концу, потому что это InnoDB ??

Это означает, что весь запрос может быть выполнен в индексе.EXPLAIN подтвердил бы это, сказав «Использование индекса», что подразумевает «индекс покрытия».

Более того, этот запрос коснулся только 50 строк - потому что индекс так хорошо настроен как на WHEREи ORDER BY, что он может на самом деле также складываться в LIMIT 50.

Запрос 2

WHERE firstName LIKE 'mar%'
ORDER BY lastName ASC, firstName ASC, middleName

Providers_firstName

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

Но ни одна из остальных оптимизаций (покрытие и т. д.) не применима.Вы можете добавить INDEX(first, last, middle, id), чтобы сделать это быстро.

Этот запрос не может сложиться в LIMIT.

A Примечание

ВСША, 10% имен начинаются с самой распространенной буквы "S".(«10%» примерно одинаково во всем мире, за исключением того, что самые популярные буквы могут отличаться.)

Оптимизатор имел несколько способов выполнить любой запрос и выбирает «лучший» на основе ограниченной информации.Когда становится ясно, что диапазон будет большим (WHERE lastName LIKE 'S%'), он может выбрать переход от использования индекса к простому отбрасыванию многих строк.Я не думаю, что это произошло здесь, но снова EXPLAIN сказал бы нам.

Подробнее о создании оптимальных индексов: http://mysql.rjweb.org/doc.php/index_cookbook_mysql

После объяснения

Если я правильно прочитал EXPLAINs, они оба используют INDEX(last, first, middle), избегая тем самым сортировки.Обратите внимание также "using_filesort": false., Это позволяет запросам останавливаться после LIMIT 50.

. Для сбора дополнительной информации, пожалуйста, выполните следующее:

FLUSH STATUS;
SELECT ...
SHOW SESSION STATUS LIKE 'Handler%';

Если Handler_write* 0, тогда не было никакой сортировки.В то же время, сумма Handler_read* values gives you the number of rows (probably in the INDEX`), к которой было произведено прикосновение.

Я ожидаю, что Query 1 покажет всего 50 для операций чтения, поскольку (теоретически) он может погрузиться в индекс на smiвозьмите следующие 50 (или меньше) строк.Это должно занять очень мало миллисекунд.

Запрос 2 запутан, поскольку ему нужно будет просмотреть множество индексов, прежде чем найти 50 с этим именем.Это не будет 7M, но это может быть 50K строк.Это, вероятно, займет несколько секунд, если необходимая часть индекса будет кэширована;или несколько минут, если это не так.

Невозможно сделать Q2 столь же быстрым, как Q1.Это может быть быстрее для mar%, но медленнее для m%: INDEX(first, last, middle).То есть ввод такого индекса рискованно.

В большинстве случаев INDEX(a) является избыточным, если у вас также есть INDEX(a,b).То есть у вас есть 2 индекса, которые, вероятно, можно отбросить.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...