类型没问题再查统计。SHOW STATS_HEALTHY 是表级的,只反映"表改了百分之多少、该不该重收集",不代表 idx_user_time 这个索引的直方图还在——索引统计要用 SHOW STATS_HISTOGRAMS WHERE Table_name='orders' AND Is_index=1 去看,重点看有没有 idx_user_time 这一行、Distinct_count 和 Update_time 是什么,顺便把 user_id 那列的 Distinct_count 跟你 SELECT COUNT(DISTINCT user_id) FROM orders 对一下。索引行压根不在,或者 NDV 被压得很低,问题就在这儿。
判断档位也很快:全表扫那步 est rows 是 2.3 亿,属于"条件没生效",往上查类型和列统计;如果是两千万这种量级,那就是 NDV 被低估,加 hint 那份 EXPLAIN 的 est rows 一定明显更小,两份摆一起就看得出来。
想让我们直接定位,用 PLAN REPLAYER DUMP EXPLAIN SELECT ... 把那条 SQL 的计划、统计、变量打成一个 zip 发我,我拉优化的同学一起看,比自己猜快得多。
SQL Plan Management 是能用的,CREATE GLOBAL BINDING 绑的是 SQL 指纹加当时那条计划用到的 hint 组合,统计信息后面再变不会让绑定自己失效,比你写死在业务 SQL 里干净。但它有两个前提要知道,SQL 文本一改就可能算另一个指纹(换绑定变量写法、加减一个注释甚至空格),匹配不上就走回默认计划;索引被删或者改名也会失效。所以我更建议先开 tidb_capture_plan_baselines 让它自己抓几天,确认这条语句一直拿到好计划,再决定要不要手动绑。反正 hint 能快 70 倍已经说明索引路径本身是划算的,多半是统计这块坏了,统计修对大概率就不用绑。