【问题标题】:MySQL Query Execution Plan With Limit Different Based on WhereMySQL Query Execution Plan with Limit 根据 Where 不同
【发布时间】:2017-03-11 15:32:16
【问题描述】:

我有一个带有限制的查询,该限制根据 where close 中的值提供截然不同的执行时间。我正在使用 MySQL 5.1,并在我最大的表“警报”中查看大约 100,000,000 条记录。

当我看到这个执行计划时,我很高兴,我的查询不到一秒钟:

mysql> EXPLAIN SELECT alert.id, category.severity, category.classification, category.completion, alert.description,
->        alert.detectTime, analyzer.name, analyzer.manufacturer, analyzer.model, analyzer.version,
->        analyzer.class, analyzer.osType, analyzer.osVersion, analyzerNode.hostName,
->        analyzerNode.address, sourceNode.hostName, sourceNode.address, targetNode.hostName,
->        targetNode.address, sourceUser.userName, sourceUser.uid, targetUser.userName, targetUser.uid,
->        alert.filePath, alert.sourceProcessPid, alert.sourceProcessPath, alert.targetProcessPid,
->        alert.targetProcessPath, alert.acknowledgeUserName, alert.acknowledgeTime, reference.url, reference.meaning
-> FROM alerts AS alert
->      LEFT JOIN categories AS category ON category.id = alert.categoryId
->      LEFT JOIN analyzers AS analyzer ON analyzer.id = alert.analyzerId
->      LEFT JOIN nodes AS analyzerNode ON analyzerNode.id = alert.analyzerNodeId
->      LEFT JOIN nodes AS sourceNode ON sourceNode.id = alert.sourceNodeId
->      LEFT JOIN nodes AS targetNode ON targetNode.id = alert.targetNodeId
->      LEFT JOIN users AS sourceUser ON sourceUser.id = alert.sourceUserId
->      LEFT JOIN users AS targetUser ON targetUser.id = alert.targetUserId
->      LEFT JOIN `references` AS reference ON reference.id = alert.referenceId
-> WHERE category.severity = 'info'
-> ORDER BY alert.id DESC
-> LIMIT 40 OFFSET 0;
  • |编号 |选择类型 |表|类型 |可能的键 |关键 | key_len |参考 |行 |额外 |
  • | 1 |简单 |警报 |索引 |类别索引 |初级 | 8 |空 | 40 | |
  • | 1 |简单 |类别 | eq_ref |初级,严重性指数 |初级 | 8 | am.alert.categoryId | 1 |使用位置 |
  • | 1 |简单 |分析仪| eq_ref |初级 |初级 | 8 | am.alert.analyzerId | 1 | |
  • | 1 |简单 |分析器节点 | eq_ref |初级 |初级 | 8 | am.alert.analyzerNodeId | 1 | |
  • | 1 |简单 |源节点 | eq_ref |初级 |初级 | 8 | am.alert.sourceNodeId | 1 | |
  • | 1 |简单 |目标节点 | eq_ref |初级 |初级 | 8 | am.alert.targetNodeId | 1 | |
  • | 1 |简单 |来源用户 | eq_ref |初级 |初级 | 8 | am.alert.sourceUserId | 1 | |
  • | 1 |简单 |目标用户 | eq_ref |初级 |初级 | 8 | am.alert.targetUserId | 1 | |
  • | 1 |简单 |参考 | eq_ref |初级 |初级 | 8 | am.alert.referenceId | 1 | |

但是,当我只是更改 where 子句的值(在本例中是一个枚举,但我认为这无关紧要)时,我会得到截然不同的结果,而且我的查询需要很长时间。

mysql> EXPLAIN SELECT alert.id, category.severity, category.classification, category.completion, alert.description,
->        alert.detectTime, analyzer.name, analyzer.manufacturer, analyzer.model, analyzer.version,
->        analyzer.class, analyzer.osType, analyzer.osVersion, analyzerNode.hostName,
->        analyzerNode.address, sourceNode.hostName, sourceNode.address, targetNode.hostName,
->        targetNode.address, sourceUser.userName, sourceUser.uid, targetUser.userName, targetUser.uid,
->        alert.filePath, alert.sourceProcessPid, alert.sourceProcessPath, alert.targetProcessPid,
->        alert.targetProcessPath, alert.acknowledgeUserName, alert.acknowledgeTime, reference.url, reference.meaning
-> FROM alerts AS alert
->      LEFT JOIN categories AS category ON category.id = alert.categoryId
->      LEFT JOIN analyzers AS analyzer ON analyzer.id = alert.analyzerId
->      LEFT JOIN nodes AS analyzerNode ON analyzerNode.id = alert.analyzerNodeId
->      LEFT JOIN nodes AS sourceNode ON sourceNode.id = alert.sourceNodeId
->      LEFT JOIN nodes AS targetNode ON targetNode.id = alert.targetNodeId
->      LEFT JOIN users AS sourceUser ON sourceUser.id = alert.sourceUserId
->      LEFT JOIN users AS targetUser ON targetUser.id = alert.targetUserId
->      LEFT JOIN `references` AS reference ON reference.id = alert.referenceId
-> WHERE category.severity = 'high'
-> ORDER BY alert.id DESC
-> LIMIT 40 OFFSET 0;
  • |编号 |选择类型 |表|类型 |可能的键 |关键 | key_len |参考 |行 |额外 |
  • | 1 |简单 |类别 |参考 |初级,严重性指数 |严重性指数 | 2 |常量 | 8 |使用哪里;使用临时的;使用文件排序 |
  • | 1 |简单 |警报 |参考 |类别索引 |类别索引 | 8 | am.category.id | 3883428 | |
  • | 1 |简单 |分析仪| eq_ref |初级 |初级 | 8 | am.alert.analyzerId | 1 | |
  • | 1 |简单 |分析器节点 | eq_ref |初级 |初级 | 8 | am.alert.analyzerNodeId | 1 | |
  • | 1 |简单 |源节点 | eq_ref |初级 |初级 | 8 | am.alert.sourceNodeId | 1 | |
  • | 1 |简单 |目标节点 | eq_ref |初级 |初级 | 8 | am.alert.targetNodeId | 1 | |
  • | 1 |简单 |来源用户 | eq_ref |初级 |初级 | 8 | am.alert.sourceUserId | 1 | |
  • | 1 |简单 |目标用户 | eq_ref |初级 |初级 | 8 | am.alert.targetUserId | 1 | |
  • | 1 |简单 |参考 | eq_ref |初级 |初级 | 8 | am.alert.referenceId | 1 | |

请注意,第一个执行的第一步是有限制,而另一个没有。有没有强制 MySQL 查询优化器像这样首先执行限制?

【问题讨论】:

  • 如果将第一个联接更改为INNER JOIN,会发生什么情况? 您的WHERE 无论如何都有效地使它成为一个,所以它不应该改变您的结果。
  • 您可以使用优化器提示:dev.mysql.com/doc/refman/5.7/en/optimizer-hints.html。另一种方法可能是添加索引,使错误的执行计划变得更好,或者让优化器创建另一个计划。
  • INNER JOIN 没有改变查询或执行计划的性能。至于优化器提示,老实说,我没有看到任何能抓住我的东西作为我的问题的解决方案(让限制先行)。我愿意接受建议。

标签: mysql sql


【解决方案1】:

使用 ORDER BY 和 LIMIT 时,重要的是 order by 在索引上。

如果优化器决定选择另一个索引,则不会应用限制,因为会检索结果并扫描更多记录。

在您的情况下,您的“高”警报可能少于“信息”警报,并且优化器偏爱类别表中的严重性索引。

可以尝试用FORCE INDEX强制对alert表进行PK或者尝试忽略category表的Severity index。

这个article 似乎很有用。

【讨论】:

    猜你喜欢
    • 2020-04-18
    • 2012-05-04
    • 2013-01-03
    • 2013-01-01
    • 1970-01-01
    • 2018-07-25
    • 2017-04-25
    • 2021-05-19
    • 1970-01-01
    相关资源
    最近更新 更多