MySQL игнорирует индекс для запроса количества в таблице INNODB в MySQL 5.7 - PullRequest
0 голосов
/ 22 октября 2019

у нас есть одна старая база данных под управлением MySQL 5.1. Теперь мы хотим перенести его в MySQL 5.7, но некоторые запросы, которые работали нормально, внезапно стали очень очень медленными (в 60 и более раз медленнее).

В рассматриваемой таблице INNODB (EVENT) среди других столбцов естьCOMPANY_ID (внешний ключ к таблице COMPANY) и EVENT_DATETIME типа DATETIME. Существует индекс COMPANY_ID, EVENT_DATETIME, и для тестирования я добавил один EVENT_DATETIME, COMPANY_ID. В настоящее время в основном все СОБЫТИЯ имеют COMPANY_ID 1, но это изменится.

У нас есть запрос подсчета для запроса количества событий за последний год:

select count(distinct this_.EVENT_ID) as y0_ from EVENT this_ where this_.EVENT_DATETIME>='2018-10-22 00:00:00' and this_.EVENT_DATETIME<='2019-11-21 00:00:00' and this_.COMPANY_ID = 1;

Результат - около 1 000 000 строки раньше занимало около 1,5 секунд, теперь это занимает до 100 секунд. В то время как запрос в MySQL 5.1 использует индекс для COMPANY_ID и EVENT_DATETIME, этот индекс игнорируется в MySQL 5.7. Кажется, что если MySQL видит, что ему нужно проанализировать слишком много строк, он отказывается от индекса, даже если это поможет. Если я уменьшу окно, например, до 10 месяцев, MySQL 5.7 снова использует индекс.

Так что в MySQL 5.1 индекс COMPANY_ID, EVENT_DATETIME используется в MySQL, он использует только индекс внешнего ключа для COMPANY_ID.

Если я выполняю запрос без указания where на COMPANY_ID

select count(distinct this_.EVENT_ID) as y0_ from EVENT this_ where this_.EVENT_DATETIME>='2018-10-22 00:00:00' and this_.EVENT_DATETIME<='2019-11-21 00:00:00';

, запрос выполняется намного быстрее.

Есть ли способ заставить MySQL 5.7 использовать определенный индекс?

Если я переписываю запрос так:

select count(distinct this_.EVENT_ID) as y0_ from EVENT this_ where this_.EVENT_DATETIME>='2018-10-22 00:00:00' and this_.EVENT_DATETIME<='2019-11-21 00:00:00' GROUP BY COMPANY_ID HAVING COMPANY_ID = 1;

, он возвращается примерно к 1 - 1,5 секундам. Проблема в том, что у нас может быть более одного из этих запросов, и запросы генерируются Hibernate Criterias, которые не поддерживают HAVING, поэтому мой обходной путь не будет работать в реальной жизни.

Обновление: MySQL 5.7 Объяснение в течение 12 месяцевзапрос (1050757 строк за 40 секунд)

{
  "query_block": {
    "select_id": 1,
    "cost_info": {
      "query_cost": "673838.60"
    },
    "table": {
      "table_name": "this_",
      "access_type": "ref",
      "possible_keys": [
      "PRIMARY",
      "FK_EVENT_COMPANY",
      "IX_REFERENCE",
      "IX_DATE_TIME",
      "EVENT_DATETIME",
      "IDX_CE_COMPANY_TYPE",
      "IDX_CE_COMPANY_DATE",
      "IDX_CE_DATE_COMPANY"
      ],
      "key": "FK_EVENT_COMPANY",
      "used_key_parts": [
        "COMPANY_ID"
      ],
      "key_length": "4",
      "ref": [
        "const"
      ],
      "rows_examined_per_scan": 2698153,
      "rows_produced_per_join": 1135826,
      "filtered": "42.10",
      "cost_info": {
        "read_cost": "134208.00",
        "eval_cost": "227165.40",
        "prefix_cost": "673838.60",
        "data_read_per_join": "1G"
      },
      "used_columns": [
        "EVENT_ID",
        "COMPANY_ID",
        "EVENT_DATETIME"
      ],
      "attached_condition": "((`test`.`this_`.`EVENT_DATETIME` >= '2018-10-22 00:00:00') and (`test`.`this_`.`EVENT_DATETIME` <= '2019-11-21 00:00:00'))"
    }
  }
}

Объяснение для 10-месячного запроса

   {
  "query_block": {
    "select_id": 1,
    "cost_info": {
      "query_cost": "634047.16"
    },
    "table": {
      "table_name": "this_",
      "access_type": "range",
      "possible_keys": [
        "PRIMARY",
        "FK_EVENT_COMPANY",
        "IX_REFERENCE",
        "IX_DATE_TIME",
        "EVENT_DATETIME",
        "IDX_CE_COMPANY_TYPE",
        "IDX_CE_COMPANY_DATE",
        "IDX_CE_DATE_COMPANY"
      ],
      "key": "IDX_CE_DATE_COMPANY",
      "used_key_parts": [
        "EVENT_DATETIME"
      ],
      "key_length": "9",
      "rows_examined_per_scan": 1578860,
      "rows_produced_per_join": 789430,
      "filtered": "50.00",
      "using_index": true,
      "cost_info": {
        "read_cost": "476161.16",
        "eval_cost": "157886.00",
        "prefix_cost": "634047.16",
        "data_read_per_join": "1G"
      },
      "used_columns": [
        "EVENT_ID",
        "COMPANY_ID",
        "EVENT_DATETIME"
      ],
      "attached_condition": "((`test`.`this_`.`COMPANY_ID` = 1) and (`test`.`this_`.`EVENT_DATETIME` >= '2019-01-22 00:00:00') and (`test`.`this_`.`EVENT_DATETIME` <= '2019-11-21 00:00:00'))"
    }
  }
}

Интересно, что первый 12-месячный (медленный) запрос не показывает COMPANY_ID в attach_condition, покав течение второго 10-месячного запроса attach_condition имеет проверку COMPANY_ID.

ANALYZE TABLE, как было предложено, ничего не изменило, кажется,

Обновление 2: объяснение для MySQL 5.1 (не поддерживает формат JSON) занимает 1,3 с

1    SIMPLE         this_  range   FK_EVENT_COMPANY,IX_DATE_TIME,EVENT_DATETIME,IDX_CE_COMPANY_TYPE,IDX_CE_COMPANY_DATE    IDX_CE_COMPANY_DATE 16      NULL    2018704   Using where; Using index

1 Ответ

1 голос
/ 23 октября 2019

Планировщик запросов может принимать неправильные решения на основе доступной статистики. Вы можете попробовать запустить ANALYZE (https://dev.mysql.com/doc/refman/5.6/en/analyze-table.html), чтобы перестроить статистику и предоставить более точные цифры планировщику. Просто обратите внимание, что ANALYZE заблокирует таблицу во время ее работы (это быстро).

ОБНОВЛЕНИЕ

Читая документацию MySQL, я нашел этот абзац:

До MySQL 5.7.18 InnoDB обрабатывал операторы SELECT COUNT () с помощьюСканирование кластерного индекса Начиная с MySQL 5.7.18, InnoDB обрабатывает операторы SELECT COUNT () путем обхода наименьшего доступного вторичного индекса, если только указатель индекса или оптимизатора не указывает оптимизатору использовать другой индекс. Если вторичный индекс не являетсяВ настоящее время кластеризованный индекс сканируется.

Ссылка: https://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html#function_count

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

...