V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
pkoukk
V2EX  ›  数据库

TIDB 在 LEFT JOIN 时,为什么不使用被关联表的索引,而是全表扫描?

  •  
  •   pkoukk · 1 天前 · 1237 次点击

    有个很简单的查询,唯一的问题是数据表较大,A 有千万级,B 有接近十亿

    SELECT FIELDS FROM A
    LEFT JOIN B ON A.a=B.b
    WHERE A.w="" AND A.time>?
    

    A 表在 time 上有索引,B 表在 b 上有索引
    在我开发环境的 MySQL 中,explain 看上去一切正常

    A range time
    B ref idx_b
    

    但是到了线上的 TIDB 中,就变成了

    IndexRangeScan_101(Build)	table:A ,index:idx_time(time)
    TableFullScan_114	table:B	keep order:false
    

    主要的问题是,B 变成了全表扫描,完全无法接受。
    如果像 MySQL 正常索引,A 表过滤后的数据量只有几千条。 改了 USE INDEX 以后,变成了索引全扫描?情况也没好到哪里去... Analyze Table 的命令也执行过了,还是没变化

    IndexRangeScan_101(Build)	table:A ,index:idx_time(time)
    IndexFullScan table:B, index:idx_b(b)	keep order:false
    
    11 条回复    2025-05-13 22:53:03 +08:00
    wuxianliang
        1
    wuxianliang  
       1 天前 via Android
    B 表取的字段全加索引上试试呢?
    winoros
        2
    winoros  
       1 天前 via Android
    最好把 explain 的结果完整的贴一下,可以把表名之类得遮掉。现在这些信息不太能判断问题
    c4pt0r
        3
    c4pt0r  
       1 天前
    @winoros 👈 这位是 TiDB team 的朋友, @pkoukk 麻烦提供一下更多信息,多谢
    Sidney
        4
    Sidney  
       1 天前
    可以先确认一下 A.a = B.b, 这两个列的类型是不是一致的,有没有类型转换
    pkoukk
        5
    pkoukk  
    OP
       1 天前
    @winoros
    替换了表名的 explain 结果,另外上面给的 SQL 有一点小偏差,不过应该也能从 explain 中看出来。
    实际是类似于
    SELECT FIELDS FROM A
    LEFT JOIN B ON A.a=B.b AND B.type="realease" AND B.ip !=""
    WHERE A.w="" AND A.time>?


    ```
    Projection_7 35303209.48 root B.ip, A.ip_id, A.is_deleted, A.insert_time, A.delete_time
    └─HashJoin_26 35303209.48 root left outer join, equal:[eq(A.ip_id, B.ip_id)]
    ├─IndexLookUp_47(Build) 2933525.58 root
    │ ├─IndexRangeScan_44(Build) 3106246.80 cop[tikv] table:A, index:idx_delete_time(delete_time) range:[1740758400,+inf], keep order:false
    │ └─Selection_46(Probe) 2933525.58 cop[tikv] eq(A.is_deleted, 1), lt(A.insert_time, 1743436800)
    │ └─TableRowIDScan_45 3106246.80 cop[tikv] table:A keep order:false
    └─TableReader_50(Probe) 35303209.48 root data:Selection_49
    └─Selection_49 35303209.48 cop[tikv] eq(B.type, "release"), ne(B.ip, ""), not(isnull(B.ip_id))
    └─TableFullScan_48 1163701770.00 cop[tikv] table:B keep order:false
    ```
    winoros
        6
    winoros  
       1 天前 via Android
    这个看起来是估算的问题 A 表的估算是三百万行,这个表是刚刚被 analyze 过吗,如果不是的话可以看一下这个表上次 analyze 的时间
    pkoukk
        7
    pkoukk  
    OP
       1 天前
    @winoros #6 analyze 过了,因为触发了慢查询告警才发现的,运维已经重新跑过 analyze 了
    spkinger
        8
    spkinger  
       1 天前
    盲猜下,换掉不等查询,B.ip !="" 换成 B.ip > "" 试试?
    Defined
        9
    Defined  
       1 天前
    B 表要走 ip_id 相关的 index 的话 join 需要时 index join ,可以加 hint 试下,但这个数量级用 index join 不一定比 hash join 快。

    hint 参考
    EXPLAIN ANALYZE SELECT /*+ INL_JOIN(t1, t2) */ * FROM t1 INNER JOIN t2 ON t1.id = t2.t1_id WHERE t1.int_col = 1;
    winoros
        11
    winoros  
       1 天前
    @winoros https://docs.pingcap.com/zh/tidb/stable/sql-plan-management/
    和 binding 来控制执行计划

    可以的话可以看一下 delete_time > 1740758400 的 [explain analyze]( https://docs.pingcap.com/zh/tidb/stable/sql-statement-explain-analyze/#explain-analyze)(估算行数和真实执行行数)是否接近
    以及单独只有 insert_time > 1743436800 的 explain analyze
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5813 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 20ms · UTC 02:02 · PVG 10:02 · LAX 19:02 · JFK 22:02
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.