避免完全 Table 扫描分页嵌套集
Avoid Full Table Scan On Paged Nested Set
我的 nested set 设置如下:
Node (Id, ParentId, LeftBounds, RightBounds, Level, Name)
LeftBounds
上有索引。
但是当我尝试 select 分页结果时,
SELECT * FROM Node ORDER BY LeftBounds ASC LIMIT 500000, 1000
Sql 进行完整的 table 扫描。还有什么我应该注意的,以避免完整的 table 扫描吗?
这通常不是什么大问题,但是 table 有几百万行,加载最后一页需要大约 3-5 秒。
您的 LIMIT 5000000, 1000
子句要求 MySQL 对结果集中的结果进行排序,跳过其中的 50 万个,然后显示 1000 个。MySQL 似乎有可能决定最好通过 table 扫描来完成。这并不奇怪。
您可以尝试延迟加入操作。这样做的目的是减少需要排序的结果集的大小。它是这样工作的。
SELECT Node.*
FROM Node
JOIN (
SELECT id
FROM Node
ORDER BY LeftBounds ASC
LIMIT 500000, 1000
) Subset ON Node.id = Subset.id
ORDER BY Node.LeftBounds ASC
如您所见,这将您需要处理的大结果集限制为更少的列,特别是 id
和 LeftBounds
。然后,它使用找到的 1000 个不同 id
值来检索完整记录。
如果您在 (LeftBounds, id)
上为自己创建一个复合索引,您很可能会大大加快此查询的速度。但它仍然必须跳过半百万行,所以你的 EXPLAIN
可能会说你正在进行完整的索引扫描。
为了加快查询速度,您接下来可以做的事情是删除 SELECT *
,而不是命名您需要的列。为什么这有帮助?因为它给出了可能有助于完全满足查询的复合覆盖索引的提示。您已经提到 LeftBounds
是唯一的,因此是 JOIN
标准的候选者。那么,让我们用一个例子来探讨一下。假设您想要 ParentId, LeftBounds, RightBounds, Level, Name
在您的结果集中。然后你可以使用这个查询:
SELECT Node.ParentId, Node.LeftBounds,
Node.RightBounds, Node.Level, Node.Name
FROM Node
JOIN (
SELECT LeftBounds
FROM Node
ORDER BY LeftBounds ASC
LIMIT 500000, 1000
) Subset ON Node.LeftBounds = Subset.LeftBounds
ORDER BY Node.LeftBounds ASC
如果你需要的列上有索引,MySQL可以从索引上满足查询。该索引应按此顺序包含这些列。
LeftBounds, ParentId, RightBounds, Level, Name
LeftBounds
需要在索引中排在第一位,因为这是您用于随机访问索引的列。这里的重点是省略必须使用 id
列来访问 table.
我的 nested set 设置如下:
Node (Id, ParentId, LeftBounds, RightBounds, Level, Name)
LeftBounds
上有索引。
但是当我尝试 select 分页结果时,
SELECT * FROM Node ORDER BY LeftBounds ASC LIMIT 500000, 1000
Sql 进行完整的 table 扫描。还有什么我应该注意的,以避免完整的 table 扫描吗?
这通常不是什么大问题,但是 table 有几百万行,加载最后一页需要大约 3-5 秒。
您的 LIMIT 5000000, 1000
子句要求 MySQL 对结果集中的结果进行排序,跳过其中的 50 万个,然后显示 1000 个。MySQL 似乎有可能决定最好通过 table 扫描来完成。这并不奇怪。
您可以尝试延迟加入操作。这样做的目的是减少需要排序的结果集的大小。它是这样工作的。
SELECT Node.*
FROM Node
JOIN (
SELECT id
FROM Node
ORDER BY LeftBounds ASC
LIMIT 500000, 1000
) Subset ON Node.id = Subset.id
ORDER BY Node.LeftBounds ASC
如您所见,这将您需要处理的大结果集限制为更少的列,特别是 id
和 LeftBounds
。然后,它使用找到的 1000 个不同 id
值来检索完整记录。
如果您在 (LeftBounds, id)
上为自己创建一个复合索引,您很可能会大大加快此查询的速度。但它仍然必须跳过半百万行,所以你的 EXPLAIN
可能会说你正在进行完整的索引扫描。
为了加快查询速度,您接下来可以做的事情是删除 SELECT *
,而不是命名您需要的列。为什么这有帮助?因为它给出了可能有助于完全满足查询的复合覆盖索引的提示。您已经提到 LeftBounds
是唯一的,因此是 JOIN
标准的候选者。那么,让我们用一个例子来探讨一下。假设您想要 ParentId, LeftBounds, RightBounds, Level, Name
在您的结果集中。然后你可以使用这个查询:
SELECT Node.ParentId, Node.LeftBounds,
Node.RightBounds, Node.Level, Node.Name
FROM Node
JOIN (
SELECT LeftBounds
FROM Node
ORDER BY LeftBounds ASC
LIMIT 500000, 1000
) Subset ON Node.LeftBounds = Subset.LeftBounds
ORDER BY Node.LeftBounds ASC
如果你需要的列上有索引,MySQL可以从索引上满足查询。该索引应按此顺序包含这些列。
LeftBounds, ParentId, RightBounds, Level, Name
LeftBounds
需要在索引中排在第一位,因为这是您用于随机访问索引的列。这里的重点是省略必须使用 id
列来访问 table.