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`)

这样优化器就不会被诱惑使用它。