【问题标题】:MySQL (id >= N AND col2 IS NULL) query unexpectedly slow for large NMySQL(id >= N AND col2 IS NULL)查询对于大 N 出乎意料地慢
【发布时间】:2015-07-16 09:33:39
【问题描述】:

我们使用的是 MySQL 5.5.42。

我们有一个表 publications 包含大约 1.5 亿行(SSD 上大约 140 GB)。

该表有很多列,其中两列特别有趣:

  • id 是表的主键,类型为 bigint
  • cluster_idbigint 类型的可空列

两列都有自己的(单独的)索引。

我们查询表单

SELECT * FROM publications
WHERE id >= 14032924480302800156 AND cluster_id IS NULL
ORDER BY id
LIMIT 0, 200;

问题出在这里id 值越大(上例中为 14032924480302800156),请求越慢。

换句话说,低id 值的请求很快(id 值越高,请求越慢(最多几分钟)。

如果我们在WHERE 子句中使用另一个(索引)列,一切都很好。比如

SELECT * FROM publications
WHERE inserted_at >= '2014-06-20 19:30:25' AND cluster_id IS NULL
ORDER BY inserted_at
LIMIT 0, 200;

其中inserted_at 的类型为timestamp

编辑:

使用id >= 14032924480302800156EXPLAIN的输出:

id | select_type | table        | type | possible_keys      | key        | key_len | ref   | rows     | Extra
---+-------------+--------------+------+--------------------+------------+---------+-------+----------+------------
1  | SIMPLE      | publications | ref  | PRIMARY,cluster_id | cluster_id | 9       | const | 71647796 | Using where

使用inserted_at >= '2014-06-20 19:30:25'EXPLAIN的输出:

id | select_type | table        | type | possible_keys          | key        | key_len | ref   | rows     | Extra
---+-------------+--------------+------+------------------------+------------+---------+-------+----------+------------
1  | SIMPLE      | publications | ref  | inserted_at,cluster_id | cluster_id | 9       | const | 71647796 | Using where

【问题讨论】:

  • 也许对于更大的ids,cluster_id 匹配请求的值(NULL)更罕见?在这种情况下,对于较大的ids,数据库可能需要遍历更多记录,以便选择带有cluster_id IS NULL 的请求的 200。
  • 显示EXPLAIN 的输出,同时使用小ID和大ID。
  • @SergeRogatch 这是一个很好的评论,我们考虑过,但我很难相信这可以解释三个数量级的减速。
  • @ImreL 我编辑了问题,为两个查询添加了 EXPLAIN 的输出。
  • 第一个请求中的using/forcing PRIMARY index 是否会极大地改变处理时间?如果是这样,我可能有一个理论......

标签: mysql performance


【解决方案1】:

关于 MySQL 使用错误顺序的索引存在一些猜测。 PRIMARY 索引的处理方式似乎与其他索引完全不同。

在具有主键条件索引PRIMARYcluster_id 的查询中可以使用。由于某种原因,MySQL 忽略了PRIMARY 索引并首先查看cluster_id 上的索引,您有一个条件:它应该是NULL。这给我们留下了一个巨大的潜在无序(NULLs 无处不在!)由id 过滤的行集。

但是,对于下一个查询,情况有所不同:PRIMARY 索引根本无法使用,因此 MySQL 会以更好的方式确定使用什么,显然首先在 inserted_at 上使用索引而没有任何提示。

它在第一个查询中实际上应该做的是首先获取PRIMARY 索引(tell it to do so)。我不是 MySQL 用户,我所有的猜测都是基于我自己对内部数据结构的理解。我不知道它是否可以在结果之上应用cluster_id 上的索引,但是创建一个复合索引并比较有无它的性能可能会提供关于它是否被使用的线索。

【讨论】:

  • 谢谢,添加USE INDEX (PRIMARY) 可以解决问题。
  • 您认为重新计算表统计信息有帮助吗? (考虑到数据库的大小,这是一项艰巨的任务......)
  • 好吧,如果你设法比程序更好地猜测要使用的索引 - 试图让它做出更好的决定可能没有用。你已经让它工作了。但是,弄清楚“为什么”可能会导致 MySQL 本身有所改进。
  • 问题是我们并不是真正手动编写 SQL 查询,它们是由实体框架生成的。手动编写 SQL 查询引入了一堆新问题,理想情况下我们希望避免这些问题......添加索引也不是理想的解决方案,因为它需要额外的磁盘空间(在备份等中)。
  • 我不是 MySQL 用户,所以我无法对其维护提供太多帮助。对不起:(
【解决方案2】:

性能问题很可能会发生,因为您只有 idcluster_id 列的单独索引,但不能同时在这两个列上建立索引。

也许对于较大的 id,cluster_id 与请求的值(NULL)匹配的情况更罕见?在这种情况下,对于较大的 id,数据库可能需要遍历更多记录,以便选择带有 cluster_id IS NULL 的请求的 200。

如果您在两列上都有一个索引,则数据库不需要遍历那么多记录,因为它会知道哪些记录同时满足 2 个搜索条件。

【讨论】:

  • 我们考虑过这一点,但这并不能解释为什么使用inserted_at 而不是id 时性能非常好(我们没有同时在inserted_atcluster_id 上建立索引)。
  • 更高的inserted_at是否对应更高的ids?除非您使用 SSD,否则如果使用小数据块随机访问,旋转硬盘很容易使速度降低几个数量级。因为旋转硬盘每秒可以执行 100-200 次寻道操作(这是每秒完整旋转的次数)。
  • 是的,更高的inserted_at 值对应于更高的id 值。
  • 也许这与 BigInt 是 8Bytes 而 Timestamp 是 4Bytes 的事实有关。无论如何,你为什么使用 BigInt ?您是否将拥有超过 40 亿条记录?
  • 这将是一个合理的解释,除了 AIUI,所有 InnoDB 二级索引都在末尾implicitly contain the primary key。所以,AFAIK,cluster_id 索引应该有效地充当(cluster_id, id) 上的复合索引,而这正是这种查询应该受益的索引。
猜你喜欢
  • 2020-10-27
  • 2019-12-28
  • 2014-06-23
  • 1970-01-01
  • 2012-06-26
  • 2014-07-01
  • 2015-05-13
  • 2014-09-19
  • 1970-01-01
相关资源
最近更新 更多