带有MIN / MAX的查询作为子查询要比单独查询慢得多

时间:2018-06-20 08:56:21

标签: mysql sql mariadb

请考虑对具有〜1M行的表进行以下查询。 (结构在最后。TL; DR:它具有UNIQUE KEY `max_filter` (`id_chat`, `id_device`, `id_message`)。)

SELECT MAX(`id_message`)
FROM `message_keys`
WHERE `id_chat` = 94609
    AND `id_device` = 26664
    AND `id_message` <= 238798

它几乎可以按预期(大约1毫秒)运行。 当我这样修改它时:

SELECT (
    SELECT MAX(id_message)
    FROM message_keys
    WHERE message_keys.id_chat = 94609
        AND message_keys.id_device = devices.id
        AND message_keys.id_message <= 238798
) AS max
FROM devices
WHERE devices.id_user = 1

假设user 1有10个device,我希望它最多运行10到20毫秒,但是这会花费500到1000毫秒。

问题出在哪里?

我正在使用MariaDB 10.1.23。

说明:

+----+--------------------+--------------+------+------------------------------+------------+---------+------------------------+------+-------------+-------------+
| id |    select_type     |    table     | type |        possible_keys         |    key     | key_len |          ref           | rows |    Extra    |             |
+----+--------------------+--------------+------+------------------------------+------------+---------+------------------------+------+-------------+-------------+
|  1 | PRIMARY            | devices      | ref  | id_user                      | id_user    |       4 | const                  |   10 | Using index |             |
|  2 | DEPENDENT SUBQUERY | message_keys | ref  | PRIMARY,max_filter,id_device | max_filter |       8 | const,devices.id       |  520 | Using where | Using index |
+----+--------------------+--------------+------+------------------------------+------------+---------+------------------------+------+-------------+-------------+

SHOW WARNINGS;EXPLAIN EXTENDED之后:

+-------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                                                                                                                                                                                                                     |
+-------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1276 | Field or reference 'tukan.devices.id' of SELECT #2 was resolved in SELECT #1                                                                                                                                                                                                                                                                                                |
| Note  | 1003 | select <expr_cache><`tukan`.`devices`.`id`>((select max(`tukan`.`message_keys`.`id_message`) from `tukan`.`message_keys` where ((`tukan`.`message_keys`.`id_chat` = 94609) and (`tukan`.`message_keys`.`id_device` = `tukan`.`devices`.`id`) and (`tukan`.`message_keys`.`id_message` <= 238798)))) AS `max` from `tukan`.`devices` where (`tukan`.`devices`.`id_user` = 1) |
+-------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

结构:

CREATE TABLE `message_keys` (
 `id_message` int(10) unsigned NOT NULL,
 `id_chat` int(10) unsigned NOT NULL,
 `id_from` int(10) NOT NULL,
 `id_device` int(10) unsigned NOT NULL,
 `key` blob NOT NULL,
 `status` enum('sent','delivered','read') CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
 PRIMARY KEY (`id_message`,`id_device`),
 UNIQUE KEY `max_filter` (`id_chat`,`id_device`,`id_message`),
 KEY `id_device` (`id_device`),
 CONSTRAINT `message_keys_ibfk_1` FOREIGN KEY (`id_message`) REFERENCES `messages` (`id`) ON DELETE CASCADE,
 CONSTRAINT `message_keys_ibfk_2` FOREIGN KEY (`id_device`) REFERENCES `devices` (`id`) ON DELETE CASCADE,
 CONSTRAINT `message_keys_ibfk_3` FOREIGN KEY (`id_chat`) REFERENCES `chats` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB;

CREATE TABLE `devices` (
 `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 `id_user` int(10) unsigned NOT NULL,
 `guid` binary(32) NOT NULL,
 `public_key` text CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
 `session_key` binary(32) DEFAULT NULL,
 `id_session_home_key` int(10) unsigned DEFAULT NULL,
 `name` varchar(255) NOT NULL,
 `description` text,
 `ip` int(10) unsigned NOT NULL,
 `is_locked` tinyint(1) NOT NULL DEFAULT '0',
 `default_home_key` binary(32) DEFAULT NULL,
 `time_created` int(10) unsigned NOT NULL,
 `time_last_authorized` int(10) unsigned NOT NULL,
 `client_message_id` int(10) unsigned NOT NULL DEFAULT '0',
 PRIMARY KEY (`id`),
 KEY `id_user` (`id_user`),
 KEY `id_session_home_key` (`id_session_home_key`),
 CONSTRAINT `devices_ibfk_1` FOREIGN KEY (`id_user`) REFERENCES `users` (`id`) ON DELETE CASCADE,
 CONSTRAINT `devices_ibfk_2` FOREIGN KEY (`id_session_home_key`) REFERENCES `home_keys` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB;

此:

SELECT MAX(`id_message`) AS `max`
FROM `devices`
LEFT JOIN `message_keys`
ON `message_keys`.`id_chat` = 94609
    AND `message_keys`.`id_device` = `devices`.`id`
    AND `message_keys`.`id_message` <= 238798
WHERE `devices`.`id_user` = 1
GROUP BY `devices`.`id`

花费相同的时间(500-1000毫秒)。

+-------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                                                                                                                                                                                                         |
+-------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1003 | select max(`tukan`.`message_keys`.`id_message`) AS `max` from `tukan`.`devices` left join `tukan`.`message_keys` on(((`tukan`.`message_keys`.`id_chat` = 94609) and (`tukan`.`message_keys`.`id_device` = `tukan`.`devices`.`id`) and (`tukan`.`message_keys`.`id_message` <= 238798))) where (`tukan`.`devices`.`id_user` = 1) group by `tukan`.`devices`.`id` |
+-------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

3 个答案:

答案 0 :(得分:0)

子查询可能导致MySQL创建一个临时表。改用连接到设备表:

SELECT MAX(a.id_message)
FROM message_keys a
JOIN devices b
    ON a.device_id = b.id
WHERE a.id_chat = 94609
    AND a.id_device = 26664
    AND a.id_message <= 238798
    AND b.id_user = 1;

答案 1 :(得分:0)

对于此查询:

SELECT (SELECT MAX(mk.id_message)
        FROM message_keys mk
        WHERE mk.id_chat = 94609 AND
              mk.id_device = d.id AND
              mk.id_message <= 238798
       ) as max
FROM devices d
WHERE d.id_user = 1;

最佳索引是devices(id_user, id)message_keys(id_device, id_chat, id_message)

特别是,您的第一个查询可以为该查询选择三个单列索引中的任何一个。第二,它必须在(device_id)上使用一个,这可能不是查询的最佳索引。

答案 2 :(得分:0)

如果对密钥进行分组会发生什么?

SELECT (
SELECT MAX(id_message)
FROM message_keys
WHERE message_keys.id_chat = 94609
    AND message_keys.id_device = devices.id
    AND message_keys.id_message <= 238798 GROUP BY 
message_keys.id_chat,message_keys.id_device ,message_keys.id_message
) AS max
FROM devices
WHERE devices.id_user = 1