MYSQL Оптимизатор просто игнорирует последний столбец, который я использую для ORDER BY в составных индексах - PullRequest
1 голос
/ 21 июня 2020

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

CREATE TABLE `profiles3m` (
  `uid` int(10) unsigned NOT NULL,
  `birth_date` date NOT NULL,
  `gender` tinyint(4) NOT NULL DEFAULT '0',
  `country` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'ID',
  `city` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'Makassar',
  `created_at` timestamp NULL DEFAULT NULL,
  `premium` tinyint(4) NOT NULL DEFAULT '0',
  `updated_at` timestamp NULL DEFAULT NULL,
  `latitude` double NOT NULL DEFAULT '0',
  `longitude` double NOT NULL DEFAULT '0',
  `orderid` int(11) NOT NULL,
  PRIMARY KEY (`uid`),
  KEY `idx_composites_latitude_longitude_gender_birth_date_created_at` (`latitude`,`longitude`,`country`,`city`,`gender`,`birth_date`) USING BTREE,
  KEY `idx_composites_country_city_gender_birth_date` (`country`,`city`,`gender`,`birth_date`,`orderid`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

Мне не удалось указать MySQL Оптимизатору использовать все столбцы в определении составного индекса, похоже, что оптимизатор просто игнорирование последнего столбца как orderid для целей заказа, который является просто копией столбца uid, как вы, возможно, знаете, PRIMARY KEY в таблице InnoDB нельзя использовать для упорядочивания, потому что он может дать оптимизатору указание использовать PRIMARY KEY в качестве индекса, а не использовать наши составные индексы, и это идея создания столбца orderid.

Следующий запрос SQL вместе с объяснением JSON и оператором Показать индекс для отображения всей статистики индекса в таблице может помочь в анализе причины.

SELECT
    pro.uid 
FROM
    `profiles3m` AS pro 
WHERE
    pro.country = 'INDONESIA' 
    AND pro.city IN ( 'MAKASSAR' ) 
    AND pro.gender = 0 
    AND ( pro.birth_date BETWEEN ( NOW()- INTERVAL 35 YEAR ) AND ( NOW()- INTERVAL 25 YEAR ) ) 
    AND pro.orderid > 0 
ORDER BY
    pro.orderid
LIMIT 30

Объяснение JSON следующим образом:

{
  "query_block": {
    "select_id": 1,
    "cost_info": {
      "query_cost": "45278.73"
    },
    "ordering_operation": {
      "using_filesort": true,
      "cost_info": {
        "sort_cost": "19051.43"
      },
      "table": {
        "table_name": "pro",
        "access_type": "range",
        "possible_keys": [
          "idx_composites_country_city_gender_birth_date"
        ],
        "key": "idx_composites_country_city_gender_birth_date",
        "used_key_parts": [
          "country",
          "city",
          "gender",
          "birth_date"
        ],
        "key_length": "488",
        "rows_examined_per_scan": 57160,
        "rows_produced_per_join": 19051,
        "filtered": "33.33",
        "using_index": true,
        "cost_info": {
          "read_cost": "22417.02",
          "eval_cost": "3810.29",
          "prefix_cost": "26227.30",
          "data_read_per_join": "9M"
        },
        "used_columns": [
          "uid",
          "birth_date",
          "gender",
          "country",
          "city",
          "orderid"
        ],
        "attached_condition": "((`restful`.`pro`.`gender` = 0) and (`restful`.`pro`.`country` = 'INDONESIA') and (`restful`.`pro`.`city` = 'MAKASSAR') and (`restful`.`pro`.`birth_date` between <cache>((now() - interval 35 year)) and <cache>((now() - interval 25 year))) and (`restful`.`pro`.`orderid` > 0))"
      }
    }
  }
}

ниже для инструкции индекса показа:

+------------+----------------------------------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+
| Non_unique | Key_name                                                       | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type |
+------------+----------------------------------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+
| 0          | PRIMARY                                                        | 1            | uid         | A         | 2984412     |          |        |      | BTREE      |
+------------+----------------------------------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+
| 1          | idx_composites_latitude_longitude_gender_birth_date_created_at | 1            | latitude    | A         | 2934360     |          |        |      | BTREE      |
+------------+----------------------------------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+
| 1          | idx_composites_latitude_longitude_gender_birth_date_created_at | 2            | longitude   | A         | 2984080     |          |        |      | BTREE      |
+------------+----------------------------------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+
| 1          | idx_composites_latitude_longitude_gender_birth_date_created_at | 3            | country     | A         | 2984080     |          |        |      | BTREE      |
+------------+----------------------------------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+
| 1          | idx_composites_latitude_longitude_gender_birth_date_created_at | 4            | city        | A         | 2984080     |          |        |      | BTREE      |
+------------+----------------------------------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+
| 1          | idx_composites_latitude_longitude_gender_birth_date_created_at | 5            | gender      | A         | 2984080     |          |        |      | BTREE      |
+------------+----------------------------------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+
| 1          | idx_composites_latitude_longitude_gender_birth_date_created_at | 6            | birth_date  | A         | 2984080     |          |        |      | BTREE      |
+------------+----------------------------------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+
| 1          | idx_composites_country_city_gender_birth_date                  | 1            | country     | A         | 1           |          |        |      | BTREE      |
+------------+----------------------------------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+
| 1          | idx_composites_country_city_gender_birth_date                  | 2            | city        | A         | 14          |          |        |      | BTREE      |
+------------+----------------------------------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+
| 1          | idx_composites_country_city_gender_birth_date                  | 3            | gender      | A         | 29          |          |        |      | BTREE      |
+------------+----------------------------------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+
| 1          | idx_composites_country_city_gender_birth_date                  | 4            | birth_date  | A         | 362449      |          |        |      | BTREE      |
+------------+----------------------------------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+
| 1          | idx_composites_country_city_gender_birth_date                  | 5            | orderid     | A         | 2984412     |          |        |      | BTREE      |
+------------+----------------------------------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+

Что действительно интересно посмотреть в Explain JSON, они сказали нам, может ли оптимизатор только y используйте четыре части нашей индексированной, и неудивительно, что операция упорядочения использует файловую сортировку, поскольку, как вы знаете, означает более медленное выполнение, что плохо для производительности приложения.

idx_composites_country_city_gender_birth_date (country, city, gender, birth_date, orderid)

"ordering_operation": {
          "using_filesort": true,
.....

"key": "idx_composites_country_city_gender_birth_date",    
"used_key_parts": [
              "country",
              "city",
              "gender",
              "birth_date"
            ],

Я что-то пропустил, это вызвано предложением RANGE в нашем операторе WHERE? Я был протестирован с различные комбинации столбцов в нашей последовательности составных индексов, например, я меняю столбец orderid на premium, который является типом столбца флага, который содержит только 0 и 1, и он работал MySQL Оптимизатор может использовать все пять столбцов, тогда почему Оптимизатор не может сделать то же самое со столбцом orderid? это связано с Кардинальностью? Я не уверен, единственное, что я могу заверить, это то, что я должен заставить ORDER BY работать без какого-либо влияния на производительность приложения, независимо от того, как это делать.

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

+------------+
| version()  |
+------------+
| 5.7.29-log |
+------------+

Ответы [ 2 ]

2 голосов
/ 21 июня 2020

MySQL не может использовать индекс для заказа. Ваше условие на birthdate означает, что строки в индексе не упорядочены по orderid.

Я не думаю, что есть способ обойти это.

1 голос
/ 21 июня 2020

Вы заметили, что используются только четыре столбца индекса:

    "used_key_parts": [
      "country",
      "city",
      "gender",
      "birth_date"
    ],

Несмотря на условия в вашем предложении WHERE, ссылающиеся на все пять столбцов:

WHERE
    pro.country = 'INDONESIA' 
    AND pro.city IN ( 'MAKASSAR' ) 
    AND pro.gender = 0 
    AND ( pro.birth_date BETWEEN ( NOW()- INTERVAL 35 YEAR ) AND ( NOW()- INTERVAL 25 YEAR ) ) 
    AND pro.orderid > 0 

Однако, в этих условиях что-то другое. Все условия country, city, gender являются условиями равенства . Как только поиск находит подмножество индекса с этими значениями, тогда подмножество упорядочивается по birth_date следующим, а если есть строки, привязанные к birth_date, они дополнительно упорядочиваются по orderid.

Также как если вы читаете телефонную книгу и находите всех людей, чья фамилия «Смит», они отсортированы по имени. Если есть несколько человек с одинаковым именем, они упорядочиваются в телефонной книге по их соответствующему номеру телефона.

Smith, Sarah 408-555-1234
Smith, Sarah 408-555-5678

Но что, если вы выполните поиск всех людей с фамилией Смит и различные имена, начинающиеся с "S"?

Smith, Sam   408-555-3298
Smith, Sarah 408-555-1234
Smith, Sarah 408-555-5678
Smith, Stan  408-555-4224

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

Если вы хотите отсортировать их по номеру телефона, вы можете создать индекс со столбцами в другой порядок, например фамилия, номер телефона, имя.

Smith 408-555-1234 Sarah
Smith 408-555-2020 David
Smith 408-555-3298 Sam
Smith 408-555-4100 Charlie
Smith 408-555-4224 Stan
Smith 408-555-5555 Annette
Smith 408-555-5678 Sarah

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

Это указывает на общую проблему с индексами: вы можете изменить порядок столбцы только для столбцов, участвующих в сравнениях равенство . Если вы хотите отсортировать результаты, вы можете использовать индекс только в том случае, если вы сортируете по столбцу в индексе и все предыдущие столбцы индекса используются только для сравнения на равенство.

После ссылки на один столбец в диапазон сравнение, любые последующие столбцы в индексе игнорируются как для поиска, так и для сортировки.

Другими словами: индекс может иметь любое количество столбцов для условий равенства, а следующий столбец index может использоваться либо для условия диапазона, либо для сортировки результатов. Но для любой из этих операций используется не более одного столбца.

Вы не можете оптимизировать все.

Повторите свой комментарий: Если у вас есть индекс по столбцам, исключая birth_date:

alter table profiles3m add key bk1 (country, city, gender, orderid);

Тогда EXPLAIN показывает, что сортировка файлов отсутствует:

EXPLAIN SELECT
    pro.uid 
FROM
    `profiles3m` AS pro 
WHERE
    pro.country = 'INDONESIA' 
    AND pro.city IN ( 'MAKASSAR' ) 
    AND pro.gender = 0 
    AND ( pro.birth_date BETWEEN ( NOW()- INTERVAL 35 YEAR ) AND ( NOW()- INTERVAL 25 YEAR ) ) 
    AND pro.orderid > 0 
ORDER BY
    pro.orderid
LIMIT 30\G

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: pro
   partitions: NULL
         type: range
possible_keys: bk1
          key: bk1
      key_len: 489
          ref: NULL
         rows: 1
     filtered: 100.00
        Extra: Using index condition; Using where

(rows выглядит низким, потому что я тестирую это с пустой таблицей.)

Предостережение заключается в том, что при этом используется индекс для соответствия всем строкам, сопоставленным country, city, gender и orderid. Затем MySQL будет оценивать оставшееся условие на birth_date сложным способом: строка за строкой.

Но после этого оптимизатор знает, что он уже получил строки в порядке индекса, поэтому он знает, что естественно будет в порядке orderid, так что он может пропустить сортировку файлов.

Это может быть или не быть net победой. Это зависит от того, сколько строк сопоставлено, но должно быть выброшено условием birth_date. И насколько дорого стоит оценивать это условие для каждой строки. И как это соотносится с экономией, которую вы получили бы, используя индекс для фильтрации по birth_date.

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