tidb 如何减少表扫描?

目前发现,TiDB在如下两个场景中很容易导致表扫描(数据百万+),除了查询改写,还有哪些控制手段?

  1. 使用 or 条件:同表不同字段,改成 union 之后走索引,直接用则全表扫描
  2. IN(…),如果值很多,比如500+,则很容易走表扫描?

来看看,是什么情况、!

加hint?

in内对象过多的,可以考虑构建临时表,然后两表关联。

主要就是看有没有不改查询能降低表扫描的方法

从 mysql 迁移过来,mysql那边表现正常,过来之后发现KV负载很重,分析慢查询,发现很多在mysql那边直接索引的,在tidb这边走表扫描,总结下来主要就是 OR 与 IN 这两类
查询改起来比较慢,所以想看有没有什么能够快速降低负载的方法

执行计划绑定了,如果sql不太多的话

举个例子

SELECT * FROM bbc
WHERE bbc.biz_type='ER'
AND bbc.biz_no='930'
;

SELECT * FROM bbc
WHERE bbc.biz_type='ER'
AND bbc.bid BASIC_INFO_NO='930'
;

-- 为什么是表扫描,而不是 index merge
SELECT * FROM bbc
WHERE
bbc.biz_type='ER'
AND
bbc.biz_no='930'
OR bbc.bid BASIC_INFO_NO='930'
)

表:`bbc`,记数率 44.6万
CREATE TABLE `bbc` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `biz_type` varchar(32) COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `biz_no` varchar(36) COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `bid_basic_info_no` varchar(50) COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_bid_change` (`bid_change_no`),
  KEY `IX_biz_type_biz_no` (`biz_type`,`biz_no`),
  KEY `IX_bid_no` (`bid_basic_info_no`),
  KEY `IX_biz_no` (`biz_no`),
  KEY `IX_biz_type_bid_basic_info_no` (`biz_type`, `bid_basic_info_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci AUTO_INCREMENT=592050 COMMENT='招标变更'

将 OR 拆成两个查询,都能走索引,而且返回的记录数都很少(实际执行也是如此),但写成OR条件之后,就变成表扫描了,絮棉按成本评估,应该是 index-merge 更优才对

in特别多的时候,我们都是改写成插入临时表,然后表关联

加分区,加索引

or 创建复合索引或联合索引

in的话,可以优化索引覆盖性

这种目前只能通过绑定处理,/+use_index_merge(bbc)/。

这只能是防守,而且是在出现问题可观察到的情况下,很疯

有一部分SQL,我们让开发改写成union了。

测试环境还可以调下这个参数试试。

52869 从 v8.1.0 版本开始引入
默认值:OFF
可选值:ON、OFF
如果查询有除了全表扫描以外的单索引扫描方式可以选择,优化器不会自动选择索引合并。详情请参考用 EXPLAIN 查看索引合并的 SQL 执行计划中的注意部分。
打开此开关后,这个限制会被解除。解除此限制能让优化器在更多查询中自动选择索引合并,但也有可能忽略其他更好的执行计划,因此建议在解除此限制前针对实际场景进行充分测试,确保不会带来性能回退。

如果查询有除了全表扫描以外的单索引扫描方式可以选择,优化器不会自动选择索引合并

这个具体应该如何理解?

  • 理解1
    打开此开后,表索引、单列索引、索引合并都会做为可选的方案(假设都有对应的索引),通过成本评估选择最优的一个,最终的方案是3者之一
  • 理解2
    打开此开关后,如果有索引合并可用,直接选择索引合并。所以后面的描述中提到:可能忽略其它更好的执行计划

开启优化器自动转换开关

执行计划绑定(SQL Binding)

若优化器未自动转换,可将OR语句绑定为走索引的UNION计划,强制优化器选择索引扫描,无需修改业务 SQL:

优化表统计信息

若统计信息不准确,优化器可能误判 “OR 条件走索引的成本高于全表扫”,需更新统计信息:

但全表扫描的伤害感觉更大
一旦有单索引可以命中,并且效果不太好的话,导致的表扫描非常致命,而且这种情况的命中弯感觉更高

EXPLAIN FORMAT='verbose'
SELECT * FROM bbc1 bbc 
WHERE bbc.biz_type='BASIC_INFO_ALTER'
AND( bbc.biz_no='422' OR bbc.bid_basic_info_no='422')
;

走表扫描

EXPLAIN FORMAT='verbose'
SELECT * FROM bbc1 bbc IGNORE INDEX(IX_biz_type_bid_basic_info_no,IX_biz_type_biz_no)
WHERE bbc.biz_type='BASIC_INFO_ALTER'
AND( bbc.biz_no='422' OR bbc.bid_basic_info_no='422')

走 index merge

EXPLAIN FORMAT='verbose'
SELECT * FROM bbc1 bbc USE INDEX(IX_biz_type_bid_basic_info_no)
WHERE bbc.biz_type='BASIC_INFO_ALTER'
AND( bbc.biz_no='422' OR bbc.bid_basic_info_no='422')
;
EXPLAIN FORMAT='verbose'
SELECT * FROM bbc1 bbc USE INDEX(IX_biz_type_biz_no)
WHERE bbc.biz_type='BASIC_INFO_ALTER'
AND( bbc.biz_no='422' OR bbc.bid_basic_info_no='422')
;

走单索引扫描