MySQL - 如何使用 order by 优化查询
MySQL - how to optimize query with order by
我正在尝试为一组用户任务生成一个包含 5 个最新历史记录项的列表。如果我删除订单,执行时间从 ~2 秒下降到 < 20 毫秒。
索引开启
h.task_id
h.mod_date
i.task_id
i.user_id
这是查询
SELECT h.*
, i.task_id
, i.user_id
, i.name
, i.completed
FROM h
, i
WHERE i.task_id = h.task_id
AND i.user_id = 42
ORDER
BY h.mod_date DESC
LIMIT 5
解释如下:
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE i ref PRIMARY,UserID UserID 4 const 3091 Using temporary; Using filesort
1 SIMPLE h ref TaskID TaskID 4 myDB.i.task_id 7
这里是显示创建表:
CREATE TABLE `h` (
`history_id` int(6) NOT NULL AUTO_INCREMENT,
`history_code` tinyint(4) NOT NULL DEFAULT '0',
`task_id` int(6) NOT NULL,
`mod_date` datetime NOT NULL,
`description` text NOT NULL,
PRIMARY KEY (`history_id`),
KEY `TaskID` (`task_id`),
KEY `historyCode` (`history_code`),
KEY `modDate` (`mod_date`)
) ENGINE=InnoDB AUTO_INCREMENT=185647 DEFAULT CHARSET=latin1
和
CREATE TABLE `i` (
`task_id` int(6) NOT NULL AUTO_INCREMENT,
`user_id` int(6) NOT NULL,
`name` varchar(60) NOT NULL,
`due_date` date DEFAULT NULL,
`create_date` date NOT NULL,
`completed` tinyint(1) NOT NULL DEFAULT '0',
`task_description` blob,
PRIMARY KEY (`task_id`),
KEY `name_2` (`name`),
KEY `UserID` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=12085 DEFAULT CHARSET=latin1
尝试更改 h.task_id
上的索引,使其成为复合索引。
CREATE OR REPLACE INDEX TaskID ON h(task_id, mod_date DESC);
这可能(也可能不允许)允许 MySql 简化您 ORDER BY ... LIMIT ...
请求中的部分或所有额外工作。顺便说一句,这是一个臭名昭著的性能反模式,但有时是必要的。
编辑 索引没有帮助。因此,让我们尝试所谓的 延迟加入 ,这样我们就不必订购然后限制来自您的 h
table.[=19= 的所有数据]
从这个子查询开始。它只检索结果中涉及的行的主键值,并且只会生成五行。
SELECT h.history_id, i.task_id
FROM h
JOIN i ON h.task_id = i.task_id
WHERE i.user_id = 42
ORDER BY h.mod_date
LIMIT 5
为什么这个子查询?它处理工作密集型 ORDER BY ... LIMIT 操作,同时仅操作主键和日期。它仍然必须对大量行进行排序,只丢弃除 5 行以外的所有行,但它必须处理的行要短得多。因为这个子查询完成了繁重的工作,所以您专注于优化它,而不是整个查询。
保留我上面建议的索引,因为它 covers h
.
的子查询
然后,像这样将其加入您的查询的其余部分。这样,您只需为您关心的五行检索昂贵的 h.description
列。
SELECT h.* , i.task_id, i.user_id , i.name, i.completed
FROM h
JOIN i ON i.task_id = h.task_id
JOIN (
SELECT h.history_id, i.task_id
FROM h
JOIN i ON h.task_id = i.task_id
WHERE i.user_id = 42
ORDER BY h.mod_date
LIMIT 5
) selected ON h.history_id = selected.history_id
AND i.task_id = selected.task_id
ORDER BY h.mod_date DESC
LIMIT 5
INDEX(task_id, mod_date, history_id) -- in this order
将“覆盖”并且列将按最佳顺序排列
此外,删除
KEY `TaskID` (`task_id`)
这样优化器就不会被诱惑使用它。
我正在尝试为一组用户任务生成一个包含 5 个最新历史记录项的列表。如果我删除订单,执行时间从 ~2 秒下降到 < 20 毫秒。
索引开启
h.task_id
h.mod_date
i.task_id
i.user_id
这是查询
SELECT h.*
, i.task_id
, i.user_id
, i.name
, i.completed
FROM h
, i
WHERE i.task_id = h.task_id
AND i.user_id = 42
ORDER
BY h.mod_date DESC
LIMIT 5
解释如下:
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE i ref PRIMARY,UserID UserID 4 const 3091 Using temporary; Using filesort
1 SIMPLE h ref TaskID TaskID 4 myDB.i.task_id 7
这里是显示创建表:
CREATE TABLE `h` (
`history_id` int(6) NOT NULL AUTO_INCREMENT,
`history_code` tinyint(4) NOT NULL DEFAULT '0',
`task_id` int(6) NOT NULL,
`mod_date` datetime NOT NULL,
`description` text NOT NULL,
PRIMARY KEY (`history_id`),
KEY `TaskID` (`task_id`),
KEY `historyCode` (`history_code`),
KEY `modDate` (`mod_date`)
) ENGINE=InnoDB AUTO_INCREMENT=185647 DEFAULT CHARSET=latin1
和
CREATE TABLE `i` (
`task_id` int(6) NOT NULL AUTO_INCREMENT,
`user_id` int(6) NOT NULL,
`name` varchar(60) NOT NULL,
`due_date` date DEFAULT NULL,
`create_date` date NOT NULL,
`completed` tinyint(1) NOT NULL DEFAULT '0',
`task_description` blob,
PRIMARY KEY (`task_id`),
KEY `name_2` (`name`),
KEY `UserID` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=12085 DEFAULT CHARSET=latin1
尝试更改 h.task_id
上的索引,使其成为复合索引。
CREATE OR REPLACE INDEX TaskID ON h(task_id, mod_date DESC);
这可能(也可能不允许)允许 MySql 简化您 ORDER BY ... LIMIT ...
请求中的部分或所有额外工作。顺便说一句,这是一个臭名昭著的性能反模式,但有时是必要的。
编辑 索引没有帮助。因此,让我们尝试所谓的 延迟加入 ,这样我们就不必订购然后限制来自您的 h
table.[=19= 的所有数据]
从这个子查询开始。它只检索结果中涉及的行的主键值,并且只会生成五行。
SELECT h.history_id, i.task_id
FROM h
JOIN i ON h.task_id = i.task_id
WHERE i.user_id = 42
ORDER BY h.mod_date
LIMIT 5
为什么这个子查询?它处理工作密集型 ORDER BY ... LIMIT 操作,同时仅操作主键和日期。它仍然必须对大量行进行排序,只丢弃除 5 行以外的所有行,但它必须处理的行要短得多。因为这个子查询完成了繁重的工作,所以您专注于优化它,而不是整个查询。
保留我上面建议的索引,因为它 covers h
.
然后,像这样将其加入您的查询的其余部分。这样,您只需为您关心的五行检索昂贵的 h.description
列。
SELECT h.* , i.task_id, i.user_id , i.name, i.completed
FROM h
JOIN i ON i.task_id = h.task_id
JOIN (
SELECT h.history_id, i.task_id
FROM h
JOIN i ON h.task_id = i.task_id
WHERE i.user_id = 42
ORDER BY h.mod_date
LIMIT 5
) selected ON h.history_id = selected.history_id
AND i.task_id = selected.task_id
ORDER BY h.mod_date DESC
LIMIT 5
INDEX(task_id, mod_date, history_id) -- in this order
将“覆盖”并且列将按最佳顺序排列
此外,删除
KEY `TaskID` (`task_id`)
这样优化器就不会被诱惑使用它。