【问题标题】:MySQL - Slow performance for limit offset on json columnMySQL - json 列上的限制偏移性能缓慢
【发布时间】:2022-01-12 22:37:16
【问题描述】:

在包含 JSON 列的表的 SELECT 语句上使用 LIMIT OFFSET 时,我发现性能很慢。

以下查询需要 3 分钟才能完成:

SELECT t.json_column
FROM table t
LIMIT 501
OFFSET 216204;

但是,如果我只选择t.id,则查询只需要几毫秒。

只有当 JSON 列是 SELECT 语句的一部分时,性能缓慢才可见,但我不明白为什么,或者我可以做些什么来改进它。

为了提供更多上下文,我使用的 MySql 版本是 5.7,该服务在 AWS Aurora 数据库上运行,表上的行数约为 216.000

【问题讨论】:

  • 如果您不提供订单依据,则无法保证您的结果将采用什么顺序,或者两个查询之间的顺序相同,因此您的限制和偏移量毫无意义。你能提供一个更接近你实际在做什么的例子吗?
  • 请包含两个查询的执行计划。
  • 如果不能比较执行计划,这个讨论是没有结果的。投票结束。
  • @TheImpaler - 如果您见过EXPLAINLIMIT,那么您已经全部见过。而且您会知道EXPLAIN 几乎是无用的。
  • 是否有人真正通过大约 400 行来获得此查询?请说明您是如何获得该查询的。

标签: mysql query-optimization amazon-rds amazon-aurora


【解决方案1】:

当 MySQL 使用OFFSET 时,它不能只跳到第 216204 行。MySQL 按值索引索引,而不是按行号。因此它必须实际检查所有这些行,这意味着读取存储这些行的页面。如有必要,将它们从磁盘加载到 RAM 中。

当你只引用t.id时,它可以沿着索引扫描。我假设id 已编入索引(甚至可能是主键),它可能是一个整数(4 个字节)或 bigint(8 个字节)。无论如何,这些条目中的很多都可以放入给定的 InnoDB 页面中(每个页面的大小是固定的,每个 16KB)。

虽然 JSON 列类似于 TEXT、BLOB 或 VARCHAR,但它可能存储在其他页面上,并且比单个整数大得多。所以它需要更多的页面。如果 JSON 文档特别大,每个文档甚至可能需要很多页。

因此,加载所有这些 JSON 文档需要大量存储 I/O。与仅引用主键相比,加载 216,000 个 JSON 文档的 I/O 工作量是其很多倍。无论如何,主键页可能已经缓存在 RAM 中,因此读取它们需要很少或不需要 I/O。

AWS Aurora 可能比传统 MySQL 更糟糕,因为 Aurora 使用分布式存储和复制缓冲池,因此 I/O 和加载到 RAM 中都有额外的开销。

你能做些什么呢?

停止在您的 OFFSET 中使用较大的值。而是按值搜索。

SELECT t.json_column
FROM table t
WHERE t.id >= 208000
LIMIT 501;

这很快,因为索引有助于直接跳到第 208,000 行检查所有前面的行。它只需要检查以 id=208000 (或您搜索的任何值)的行开头的行。一旦为您的LIMIT 找到足够的行,它就会停止检查行。

【讨论】:

  • 这让我很困扰; SELECT json_column FROM table WHERE id in (SELECT id FROM table ORDER BY id LIMIT 501 OFFSET 216204) 的表现是否优于 SELECT json_column FROM table ORDER BY id LIMIT 501 OFFSET 216204?我真的不明白为什么会这样
  • 这只会读取 216204 行的 id,因此它可以避免读取那么多 JSON 文档。然后子查询将最多返回 501 个 id,这将导致外部查询使用索引仅检查 501 行以及相应的 JSON 文档。加载 501 个 JSON 文档的 I/O 通常比加载 216204 个 JSON 文档更快。
  • 如果id是PK,那么主索引不是很小,而是包含了表的所有列。
  • @TheImpaler,如果 JSON 足够大,则不会。长字符串或 blob 类型的数据会溢出到其他页面。
  • SELECT 假设没有 id 低于 20800。有 几种 方法可以让 auto_increment 丢失。
【解决方案2】:

JSON 列很大,对吗?如果是这样,这可能运行得更快:

SELECT t.json_column
    FROM ( SELECT id
        FROM table
        LIMIT 501
        OFFSET 216204 ) AS ids
    JOIN table AS t  USING(id);

内部查询可能能够以足够快的速度获取 501 个 ID,以补偿 JOIN

(不过,请参阅其他答案和评论。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-02-19
    • 1970-01-01
    • 2016-03-09
    • 2021-12-29
    • 2016-06-18
    • 2011-03-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多