mysql - 优化连接的 table 列上的 ORDER BY COALESCE
mysql - Optimizing ORDER BY COALESCE on joined table column
已编辑:按要求添加了完整查询。
本质上,我有一个 table of posts 一对多链接到 table of reposts,类似于 Twitter。我想加载按 repost(如果存在)时间或原始 post 时间排序的 posts。但是,使用单个查询的排序过程非常慢(可能是因为 COALESCE(x, y) 没有充分利用 MySQL 索引)。两个相关 table 上的时间列已编入索引。
我的查询看起来像这样。
SELECT * FROM Post p LEFT JOIN p.reposts ON ... WHERE ...
ORDER BY COALESCE(r.time, p.time) LIMIT 0, 10
更准确地说(伪)因为我使用的是 DAL:
SELECT * FROM Post p LEFT JOIN p.reposts repost ON (p.id = repost.post_id AND
repost.time = (
SELECT MIN(r.time) FROM Repost r WHERE p.id = r.post_id
AND r.user_id IN (1, 2, 3...) AND r.user_id NOT IN (4, 5, 6...))
))
WHERE (repost IS NOT NULL OR p.author_id IN (1, 2, 3...))
AND p.author_id NOT IN (4, 5, 6...)
ORDER BY COALESCE(repost.time, p.time) LIMIT 0, 10
上面的ON子句保证最多加入一个repost(我想要的那个)。 COALESCE 是必要的,因为如果 post 没有被重新 posted,r 可能为 NULL。查询的行为符合预期——当省略 ORDER BY 子句或仅在 p.time 等索引列上使用时速度很快。这是意料之中的,因为 Post table 有超过 100k 行。
查询说明
编辑:更好地解释查询应该做什么。值得注意的是这里的逻辑有效——我得到了我想要的数据。问题是应用 ORDER BY 子句导致查询 运行 大约慢 50 倍,因为 MySQL 不能在连接的 table.
上使用带有 COALESCE 的索引
- 加载 10 个 post 的列表,这些列表要么由一组用户创作(跟随),要么由同一组用户重新post编辑(跟随),按最新排序。
- Posts 应按 post 的时间或第一个 post.
的时间排序
忽略不同组中用户的 posts 和重新posts(已阻止)
从 posts
获取 posts: SELECT
- 获取以下用户最早重新post:LEFT JOIN ON... r.time = (SELECT MIN(r.time).. .)
- 过滤掉 post 未由用户在以下集合中创作或重新post编辑的内容:WHERE (repost IS NOT NULL...)
- Order 为第一个 repost(如果存在)或发布时间:ORDER BY COALESCE(repost.time, p.time)
- 最多加载 10 post 秒:LIMIT 0、10
更新
我发现:
...ORDER BY repost.time DESC
生成的结果也很慢,除非我还添加:
...WHERE repost.id IS NOT NULL...
在这种情况下查询速度很快。这使我相信真正的问题是对可为空的列索引进行排序。我也试过:
... ORDER BY CASE WHEN repost.id IS NULL p.time ELSE repost.time END DESC
这没有帮助。
更新 2
由于 MySQL 使用 b 树作为其索引,似乎无法按照我想要的方式利用索引。因此,我目前最好的想法是将每个原始 post 视为其作者的 "repost",然后执行我的 select 并在 repost table 上订购,例如
SELECT * FROM Repost r LEFT JOIN r.post ON ... WHERE ... ORDER BY r.time DESC
这里的问题正如我在问题的更新 2 中所描述的那样。 MySQL 使用索引快速执行 ORDER BY 操作。更具体地说,MySQL 使用 B-trees 来索引列(例如时间戳 - p.time/r.time),这会占用更多 space 但可以加快排序速度。
我的查询的问题是它在两个 table 中按时间列排序,使用来自 repost table 的时间戳(如果可用)和 post table 否则。由于 MySQL 无法合并来自两个 table 的 B 树,因此它无法对来自两个不同 table 的列执行快速索引排序。
我通过两种方式修改了我的查询和 table 结构来解决这个问题。
1) 首先根据被阻止的用户进行过滤,因此只需要对当前用户可以访问的 post 进行排序。这不是问题的根源,而是实际优化。例如
SELECT * FROM (SELECT * FROM Post p WHERE p.author_id NOT IN (4, 5, 6...))...
2) 将每个 post 视为其作者的 repost,因此每个 post 都保证有一个可连接的 repost 和 repost.time 对其进行索引和排序。例如
SELECT * FROM (...) LEFT JOIN p.reposts repost ON (p.id = repost.post_id AND
repost.time = (
SELECT MIN(r.time) FROM Repost r WHERE p.id = r.post_id
AND r.user_id IN (1, 2, 3...) AND r.user_id NOT IN (4, 5, 6...))
))
WHERE (repost.id IS NOT NULL) ORDER BY repost.time DESC LIMIT 0, 10
最终问题归结为 ORDER BY - 这种方法将查询时间从大约 8 秒减少到 20 毫秒。
已编辑:按要求添加了完整查询。
本质上,我有一个 table of posts 一对多链接到 table of reposts,类似于 Twitter。我想加载按 repost(如果存在)时间或原始 post 时间排序的 posts。但是,使用单个查询的排序过程非常慢(可能是因为 COALESCE(x, y) 没有充分利用 MySQL 索引)。两个相关 table 上的时间列已编入索引。
我的查询看起来像这样。
SELECT * FROM Post p LEFT JOIN p.reposts ON ... WHERE ...
ORDER BY COALESCE(r.time, p.time) LIMIT 0, 10
更准确地说(伪)因为我使用的是 DAL:
SELECT * FROM Post p LEFT JOIN p.reposts repost ON (p.id = repost.post_id AND
repost.time = (
SELECT MIN(r.time) FROM Repost r WHERE p.id = r.post_id
AND r.user_id IN (1, 2, 3...) AND r.user_id NOT IN (4, 5, 6...))
))
WHERE (repost IS NOT NULL OR p.author_id IN (1, 2, 3...))
AND p.author_id NOT IN (4, 5, 6...)
ORDER BY COALESCE(repost.time, p.time) LIMIT 0, 10
上面的ON子句保证最多加入一个repost(我想要的那个)。 COALESCE 是必要的,因为如果 post 没有被重新 posted,r 可能为 NULL。查询的行为符合预期——当省略 ORDER BY 子句或仅在 p.time 等索引列上使用时速度很快。这是意料之中的,因为 Post table 有超过 100k 行。
查询说明
编辑:更好地解释查询应该做什么。值得注意的是这里的逻辑有效——我得到了我想要的数据。问题是应用 ORDER BY 子句导致查询 运行 大约慢 50 倍,因为 MySQL 不能在连接的 table.
上使用带有 COALESCE 的索引- 加载 10 个 post 的列表,这些列表要么由一组用户创作(跟随),要么由同一组用户重新post编辑(跟随),按最新排序。
- Posts 应按 post 的时间或第一个 post. 的时间排序
忽略不同组中用户的 posts 和重新posts(已阻止)
从 posts
获取 posts: SELECT
- 获取以下用户最早重新post:LEFT JOIN ON... r.time = (SELECT MIN(r.time).. .)
- 过滤掉 post 未由用户在以下集合中创作或重新post编辑的内容:WHERE (repost IS NOT NULL...)
- Order 为第一个 repost(如果存在)或发布时间:ORDER BY COALESCE(repost.time, p.time)
- 最多加载 10 post 秒:LIMIT 0、10
更新
我发现:
...ORDER BY repost.time DESC
生成的结果也很慢,除非我还添加:
...WHERE repost.id IS NOT NULL...
在这种情况下查询速度很快。这使我相信真正的问题是对可为空的列索引进行排序。我也试过:
... ORDER BY CASE WHEN repost.id IS NULL p.time ELSE repost.time END DESC
这没有帮助。
更新 2
由于 MySQL 使用 b 树作为其索引,似乎无法按照我想要的方式利用索引。因此,我目前最好的想法是将每个原始 post 视为其作者的 "repost",然后执行我的 select 并在 repost table 上订购,例如
SELECT * FROM Repost r LEFT JOIN r.post ON ... WHERE ... ORDER BY r.time DESC
这里的问题正如我在问题的更新 2 中所描述的那样。 MySQL 使用索引快速执行 ORDER BY 操作。更具体地说,MySQL 使用 B-trees 来索引列(例如时间戳 - p.time/r.time),这会占用更多 space 但可以加快排序速度。
我的查询的问题是它在两个 table 中按时间列排序,使用来自 repost table 的时间戳(如果可用)和 post table 否则。由于 MySQL 无法合并来自两个 table 的 B 树,因此它无法对来自两个不同 table 的列执行快速索引排序。
我通过两种方式修改了我的查询和 table 结构来解决这个问题。
1) 首先根据被阻止的用户进行过滤,因此只需要对当前用户可以访问的 post 进行排序。这不是问题的根源,而是实际优化。例如
SELECT * FROM (SELECT * FROM Post p WHERE p.author_id NOT IN (4, 5, 6...))...
2) 将每个 post 视为其作者的 repost,因此每个 post 都保证有一个可连接的 repost 和 repost.time 对其进行索引和排序。例如
SELECT * FROM (...) LEFT JOIN p.reposts repost ON (p.id = repost.post_id AND
repost.time = (
SELECT MIN(r.time) FROM Repost r WHERE p.id = r.post_id
AND r.user_id IN (1, 2, 3...) AND r.user_id NOT IN (4, 5, 6...))
))
WHERE (repost.id IS NOT NULL) ORDER BY repost.time DESC LIMIT 0, 10
最终问题归结为 ORDER BY - 这种方法将查询时间从大约 8 秒减少到 20 毫秒。