TableRangeScan扫描方式实现和index join

为提高效率,请提供以下信息,问题描述清晰能够更快得到解决:

【TiDB 版本】4.0.12

【问题描述】
1、 建3个表 a b c
create table a (id bigint primary key,name varchar(10));
create table b (id1 bigint ,id2 bigint,txt varchar(10),primary key(id1,id2));
create table c(id1 bigint ,id2 bigint,txt varchar(10));

image
2、 a b 关联, 根据b表结果驱动扫描a,TableRangeScan_9
explain select a.* from a,b where b.id1=1 and b.id2=a.id;


问题: TableRangScan的具体执行过程或者说是如何实现的?有什么前提条件? 官网只是解释为:一类按范围进行的扫表操作。没有其他具体说明

3、 a c关联(c无索引),a驱动c,a c全表扫描,hash join



问题: c有过滤条件c.id1=1,全表扫描后只有1条数据,不应该是c作为外表驱动a表更合适吗?

4、 a c关联(c.id1添加索引),a表TableRangeScan, c 仍然全表扫描


问题: c表id1在建立索引后,有列信息、直方图信息可以看到id1=1是bucket里只有1个值,此时优化器仍然选择了c全表扫描,只不过是c作为了驱动表,为什么c不能直接使用索引?

5、 ac关联,c强制索引后使用索引扫描

问题: c表使用强制索引后可以使用索引,如何看到c表索引扫描、全表扫扫描的估算?

问题6、 TiDB 源码阅读系列文章(十一)Index Lookup Join里对index join的解释里貌似并没有体现index,只是说驱动表分成多批,每批次计算一个范围然后根据这个范围去扫描内表,也没有说内表的扫描方式,索引index join怎么体现的index?

谢谢!

  • TiKV 是一个分布式的提供事务的 Key-Value 存储引擎,explain select a.* from a,b where b.id1=1 and b.id2=a.id; TableRangScan 算子是根据 b.id2 的值来对 a 的数据进行范围扫描。

  • 问题: c有过滤条件c.id1=1,全表扫描后只有1条数据,不应该是c作为外表驱动a表更合适吗?
    根据 explain 的 estRows a 表的预估值只有 5 行数据, selection 之后有 10 行数据。选择 a (数据更少) 作为 build 端 的Hash Table。另外执行计划内有 stats:pseudo 表示该表没有实际统计信息,执行 ANALYZE TABLE trips 收集统计信息后,预计的估算的数字会更加准确。

  • a c关联(c.id1添加索引),a表TableRangeScan, c 仍然全表扫描
    是因为本来数据量很少,如果使用索引 c 表的索引的话还需要回表取出 id2 的值进行关联。成本更高。

  • c表使用强制索引后可以使用索引,如何看到c表索引扫描、全表扫扫描的估算?
    应该是指的像 MySQL 的 trace 的功能吧?目前 TiDB 没有呢。可以在 TiDB GitHub 的 repo 提 feature request .https://github.com/pingcap/tidb

  • index join 的算法可以参考 :https://docs.pingcap.com/zh/tidb/v4.0/explain-joins#index-join 辅助理解。内标的扫描的话就是根据 index 的 range 进行扫描。

感谢,对这些问题还是有些疑惑,
第二次测试: a b c 3张表清理数据后各插入10001条数据,然后analyze。

  1. TableRangScan 问题:
    对于表数据访问要么通过索引回表取一定范围数据,要么全表扫描后取过滤后数据(不考虑聚簇索引),tablerangescan 是如何做的范围扫描,下面的测试中a表有单列bigint主键索引,所以rowid也是该列值。b a表关联时a使用range scan。b c表关联由于c没走索引所以全表扫描,c.id2添加索引后c走的索引扫描,tablerangescan是否是只在列能作为rowid的情况下才能使用,然后根据rowid过滤region范围,只读取所在region数据? 对于这个具体过程不是很了解

  2. c驱动a问题,在 a c表有1万条数据情况下,执行计划正常,c全表扫描后驱动A,走index join。

按照a b c表结构创建a1 b1 c1, 删除c1上索引,只插入3条数据,执行计划正常,c全表扫描后驱动A,走index join



上述执行计划对c1.id1=10和c1.id1=1行数预估有误,表行数3,c1.id1唯一值3,直方图桶数3,每桶值数量1,无论id1的值是否落在直方图范围内,评估行数都是6。
image

  1. 官方描述“而 operator info 中的 stats:pseudo 表示可能因为没有统计信息,或者统计信息过旧,不会用统计信息来进行估算” 有2个问题要请教: 1. 没有统计信息时 如何进行行数估算? 2. 达到什么样的条件是过旧的统计信息(stats_healthy?)

  2. 看博客文章的描述 index join跟index没啥关系,如果Index join时内表一定是走索引的话,上面的测试中index join时 内表走的tablerangescan,又回到了table range scan具体执行过程的问题了。
    另外测试时无论3条数据表关联还是1万条数据表关联,最后root task的评估行数都是1,走的笛卡尔积,不知道选择笛卡尔积的原因是什么? 当然只有1条数据的笛卡尔积也没性能问题。


6月2日,查询user表(此时的user表已重建为非分区)由于serial_number类型不对,导致没能走该列上的索引,产生了tablerangscan

  1. 建议先详细看看 tikv 这边数据是如何存取的。https://zhuanlan.zhihu.com/p/46372968
  2. 当 (修改数/总行数) 大于 pseudo-estimate-ratio(默认值 0.8) 时,TiDB 会使用假的统计信息
    https://docs.pingcap.com/zh/tidb/stable/tidb-configuration-file#pseudo-estimate-ratio
  3. 笛卡尔积的问题:应该是直接由于 c1.id1 =1 这个条件推导出 b1.id1 也是 1,然后直接把这两个条件推下去了,所以 Join 的时候不再需要考虑这个等值条件了,没有等值条件的 join 就会显示成笛卡尔积。
  4. 隐式转换会导致使用不了索引符合预期,因为涉及到对列的转换然后再做比较。隐式转换的问题 MySQL 也会存在。所以这边建议要使用一致的类型查询。
  1. 假的统计信息是个什么样的?是否有个固定的计算公式或数值?
  2. 这个问题不是请教隐式转换导致不能使用索引问题,之前没有写上问题, tablerangescan的range范围应该是指的rowid吧,执行计划TableRangeScan_11中的范围rang:[0,+inf], 在不能走索引后这个范围是如何计算得来的? 表中的最小rowid也不是0.(USER_ID bigint(16) unsigned ,PRIMARY KEY (USER_ID))
    image
  1. pseudo stats 的内部逻辑是假设数据是 10000 均匀分布的数据 。
  2. 因为 user_id 是 primary key 且没有条件。所以走的 tablescan 。因为有 unsigned 属性。所以只需要查询 [0,+inf] 的范围即可。

学习了, 生成执行计划时无法获得从驱动表获取记录的数量或值,所以user表的range是[0,+inf]. 就是一个全表扫描了,有统计信息评估从驱动表获取的行数和从被驱动表预估读取的行数,按理比这种[0,+inf]的rangescan成本要低很多