MySQL 单个 table 上的 ORDERBY 日期时间性能

MySQL Performance ORDERBY datetime on single table

我有一个 MySql 事件 table 像这样:

+---------------------+--------------+------+-----+---------+----------------+
| Field               | Type         | Null | Key | Default | Extra          |
+---------------------+--------------+------+-----+---------+----------------+
| EventId             | int(11)      | NO   | PRI | NULL    | auto_increment |
| ControllerId        | int(11)      | NO   | MUL | NULL    |                |
| EventTypeId         | int(11)      | NO   | MUL | NULL    |                |
| DateTime            | datetime(3)  | NO   | MUL | NULL    |                |
| InputId             | int(11)      | YES  | MUL |         |                |
...
| AdditionalInfo      | text         | YES  |     |         |                |
+---------------------+--------------+------+-----+---------+----------------+

目前它有 200M 条记录,并且是 运行 文件。为了加快速度,我不使用任何连接查询它,但现在我开始进行一些长 运行 查询。一个慢 运行 查询的例子是这样的:

SELECT E.* 
FROM Event E
WHERE (E.EventTypeId != 4 OR (E.EventTypeId = 4 AND E.InputId IS NOT NULL)) 
AND E.EventTypeId != 27 AND E.EventTypeId != 12  
AND E.ControllerId in (5190, 5191, 5192, 5193)
ORDER BY E.DateTime DESC
LIMIT 0, 200

该查询需要 5 分钟!解释的重要(我认为)部分如下:

"key_length": "7",
"rows_examined_per_scan": 180071,
"rows_produced_per_join": 125770,
"filtered": "0.06",
"cost_info": {
    "read_cost":    "284389.84",
    "eval_cost":    " 25154.17",
    "prefix_cost":  "309544.01",
    "data_read_per_join": "20M"
},

现在,如果我删除查询末尾的 ORDER BY E.DateTime DESC,大约需要 0.1 秒才能完成。我已经有了 DateTime 的索引。

我想我理解服务器必须读取所有 180k 的概念? WHERE 子句返回的行在返回给客户端之前对它们进行排序,但为什么要花这么长时间?有什么我可以做的吗?复合索引在这里有帮助吗?

排序 180k 行应该不需要 5 分钟,除非您的硬件真的非常慢。对于此查询:

SELECT E.* 
FROM Event E
WHERE (E.EventTypeId <> 4 OR (E.EventTypeId = 4 AND E.InputId IS NOT NULL)
      )  AND
      E.EventTypeId NOT IN (12, 27) AND
      E.ControllerId in (5190, 5191, 5192, 5193)
ORDER BY E.DateTime DESC
LIMIT 0, 200;

您可以尝试索引 (ControllerId, EventTypeId, InputId)。但是,我猜这不会很好。

一种可能是使用上面的索引,然后一次做一个控制器:

(SELECT E.* 
 FROM Event E
 WHERE (E.EventTypeId <> 4 OR (E.EventTypeId = 4 AND E.InputId IS NOT NULL)
       )  AND
       E.EventTypeId NOT IN (12, 27) AND
       E.ControllerId  = 5190
 ORDER BY E.DateTime DESC
 LIMIT 0, 200
) UNION ALL
(SELECT E.* 
 FROM Event E
 WHERE (E.EventTypeId <> 4 OR (E.EventTypeId = 4 AND E.InputId IS NOT NULL)
       )  AND
       E.EventTypeId NOT IN (12, 27) AND
       E.ControllerId  = 5191
 ORDER BY E.DateTime DESC
 LIMIT 0, 200
) 
. . . 
ORDER BY DateTime DESC
LIMIT 0, 200;

索引可以更有效地用于每个子查询。

你对 JOINs 的恐惧是错误的。当然,有些 JOINs 是昂贵的,但其他一些通过避免 JOIN.

会慢得多

您这里的查询无法通过 changing/adding 索引进行优化。

没有 ORDER BY,它快速扫描 table 的 部分 ,找到 200 行并退出。使用 ORDER BY,它必须扫描整个 table,排序,然后 剥离 200 行。

戈登索引可能是最好的选择。但是,如果 IN 列表是动态的,则意味着动态构建 UNION

MySQL 的较新版本将更好地利用他的 3 列 INDEX 因为过滤现在完全在 InnoDB 引擎中完成,而不是反弹回通用 'handler'.

这可以简化

(E.EventTypeId != 4 OR (E.EventTypeId = 4 AND E.InputId IS NOT NULL))

至 (E.EventTypeId != 4 或 E.InputId 不为空)

但它不会加快速度以引起注意。

请使用SHOW CREATE TABLE;它比 DESCRIBE.

更具描述性

INT 允许 +/-20 亿的巨大范围。需要那么多id吗?它需要 4 个字节。考虑其他大小,例如 SMALLINT UNSIGNED(2 字节,0..65K)。缩小 table 大小对性能有一些影响。

你真的需要E.*吗?如果您不需要所有的列,拼出您确实需要的列会更快 运行;在某些情况下明显更快。

还有一件事...如果您是 "paginating",那么有一个技巧可以用来处理 UNION + LIMIT + OFFSET;见 here