排序和limit无法下推的SQL优化

为提高效率,请提供以下信息,问题描述清晰能够更快得到解决:
【 TiDB 使用环境】
v4.0.13

【概述】 场景 + 问题概述

SELECT doc_id,pub_code,MAX(pub_time) AS pub_time FROM fact_3860
WHERE pub_code = “bg_m_sinacn”
GROUP BY doc_id, pub_code
ORDER BY MAX(IF(like_cnt IS NULL, 0, like_cnt) + IF(collect_cnt IS NULL, 0, collect_cnt) + IF(forward_cnt IS NULL, 0, forward_cnt) + IF(reply_cnt IS NULL, 0, reply_cnt)) DESC
LIMIT 100;


这个sql就是一个 根据某字段排序取前100条记录,查看执行计划发现 limit 无法下推tiflash,导致会把结果全部推到tidb server 去做统一排序,导致tidb server oom

这个可以优化吗,4.0.13,无法升级5版本的

1 个赞

explain analyze 看下结果

1 个赞

这个不太能行,我们测试环境执行了这个sql,直接把测试环境tidb server 弄挂了,所以就看了下这个执行计划,发现order by 和limit 并没有下推下去,理论上来说 如果能下推tikv/tiflash,每个region各自 order by 再limit100条推送给tidb ,数据量不会太大的,tidb是不会崩的

1 个赞

你想先limit 就应该做条件筛选,如果做了group by ,必然会从多个tikv 聚合到 tidb 做分组了,肯定不可能下推的;
看看能不能优化一下SQL

这个逻辑就是 根据一个字段排序取前100, 难道不能在tiflash计算完后,每个region各自排序只取前100 推送给tidb,tidb在做最后的聚合? 这个sql 分组后还有百万数据呢,全推到tidb server不合适吧。。。

这个逻辑我觉得在很多场景都会用到吧,分组后排序取前100,如果下推不了,千万、亿级数据都要推送到tidb server 去排序取100?

如果无法下推,那这种分组后取top 100的 sql该怎么优化呢

表结构方便发一下吗?

大佬,你终于来了

CREATE TABLE fact_3860 (
id varchar(40) NOT NULL,
doc_id varchar(40) DEFAULT NULL,
account_id varchar(40) DEFAULT NULL,
pub_code varchar(40) DEFAULT NULL,
pub_type varchar(2) DEFAULT NULL,
pub_time datetime DEFAULT NULL,
subject1_id varchar(50) DEFAULT NULL,
subject2_id varchar(50) DEFAULT NULL,
subject3_id varchar(50) DEFAULT NULL,
subject4_id varchar(50) DEFAULT NULL,
subject5_id varchar(50) DEFAULT NULL,
subject6_id varchar(50) DEFAULT NULL,
subject_keyword text DEFAULT NULL,
aspect1_id varchar(50) DEFAULT NULL,
aspect2_id varchar(50) DEFAULT NULL,
aspect3_id varchar(50) DEFAULT NULL,
aspect4_id varchar(50) DEFAULT NULL,
aspect5_id varchar(50) DEFAULT NULL,
aspect6_id varchar(50) DEFAULT NULL,
aspect_keyword text DEFAULT NULL,
subject_sentiment_type_id int(11) DEFAULT NULL,
subject_sentiment_keyword text DEFAULT NULL,
sentiment_type_id int(11) DEFAULT NULL,
unit_content text DEFAULT NULL,
bi_status_id varchar(20) DEFAULT NULL,
bi_attr_id int(11) DEFAULT NULL,
process_start_time datetime DEFAULT NULL,
process_end_time datetime DEFAULT NULL,
reply_cnt int(11) DEFAULT NULL,
view_cnt int(11) DEFAULT NULL,
like_cnt int(11) DEFAULT NULL,
forward_cnt int(11) DEFAULT NULL,
dislike_cnt int(11) DEFAULT NULL,
collect_cnt int(11) DEFAULT NULL,
play_cnt int(11) DEFAULT NULL,
haha_cnt int(11) DEFAULT NULL,
love_cnt int(11) DEFAULT NULL,
wow_cnt int(11) DEFAULT NULL,
angry_cnt int(11) DEFAULT NULL,
sad_cnt int(11) DEFAULT NULL,
care_cnt int(11) DEFAULT NULL,
coin_cnt int(11) DEFAULT NULL,
danmaku_cnt int(11) DEFAULT NULL,
pv int(11) DEFAULT NULL,
keyword varchar(1000) DEFAULT NULL,
keyword_frequency varchar(500) DEFAULT NULL,
media_id int(11) DEFAULT NULL,
media_type_id int(11) DEFAULT NULL,
dedup_label varchar(5) DEFAULT NULL,
doc_type varchar(5) DEFAULT NULL,
author_tag varchar(5) DEFAULT NULL,
flag_qc tinyint(1) DEFAULT NULL,
flag_reject tinyint(1) DEFAULT NULL,
PRIMARY KEY (id),
KEY doc_id (doc_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin

表结构.txt (2.2 KB)

直接宕机

@songxuecheng @xfworld @yilong 更新统计信息,还是不行:

我对sql做了修改,我们先拿出top 1w,再group by,推送tidb 数据少些,但是报出了个bug

直接执行里面的sql没问题,取top1w很快

再对着top1w取group by order by limit,报错,应该是个bug:

这种方案要用到临时表吧…

临时表的方案,好像5.2 才能支持到… :sweat:


刚查了一下,貌似。4.X 就开始支持临时表了 ,不知道能不能怎么操作 @yilong

我觉得是两点啊,
第一点,group by 后取TopN 我觉得是个太常见的场景了,但是却无法下推tikv、tiflash, 5.0.2我看了下也无法下推,不知道官方对这个场景有没有什么建议

第二点,上面这个sql,就是您上面建议的,先过滤,我们改了sql先取 top 1w,对这个子查询,groupby,这也是很常见的场景吧,还需要用到临时表? 这个在mysql中就是个基本操作吧应该

mysql 也是内置了临时表来实现了

这种方案,目前的版本不支持,只能做业务层面的改造了,把数据的获取和计算进行分离

MPP 完整方案貌似可以支持,不知道5.X 是否能够满足你 (有时间的话,可以测一下)

比较靠谱的方案,只能拆分了…:rofl:

此话题已在最后回复的 1 分钟后被自动关闭。不再允许新回复。