当前位置: 首页 > 知识库问答 >
问题:

优化排序依据

乜思淼
2023-03-14

我正在尝试优化这个查询,该查询通过信誉字段(第1个)和ID字段(第2个)对帖子进行排序。没有第一个字段查询需要大约0.250秒,但有了它需要大约2.500秒(意味着慢了10倍,可怕)。有什么建议吗?

SELECT -- everything is ok here
FROM posts AS p
ORDER BY 
    -- 1st: sort by reputation if exists (1 reputation = 1 day)
    (CASE WHEN p.created_at >= unix_timestamp(now() - INTERVAL p.reputation DAY) 
        THEN +p.reputation ELSE NULL END) DESC, -- also used 0 instead of NULL
    -- 2nd: sort by id dec
    p.id DESC
WHERE p.status = 'published' -- the only thing for filter
LIMIT 0,10 -- limit provided as well

注意:
-使用InnoDB(MySQL 5.7.19)
-主要是posts表上的ID
-字段同时被索引created_at信誉

解释结果:

# id,  select_type, table, partitions, type,  possible_keys, key,  key_len, ref,  rows,    filtered, Extra
# '1', 'SIMPLE',    'p',   NULL,       'ALL', NULL,          NULL, NULL,    NULL, '31968', '100.00', 'Using filesort'

更新^^

信誉规定:一个帖子,多少(n=信誉)天可以显示在列表的顶部。

实际上,我试图给一些帖子的声誉,可以在列表的顶部,并找到解决办法:订单的帖子由“代表”,但只有“一”天的限制。但是过了一段时间(大约2年),由于表数据量的增加,这个解决方案现在成了一个问题。如果我不能解决这个问题,那么我应该从服务中删除该功能。

更新^^

-- all date's are unix timestamp (bigint)
SELECT p.*
    , u.name user_name, u.status user_status
    , c.name city_name, t.name town_name, d.name dist_name
    , pm.meta_name, pm.meta_email, pm.meta_phone
    -- gets last comment as json
    , (SELECT concat("{", 
        '"id":"', pc.id, '",', 
        '"content":"', replace(pc.content, '"', '\\"'), '",', 
        '"date":"', pc.date, '",', 
        '"user_id":"', pcu.id, '",', 
        '"user_name":"', pcu.name, '"}"') last_comment_json 
        FROM post_comments pc 
        LEFT JOIN users pcu ON (pcu.id = pc.user_id) 
        WHERE pc.post_id = p.id
        ORDER BY pc.id DESC LIMIT 1) AS last_comment
FROM posts p
    -- no issues with these
    LEFT JOIN users u ON (u.id = p.user_id)
    LEFT JOIN citys c ON (c.id = p.city_id)
    LEFT JOIN towns t ON (t.id = p.town_id)
    LEFT JOIN dists d ON (d.id = p.dist_id)
    LEFT JOIN post_metas pm ON (pm.post_id = p.id)
WHERE p.status = 'published'
GROUP BY p.id
ORDER BY 
    -- everything okay until here
    -- any other indexed fields makes query slow, not just "case" part
    (CASE WHEN p.created_at >= unix_timestamp(now() - INTERVAL p.reputation DAY) 
        THEN +p.reputation ELSE NULL END) DESC, 
    -- only id field (primary) is effective, no other indexes 
    p.id DESC
LIMIT 0,10;

解释;

# id, select_type, table, partitions, type, possible_keys, key, key_len, ref, rows, filtered, Extra
1, PRIMARY, p, , ref, PRIMARY,user_id,status,reputation,created_at,city_id-town_id-dist_id,title-content, status, 1, const, 15283, 100.00, Using index condition; Using temporary; Using filesort
# dunno, these join's are not using, but if i remove returning fields from select part show "Using index condition"
1, PRIMARY, u, , eq_ref, PRIMARY, PRIMARY, 2, p.user_id, 1, 100.00, 
1, PRIMARY, c, , eq_ref, PRIMARY, PRIMARY, 1, p.city_id, 1, 100.00, 
1, PRIMARY, t, , eq_ref, PRIMARY, PRIMARY, 2, p.town_id, 1, 100.00, 
1, PRIMARY, d, , eq_ref, PRIMARY, PRIMARY, 2, p.dist_id, 1, 100.00, 
1, PRIMARY, pp, , eq_ref, PRIMARY, PRIMARY, 2, p.id, 1, 100.00, 
2, DEPENDENT SUBQUERY, pc, , ref, post_id,visibility,status, post_id, 2, func, 2, 67.11, Using index condition; Using where; Using filesort
2, DEPENDENT SUBQUERY, pcu, , eq_ref, PRIMARY, PRIMARY, 2, pc.user_id, 1, 100.00, 

共有2个答案

那铭
2023-03-14

你的问题是:

  • “Order BY expression”:必须为表中的每一行计算表达式,然后对整个表进行排序,然后结果通过限制。
  • 没有索引use:当“col”是索引的一部分时,“order BY col”可以通过按顺序遍历索引来消除排序。这在使用LIMIT时非常高效。但是,它在这里行不通。

有办法摆脱这种混乱,但你需要告诉你有多少不同级别的“声誉”(比如3个,或者说“很多”),以及它们在统计上是如何分布的(比如,1个用户的声誉为100,其余的用户都为零,或者平均分布)。

编辑

嗯,没有关于“声誉”的统计分布或其可能的数值范围的信息。在这种情况下,让我们使用直截了当的方法:

让我们添加一个列“repdate”,它包含:

repdate = p.created_at + INTERVAL p.reputation DAY

这就相当于在未来的某一天为他们拥有的每一个声誉点转移帖子。然后它们将进行相应的排序。如果p.created_at不是datetime,请根据需要进行调整。

现在,我们可以简单地“Order BY repdate desc”,并且有了索引,它将会很快。

闻人高卓
2023-03-14

这是一个非常有趣的查询。在优化过程中,您可能会发现并理解许多关于MySQL工作方式的新信息。我不确定我会有时间一下子把所有的细节都写出来,但我可以逐渐更新。

基本上有两种情况:一种是快速的,一种是缓慢的。

在一个快速场景中,您将按照预定义的顺序在一个表上遍历,并可能同时从其他表中按每行的id快速获取一些数据。在这种情况下,只要LIMIT子句指定了足够多的行,就会立即停止遍历。订单从何而来?根据表上的B树索引或子查询中结果集的顺序。

在一个缓慢的场景中,您没有预定义的顺序,MySQL必须隐式地将所有数据放入一个临时表中,在某个字段上对表进行排序,并从LIMIT子句返回n行。如果您放入临时表中的任何字段是TEXT类型的(而不是VARCHAR),则MySQL甚至不会尝试将该表保存在RAM中,而是在磁盘上对其进行刷新和排序(因此需要额外的IO处理)。

在许多情况下,您无法构建一个允许您遵循其顺序的索引(例如,当您按不同表中的列排序时),因此在这种情况下,经验法则是最小化MySQL将放入临时表中的数据。你怎么能做到?您只选择子查询中行的标识符,获得ID后,将ID连接到表本身和其他表以获取内容。也就是说,您用订单制作一个小桌子,然后使用quick场景。(这与一般的SQL略有矛盾,但SQL的每种风格都有自己的方法以这种方式优化查询)。

巧合的是,您的选择--everything is ok here看起来很有趣,因为这是它不ok的第一个地方。

SELECT p.*
    , u.name user_name, u.status user_status
    , c.name city_name, t.name town_name, d.name dist_name
    , pm.meta_name, pm.meta_email, pm.meta_phone
    , (SELECT concat("{", 
        '"id":"', pc.id, '",', 
        '"content":"', replace(pc.content, '"', '\\"'), '",', 
        '"date":"', pc.date, '",', 
        '"user_id":"', pcu.id, '",', 
        '"user_name":"', pcu.name, '"}"') last_comment_json 
        FROM post_comments pc 
        LEFT JOIN users pcu ON (pcu.id = pc.user_id) 
        WHERE pc.post_id = p.id
        ORDER BY pc.id DESC LIMIT 1) AS last_comment
FROM (
    SELECT id
    FROM posts p
    WHERE p.status = 'published'
    ORDER BY 
        (CASE WHEN p.created_at >= unix_timestamp(now() - INTERVAL p.reputation DAY) 
            THEN +p.reputation ELSE NULL END) DESC, 
        p.id DESC
    LIMIT 0,10
) ids
JOIN posts p ON ids.id = p.id  -- mind the join for the p data
LEFT JOIN users u ON (u.id = p.user_id)
LEFT JOIN citys c ON (c.id = p.city_id)
LEFT JOIN towns t ON (t.id = p.town_id)
LEFT JOIN dists d ON (d.id = p.dist_id)
LEFT JOIN post_metas pm ON (pm.post_id = p.id)
;

这是第一步,但即使现在您也可以看到,您不需要对不需要的行进行这些无用的左联接和json序列化。(我跳过了group BY p.id,因为我看不出哪个左联接可能会产生几行,所以您没有进行任何聚合)。

尚未写到:

  • 索引
  • 重新制定CASE子句(使用UNION ALL)
  • 可能强制索引
 类似资料:
  • 主要内容:src/runoob/heap/HeapSort.java 文件代码:上一节的堆排序,我们开辟了额外的空间进行构造堆和对堆进行排序。这一小节,我们进行优化,使用原地堆排序。 对于一个最大堆,首先将开始位置数据和数组末尾数值进行交换,那么数组末尾就是最大元素,然后再对W元素进行 shift down 操作,重新生成最大堆,然后将新生成的最大数和整个数组倒数第二位置进行交换,此时到处第二位置就是倒数第二大数据,这个过程以此类推。 整个过程可以用如下图表示: Java 实

  • 问题内容: 在Java6中,quicksort和mergesort分别在中用于原始数组和对象数组。在Java7中,它们都已更改为DualPivotQuicksort和Timsort。 在新的快速排序的来源中,以下注释出现在几个地方(例如354行): 这是一个性能问题吗?编译器不会将这些简化为同一件事吗? 更广泛地说,调查自己的最佳策略是什么?我可以运行基准测试,但对分析已编译代码中的任何差异会更感

  • 问题内容: 我想知道还有什么可以优化冒泡排序的方法,以便即使在第一次通过之后也可以忽略已经排序的元素。 我们观察到[4,5,6]已经按顺序排列,如何修改我的代码,以便在下一遍中忽略这3个元素?(这意味着排序会更有效?)您是否建议使用递归方法? 谢谢你的时间! 问题答案: 首先,您具有越界访问权限: 因为,所以循环条件应该是。 但是,在Bubble排序中,您知道经过传递后,最大的元素将在数组的最后一

  • 问题内容: [http://jsperf.com/optimized-mergesort-versus- quicksort][1] 为什么这个半缓冲区合并排序的工作速度与quicksort一样快? QuickSort是: 就地虽然会占用递归(堆栈空间) 缓存友好 这一半缓冲区合并排序: 使用Buffer进行合并。 使用递归。 进行较少的比较。 我的问题是,在这种情况下,为什么半缓冲区合并排序与Q

  • 问题内容: 在这里可以找到很多类似的问题,但是我认为没有一个人能够充分回答这个问题。 如果可以的话,我将继续从当前最受欢迎的问题开始,并使用其示例。 本例中的任务是获取数据库中每个作者的最新帖子。 该示例查询产生不可用的结果,因为它并不总是返回最新的帖子。 当前接受的答案是 不幸的是,这个答案是简单而简单的错误,并且在许多情况下产生的结果比原始查询更不稳定。 我最好的解决方案是使用形式的子查询 我

  • 问题内容: 感觉应该有一个基本的解决方案,但我似乎没有。 进行以下查询: 我想有效地做到这一点: 我并不是很想选择DatePublished,但是按它进行排序似乎很有意义。那是行不通的。 基本上,我想按最新的DatePublished文章对类别进行排序。 问题答案: 由于执行了,因此需要在非分组列上运行一些聚合函数。 将从每个类别中选择最后发表文章的日期,并相应地订购类别。