【发布时间】:2017-07-30 15:12:34
【问题描述】:
我正在尝试优化这个查询,它按reputation 字段(第一个)和id 字段(第二个)对posts 进行排序。如果没有第一个字段查询需要 ~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)
- 主要是id 在posts 表
- 字段同时被 created_at 和 reputation 编入索引
解释结果:
# 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', '使用文件排序'更新^^
声誉规定:一个帖子,多少天(n=声誉)可以显示在列表的顶部。
实际上,我试图为一些可以在列表顶部获取的帖子提供声誉,并找到解决方案:Order posts by "rep" but only for "one" day limit。但经过一段时间(大约 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, 使用索引条件;使用临时的;使用文件排序 # 不知道,这些连接没有使用,但是如果我从选择部分中删除返回字段显示“使用索引条件” 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, 使用索引条件;使用哪里;使用文件排序 2, 相关子查询, pcu, , eq_ref, PRIMARY, PRIMARY, 2, pc.user_id, 1, 100.00,【问题讨论】:
-
没有索引可以用于 CASE 语句的排序。你能解释一下那个 CASE 语句的逻辑吗?
-
嗨@Paul,感谢您的回复。我已经更新了我的问题。
-
虽然在集合论方面不是一个完美的解决方案,但您可以每天运行一次设置声誉的程序/事件(如果您想保持原始声誉,则可以复制该字段)当其生命周期结束时为 0。您还可以找到可以使用索引建模的类似排序,但是对于您的特定公式(“生命周期”结束时的硬截止并切换到 id;并且代表 100 的帖子将始终在具有代表的帖子之前排序99,即使 rep100-post 已经显示了过去 90 天),我没有看到(至少乍一看)一个可索引的公式。
-
嘿,我想到了,使用一个类似cron的工具。但似乎问题与不同的东西有关。因为,即使 order by 子句中的每个字段都已经被索引,当我给第二个字段排序时,应用程序会变慢。想不通。
-
如果
id是您的主键,并且您的where子句中没有任何内容,则order by newreputationcolumn desc, id desc应该可以正常工作。否则,您将需要(newreputationcolumn, id)上的复合索引。如果这不起作用,请添加解释输出。重要的是您不要按派生值排序(如果您只是使用事件/cronjob 在其中写“0”而不是动态计算它,则不会这样做)。
标签: mysql sql-order-by query-optimization