MySQL. История одного медленного запроса
SQL запросы иногда бывают не оптимизированы, из-за неправильного понимания механизмов работы
Есть вот такая табличка:
mysql> show create table mobile_stat_device\G *************************** 1. row *************************** Table: mobile_stat_device Create Table: CREATE TABLE `mobile_stat_device` ( `id` int unsigned NOT NULL AUTO_INCREMENT, `deviceUuid` char(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'Unique device ID', `deviceUuidHash` int unsigned GENERATED ALWAYS AS (crc32(`deviceUuid`)) STORED NOT NULL, `createdAt` datetime NOT NULL COMMENT 'Inserting date', PRIMARY KEY (`id`), UNIQUE KEY `deviceUuid_udx` (`deviceUuidHash`,`deviceUuid`), KEY `deviceUuidHash_idx` (`deviceUuidHash`) ) ENGINE=InnoDB AUTO_INCREMENT=2490316 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
Табличка с uuid
устройств. Для ускорения запросов добавлено поле deviceUuidHash
и создан комбинированный уникальный индекс по полям deviceUuidHash
и deviceUuid
.
Кол-во записей в mobile_stat_device чуть больше 2х миллионов:
mysql> select count(1) from mobile_stat_device; +----------+ | count(1) | +----------+ | 2097152 | +----------+
Нам необходимо выбрать id
для определенных uuid
устройств. Мой первый запрос выглядел вот так:
mysql> select id, deviceUuid from mobile_stat_device where (deviceUuid = '36fffc8d-519b-11eb-a85f-42010a9c0002' and deviceUuidHash = crc32(deviceUuid)) or (deviceUuid = '36fffd73-519b-11eb-a85f-42010a9c0002' and deviceUuidHash = crc32(deviceUuid)); +---------+--------------------------------------+ | id | deviceUuid | +---------+--------------------------------------+ | 2327645 | 36fffc8d-519b-11eb-a85f-42010a9c0002 | | 2327653 | 36fffd73-519b-11eb-a85f-42010a9c0002 | +---------+--------------------------------------+
Посмотрев на explain
, я успокоился - индекс используется, ну и ладно
mysql> explain select id, deviceUuid from mobile_stat_device where (deviceUuid = '36fffc8d-519b-11eb-a85f-42010a9c0002' and deviceUuidHash=crc32(deviceUuid)) or (deviceUuid = '36fffd73-519b-11eb-a85f-42010a9c0002' and deviceUuidHash = crc32(deviceUuid)); +----+-------------+--------------------+------------+-------+---------------+----------------+---------+------+---------+----------+--------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+--------------------+------------+-------+---------------+----------------+---------+------+---------+----------+--------------------------+ | 1 | SIMPLE | mobile_stat_device | NULL | index | NULL | deviceUuid_udx | 148 | NULL | 2088550 | 1.99 | Using where; Using index | +----+-------------+--------------------+------------+-------+---------------+----------------+---------+------+---------+----------+--------------------------+
Я не обратил внимание на количество rows
в эксплейне. Точнее, когда данных мало, то и кол-во rows
приемлемое. Ошибочность запроса стала понятна только когда нагрузил данными табличку и еще раз проверил скорость запроса.
Вот тот же explain
в более информативном виде.
mysql> explain format=json select id, deviceUuid from mobile_stat_device where (deviceUuid = '36fffc8d-519b-11eb-a85f-42010a9c0002' and deviceUuidHash=crc32(deviceUuid)) or (deviceUuid = '36fffd73-519b-11eb-a85f-42010a9c0002' and deviceUuidHash = crc32(deviceUuid))\G *************************** 1. row *************************** EXPLAIN: { "query_block": { "select_id": 1, "cost_info": { "query_cost": "216917.41" }, "table": { "table_name": "mobile_stat_device", "access_type": "index", "key": "deviceUuid_udx", "used_key_parts": [ "deviceUuidHash", "deviceUuid" ], "key_length": "148", "rows_examined_per_scan": 2088550, "rows_produced_per_join": 41562, "filtered": "1.99", "using_index": true, "cost_info": { "read_cost": "212761.19", "eval_cost": "4156.21", "prefix_cost": "216917.41", "data_read_per_join": "6M" }, "used_columns": [ "id", "deviceUuid", "deviceUuidHash" ], "attached_condition": "(((`test`.`mobile_stat_device`.`deviceUuid` = '36fffc8d-519b-11eb-a85f-42010a9c0002') and (`test`.`mobile_stat_device`.`deviceUuidHash` = crc32(`test`.`mobile_stat_device`.`deviceUuid`))) or ((`test`.`mobile_stat_device`.`deviceUuid` = '36fffd73-519b-11eb-a85f-42010a9c0002') and (`test`.`mobile_stat_device`.`deviceUuidHash` = crc32(`test`.`mobile_stat_device`.`deviceUuid`))))" } } }
О чём это говорит? Запрос пробегает по 2088550 записям и для ответа использует 41562. А так быть не должно. Где-то ошибка.
Внимательный читатель уже догадался где. Вот в этом условии - and deviceUuidHash = crc32(deviceUuid)
. Похоже, MySQL сначала вычисляет значение crc32
для всех записей, а потом уже, используя индекс, делается селект.
Переписанный запрос.
mysql> explain format=json select id, deviceUuid from mobile_stat_device where (deviceUuid = '36fffc8d-519b-11eb-a85f-42010a9c0002' and deviceUuidHash=crc32('36fffc8d-519b-11eb-a85f-42010a9c0002')) or (deviceUuid = '36fffd73-519b-11eb-a85f-42010a9c0002' and deviceUuidHash = crc32('36fffd73-519b-11eb-a85f-42010a9c0002'))\G *************************** 1. row *************************** EXPLAIN: { "query_block": { "select_id": 1, "cost_info": { "query_cost": "0.85" }, "table": { "table_name": "mobile_stat_device", "access_type": "range", "possible_keys": [ "deviceUuid_udx", "deviceUuidHash_idx" ], "key": "deviceUuid_udx", "used_key_parts": [ "deviceUuidHash", "deviceUuid" ], "key_length": "148", "rows_examined_per_scan": 2, "rows_produced_per_join": 2, "filtered": "100.00", "using_index": true, "cost_info": { "read_cost": "0.65", "eval_cost": "0.20", "prefix_cost": "0.85", "data_read_per_join": "320" }, "used_columns": [ "id", "deviceUuid", "deviceUuidHash" ], "attached_condition": "(((`test`.`mobile_stat_device`.`deviceUuidHash` = <cache>(crc32('36fffc8d-519b-11eb-a85f-42010a9c0002'))) and (`test`.`mobile_stat_device`.`deviceUuid` = '36fffc8d-519b-11eb-a85f-42010a9c0002')) or ((`test`.`mobile_stat_device`.`deviceUuidHash` = <cache>(crc32('36fffd73-519b-11eb-a85f-42010a9c0002'))) and (`test`.`mobile_stat_device`.`deviceUuid` = '36fffd73-519b-11eb-a85f-42010a9c0002')))" } } }
Обратите внимание, насколько упал query_cost
и насколько меньше записей выбирается.
Вывода два:
- Быть осторожнее с вычислительными значениями в запросах, если эти значения используются в where.
- Нагружать БД данными и потом уже проверять насколько корректно отрабатывают запросы.