MySQL - json 列上的限制偏移性能缓慢
MySQL - Slow performance for limit offset on json column
我发现在对包含 JSON 列的 table 的 SELECT 语句中使用 LIMIT OFFSET 时性能下降。
以下查询需要 3 分钟才能完成:
SELECT t.json_column
FROM table t
LIMIT 501
OFFSET 216204;
但是,如果我只select t.id
,查询只需要几毫秒。
仅当 JSON 列是 SELECT 语句的一部分时才可见性能低下,但我不明白为什么,或者我可以做些什么来改进它。
为了提供更多上下文,我使用的 MySql 版本是 5.7,该服务在 AWS Aurora 数据库上是 运行,table 上的行数是~216.000
当MySQL使用OFFSET
时,它不能直接跳到第216204行。MySQL按值索引,而不是按行号索引。所以它实际上必须检查所有这些行,这意味着读取存储这些行的页面。如有必要,将它们从磁盘加载到 RAM。
当你只引用t.id
时,它可以沿着索引进行扫描。我假设 id
被索引(甚至可能是主键),它可能是一个整数(4 字节)或 bigint(8 字节)。无论如何,这些条目中的很多都可以放入给定的 InnoDB 页面(每个页面大小固定,每个 16KB)。
而 JSON 列类似于 TEXT 或 BLOB 或 VARCHAR,因为它可能存储在其他页面上,并且比单个整数大得多。所以它需要更多的页面。如果 JSON 个文档特别大,每个文档甚至可能需要很多页。
因此,加载所有这些 JSON 文档需要大量存储空间 I/O。与仅引用主键相比,I/O 加载 216,000 个 JSON 文档的工作量是很多倍。无论如何,主键页面可能已经缓存在 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
.
,它就会停止检查行
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
。
(不过,请参阅其他答案和评论。)
我发现在对包含 JSON 列的 table 的 SELECT 语句中使用 LIMIT OFFSET 时性能下降。
以下查询需要 3 分钟才能完成:
SELECT t.json_column
FROM table t
LIMIT 501
OFFSET 216204;
但是,如果我只select t.id
,查询只需要几毫秒。
仅当 JSON 列是 SELECT 语句的一部分时才可见性能低下,但我不明白为什么,或者我可以做些什么来改进它。
为了提供更多上下文,我使用的 MySql 版本是 5.7,该服务在 AWS Aurora 数据库上是 运行,table 上的行数是~216.000
当MySQL使用OFFSET
时,它不能直接跳到第216204行。MySQL按值索引,而不是按行号索引。所以它实际上必须检查所有这些行,这意味着读取存储这些行的页面。如有必要,将它们从磁盘加载到 RAM。
当你只引用t.id
时,它可以沿着索引进行扫描。我假设 id
被索引(甚至可能是主键),它可能是一个整数(4 字节)或 bigint(8 字节)。无论如何,这些条目中的很多都可以放入给定的 InnoDB 页面(每个页面大小固定,每个 16KB)。
而 JSON 列类似于 TEXT 或 BLOB 或 VARCHAR,因为它可能存储在其他页面上,并且比单个整数大得多。所以它需要更多的页面。如果 JSON 个文档特别大,每个文档甚至可能需要很多页。
因此,加载所有这些 JSON 文档需要大量存储空间 I/O。与仅引用主键相比,I/O 加载 216,000 个 JSON 文档的工作量是很多倍。无论如何,主键页面可能已经缓存在 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
.
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
。
(不过,请参阅其他答案和评论。)