v7.5.2 索引热点,split index region后还无法解决,如何优化

【 TiDB 使用环境】生产环境
【 TiDB 版本】v7.5.2
【复现路径】定时任务批量查询
【遇到的问题:问题现象及影响】
业务夜间定时批量处理数据,集群变慢
表结构

CREATE TABLE `leader_relate_link_extention_log` (
  `id` bigint(20) NOT NULL /*T![auto_rand] AUTO_RANDOM(5, 54) */ COMMENT '物理ID',
  `tenant_id` int(11) NOT NULL COMMENT ' 团长id',
  `link_id` bigint(20) NOT NULL COMMENT '链接id',
  `status` int(2) DEFAULT '2' COMMENT '申请状态,1申请成功,2申请失败 3审核通过 4审核拒绝',
  `link_end_time` datetime NOT NULL COMMENT '链接终止时间(申请前)',
  `extention_time` varchar(50) NOT NULL COMMENT '延期时长',
  `fail_reason` varchar(2000) DEFAULT NULL COMMENT '失败原因',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '系统字段,创建时间,不再变化',
  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '系统字段,修改时间,每次变更自动更新',
  PRIMARY KEY (`id`,`tenant_id`) /*T![clustered_index] CLUSTERED */,
  KEY `idx_link_id` (`link_id`,`link_end_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin /*T![auto_rand_base] AUTO_RANDOM_BASE=10560714 */ COMMENT='商品延期申请表'
PARTITION BY KEY (`tenant_id`) PARTITIONS 10

SELECT a.id, a.relate_product_id, a.institution_id, a.relate_activity_id, a.link_end_time, a.tenant_id FROM leader_relate_link a WHERE a.tenant_id = 187 AND a.platform = 1 AND a.relate_activity_id = '889562' AND a.link_end_time >= '2024-07-19 00:00:00' AND a.link_end_time <= '2024-08-03 23:59:59.999' AND a.relate_activity_product_status IN (1, 6) AND NOT EXISTS ( SELECT 1 FROM leader_relate_link_extention_log l WHERE l.link_id = a.id AND l.tenant_id = a.tenant_id AND l.link_end_time = a.link_end_time AND l.status = 1 ) LIMIT 6000, 2000;

执行计划


监控图
image

怀疑是索引热点,对索引表7点进行split,效果不佳

加一个(tenant_id,link_id,link_end_time)` 索引试试呢 ,表是按tenant_id分区的这样索引也能打散了

(link_id, tenant_id ,link_end_time ) 会不会更合适一些

另外想讨论一下:分区表是用hash/key 分区好 还是 range 分区好 ,可以用range的场景
我感觉hash/key 就是运维简单 , 性能不一定好 , 重新改分区个数还要全表所有数据重整 。
range 分区 ,我想动哪个分区 就只影响哪个分区 。 能快速实现分区裁剪 ,扫描尽量少的数据 ,尽量少的coprocessor

tidb的分区表索引实际都是local 的,得让首列和分区键一致,link_id开头的话他还是个顺序的。 那种类型的分区得看你需求吧,hash/key分区打散效果好些,不能分区裁剪,性能都一样。

我感觉真正有用的就是range分区,hash和key对tidb来说没有很好的应用场景。

因为没有全局索引,只要跨分区,性能都不会很好。
分区最大的作用就是2个:1是删除数据方便,2冷热分离 用于调度。
这2点上,都是range分区好用。

1 个赞

确定是这样吗? 我怎么感觉 分区键应该放最后面呢? 比如我的range分区 ,这个分区就1个分区键值 3 。 你索引还用3 开头 那还有什么意义? 。
另外不带分区键的索引都是全局二级索引 ,不是local的 。

现在是 hint 是 /+ SEMI_JOIN_REWRITE() /的情况下 有他在 没法binding 改成强制 /+ read_from_storage(tiflash[link])/

tidb的分区表,索引都是local的,没有全局的,因为tidb的分区表,从物理来看,实际是多个小表组合成的大表,实际索引都是建在每个小表上的,所以你建索引不如索性带上分区键,例如的range分区 ,这个分区就1个分区键值 3 ,这时假如你sql只查3分区的数据,直接用3分区对应小表上的索引就行了。。。

不是的 ,官方CTO跟我说过 带分区字段的索引是 local的 。
不带的是全局二级索引 ,一般情况性能也没问题。

帮你格式化了下 SQL

SELECT a.id, a.relate_product_id, a.institution_id, a.relate_activity_id, a.link_end_time, a.tenant_id 
FROM leader_relate_link a 
WHERE a.tenant_id = 187 
AND a.platform = 1 
AND a.relate_activity_id = '889562' 
AND a.link_end_time >= '2024-07-19 00:00:00' 
AND a.link_end_time <= '2024-08-03 23:59:59.999' 
AND a.relate_activity_product_status IN (1, 6) 
AND NOT EXISTS 
( SELECT 1 FROM leader_relate_link_extention_log 
	l WHERE l.link_id = a.id AND l.tenant_id = a.tenant_id 
	AND l.link_end_time = a.link_end_time AND l.status = 1 ) LIMIT 6000, 2000;

几个建议:
1、这个 SQL 量不大,leader_relate_link 无需走 TiFlash,Tiflash 支持不了高并发,建议relate_activity_id、link_end_time 列建立复合索引
2、leader_relate_link_extention_log 看起来像 leader_relate_link 的子表,分区建议与主表一致,然后 tenant_id 都要明确写出来,当前 leader_relate_link_extention_log 走的时候都是 partition all
3、俩表走 NL ,在表 leader_relate_link_extention_log 列 link_id、link_end_time 上建立索引

但是官方文档是没有写已经支持了分区全局索引的。。。

1、tiflash 快很多 也支持高并发 (range分区拆分得好的话)。
2、子表现在是key 分区 ,我也建议用range 分区 ,因为实际上只用到 tenant_id = 187 相关的分区数据据。或者把partitions 数改成32 。
3、走NL 容易出现索引热点 。 子查询强制走tiflash 就很快 。
4、 现在是程序里有 hint 是 /+ SEMI_JOIN_REWRITE() /的情况下, 有他在 没法binding 改成强制 /+ read_from_storage(tiflash[link]) /

你可以试一下 不带分区键的索引 查询速度挺快的,并没有预想的 每个分区都去查一遍造成查询慢的问题 。
只是如果官方确定不带分区键的索引是全局二级索引的话 ,是不是执行计划里可以把 partition:all 改成 global 全局索引 ,避免大家看到 partition:all 就感觉像是本地分区索引都去扫了一遍 一样。我一开始也是这么以为的 :joy:

分区表的key格式是:part_id+rowid 分区表索引的key格式: part_id+idx_id+idx_column+rowid 。所以还是local的。 并不像类似Oracle那样没指定local时 索引是一个整体的全局索引。 如果按(tenant_id ,link_id ,link_end_time )建索引当有多个tenant_id落到同一个分区时索引首先按tenent_id排序,link_id可以被打散。如果是 (link_id , tenant_id ,link_end_time )那一个分区内的索引还是按Link_id排序,就还会可能产生热点。

tidb解决索引热点 还有个shard index, 限制条件比较多
https://docs.pingcap.com/zh/tidb/stable/tidb-functions#tidb_shard

我目前看这个 SQL,我个人觉得应该走 TiKV,不盲目崇拜 :star_struck: Tiflash。

现在有热点是因为扫了太多无用数据了。

建议你先按建议测试下发下测试结果再说 :thinking: 一起分析下

SELECT a.id, a.relate_product_id, a.institution_id, a.relate_activity_id, a.link_end_time, a.tenant_id 
FROM leader_relate_link a 
WHERE a.tenant_id = 187 
AND a.platform = 1 
AND a.relate_activity_id = '889562' 
AND a.link_end_time >= '2024-07-19 00:00:00' 
AND a.link_end_time <= '2024-08-03 23:59:59.999' 
AND a.relate_activity_product_status IN (1, 6) 
AND NOT EXISTS 
( SELECT 1 FROM leader_relate_link_extention_log 
	l WHERE l.link_id = a.id AND l.tenant_id = 187 
	AND l.link_end_time = a.link_end_time AND l.status = 1 ) LIMIT 6000, 2000;

为什么不把tenant_id 下给到log表呢?
@foxchan 能给下这个的执行计划看下嘛

执行计划应该会给到的 , 只是因为是key分区 所以执行计划看不出来 。

他给出的执行计划不是全分区的检索嘛,理论上吧tenant_id是个常值应该是下到子查询内进行分区裁剪的吧。
下面是官方对hash分区裁剪的说明,然后再应该是建立(tenant_id,link_id,endtime)的联合索引,避免一个分区内数据过大。

1 个赞

我之前也考虑过,你提的这个我其实一开始也想这么说。
不过后来考虑到这个子查询是个需要not exists的结果。可能是有什么业务逻辑的限制导致,不能把这个tenant_id放进子查询。

除了这个疑似业务逻辑限制以外,我完全同意你的说法。我感觉慢的根因,其实就是子查询没有带分区键,导致扫描了所有分区,然后就慢了。

如果是要把业务逻辑考虑进去,那么应该是走2个表内连接过滤出主表id,再用主表的id做not exist。这样改动,或许就大了把

1 个赞