【问题标题】:Function of deferred join in MySQLMySQL中延迟连接的功能
【发布时间】:2015-10-11 20:34:53
【问题描述】:

我正在阅读高性能 MySQL,我对延迟连接有点困惑。

书上说下面的操作不能通过 index(sex, rating) 进行优化,因为高偏移量需要他们花费大部分时间扫描大量数据,然后他们会丢弃。

mysql> SELECT <cols> FROM profiles WHERE sex='M' ORDER BY rating LIMIT 100000, 10;

虽然延迟连接有助于最大程度地减少 MySQL 必须完成的收集数据的工作量,但它只会丢弃。

  SELECT <cols> FROM profiles INNER JOIN (
  SELECT <primary key cols> FROM profiles
  WHERE x.sex='M' ORDER BY rating LIMIT 100000, 10
  ) AS x USING(<primary key cols>);

为什么延迟连接会最大限度地减少收集的数据量。

【问题讨论】:

  • 所以人们真的可以提出好的问题吗?
  • @Hanky웃Panky 是的,看起来是这样。好久不见的第一个。
  • 这种方法将避免获取除sex,rating以外的列的数据和100k行的主键数据,并且只获取返回的10行数据。

标签: mysql database indexing


【解决方案1】:

您提供的示例假定使用了 InnoDB。假设PRIMARY KEY 就是id

INDEX(sex, rating)

是“辅助键”。每个辅助键(在 InnoDB 中)都隐含地包含 PK,因此它实际上是 (sex, rating, id) 值的有序列表。为了获取“数据”(&lt;cols&gt;),它使用id 向下钻取PK BTree(也包含数据)以查找记录。

快速案例:因此,

SELECT id FROM profiles
    WHERE x.sex='M' ORDER BY rating LIMIT 100000, 10

将对索引中的 100010 个“行”进行“范围扫描”。这对于 I/O 来说非常有效,因为所有信息都是连续的,没有任何浪费。 (不,跳过 100000 行是不够聪明的;那会很混乱,尤其是考虑到 transaction_isolation_mode 时。)这 100010 行可能适合大约 1000 个索引块。然后得到id的10个值。

使用这 10 个 id,它可以进行 10 次连接(“NLJ”=“嵌套循环连接”)。这 10 行很可能分散在桌子周围,可能需要对磁盘进行 10 次点击。

让我们“计算磁盘命中数”(忽略 BTree 中的非叶节点,它们很可能会被缓存):1000 + 10 = 1010。在普通磁盘上,这可能需要 10 秒。

Slow Case:现在让我们看看原始查询 (SELECT &lt;cols&gt; FROM profiles WHERE sex='M' ORDER BY rating LIMIT 100000, 10;)。让我们继续假设INDEX(sex, rating) 加上最后隐含的id

和以前一样,它将索引扫描 100010 行(估计 1000 次磁盘命中)。但事实上,做上面所做的事情太愚蠢了。它将进入数据以获取&lt;cols&gt;。这通常(取决于缓存)需要随机磁盘命中。这可能超过 100010 次磁盘命中(如果表很大并且缓存不是很有用)。

再一次,100000 被抛,10 被交付。总“成本”:100010 次磁盘命中(最坏情况),可能需要 17 分钟。

请记住,高性能 MySQL 有 3 个版本;它们是在过去 13 年左右的时间里写成的。您可能使用的 MySQL 版本比他们介绍的要新得多。我不知道优化器是否在这方面变得更聪明了。这些,如果对你可用,可能会提供线索:

EXPLAIN FORMAT=JSON SELECT ...;
OPTIMIZER TRACE...

我最喜欢的用于研究事物如何工作的“处理程序”技巧可能会有所帮助:

FLUSH STATUS;
SELECT ...
SHOW SESSION STATUS LIKE 'Handler%'.

您可能会看到 100000 和 10 之类的数字,或者是此类数字的小倍数。但是,请记住,索引的快速范围扫描计为每行 1 个,对于大量 &lt;cols&gt; 的慢速随机磁盘命中也是如此。

概述:为了使这项技术发挥作用,子查询需要一个“覆盖”索引,并且列的顺序正确。

“覆盖”表示(sex, rating, id) 包含所有触及的列。 (我们假设&lt;cols&gt; 包含其他列,可能是在INDEX 中无法使用的庞大列。)

列的“正确”排序:列的顺序正好可以通过查询。 (另见my cookbook。)

  • 首先将任何WHERE 列与= 比较到常量。 (sex)
  • 然后依次出现整个ORDER BY。 (rating)
  • 最后是“覆盖”。 (id)

【讨论】:

  • “上面做了什么”是什么意思?而且我确实认为原始查询不需要访问 ...我不知道为什么需要访问列..
  • @inherithandle - 简单地删除带有“上面完成”的句子可能会使其更具可读性。我做了一些进一步的研究,但找不到一种方法来“证明”额外的 100000 组 &lt;cols&gt; 是否被不必要地拖着走。 (Handler_read* 没有帮助,但确实显示了 MariaDB 和 Oracle 之间的差异。)。
  • @RickJames “尤其是考虑到 transaction_isolation_mode 时”是什么意思,transaction_isolation_mode 会因为 mvcc 使用更多内存还是运行速度变慢?
【解决方案2】:

来自官方(https://dev.mysql.com/doc/refman/5.7/en/limit-optimization.html)的以下描述:

如果将 LIMIT row_count 与 ORDER BY 结合使用,MySQL 会在找到排序结果的前 row_count 行后立即停止排序,而不是对整个结果进行排序。如果使用索引进行排序,则速度非常快。如果必须进行文件排序,则在找到第一个 row_count 之前,将选择与查询匹配且没有 LIMIT 子句的所有行,并对它们中的大部分或全部进行排序。找到初始行后,MySQL 不对结果集的任何剩余部分进行排序。

我们可以看到它们应该没有区别。

但是 percona 提出了这个建议,并给出了测试数据。但是不要给出任何理由,我认为在处理这种情况时,mysql中可能存在一些“错误”。所以我们只是认为这是一种有用的经验。

【讨论】:

  • 那里的报价相当草率。 “所有行......都找到”是指使用“优先排序”。此技术查看所有行,进行足够的排序以获得LIMIT行。
猜你喜欢
  • 2011-05-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多