tidb表健康度100但实际统计信息却不准

【 TiDB 使用环境】测试/ Poc
【 TiDB 版本】7.5.3
【复现路径】做过哪些操作出现的问题
每天凌晨对 表健康度不是100的表执行 analyze table
【遇到的问题:问题现象及影响】
压测过程发现sql执行慢,查看表的健康度为 100

SHOW STATS_HEALTHY where table_name like  'account';
+----------------+------------+----------------+---------+
| Db_name        | Table_name | Partition_name | Healthy |
+----------------+------------+----------------+---------+
| user_manager   | account    |                |     100 |
+----------------+------------+----------------+---------+

查看表的统计信息 显示表行数为 401:

SHOW STATS_META where table_name like  'account';
+----------------+------------+----------------+---------------------+--------------+-----------+
| Db_name        | Table_name | Partition_name | Update_time         | Modify_count | Row_count |
+----------------+------------+----------------+---------------------+--------------+-----------+
| user_manager   | account    |                | 2025-03-04 01:02:47 |            0 |       401 |
+----------------+------------+----------------+---------------------+--------------+-----------+

使用count查看实际行数为 11369 于统计信息上的相差很大:

select count(*) from account ;
+----------+
| count(*) |
+----------+
|    11369 |
+----------+

tidb相关配置:

+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| tidb_auto_analyze_ratio | 0.5   |
+-------------------------+-------+
| tidb_auto_analyze_start_time | 00:00 +0000 |
+------------------------------+-------------+
| tidb_auto_analyze_end_time | 23:59 +0000 |
+----------------------------+-------------+
| tidb_persist_analyze_options | ON    |
+------------------------------+-------+

查看其他表也是一样,即使健康度100但是统计信息中的行数和实际行数相差很大:

MySQL [user_manager]> select count(*) from tree_node;
+----------+
| count(*) |
+----------+
|    11892 |
+----------+
1 row in set (0.002 sec)

MySQL [user_manager]> SHOW STATS_META WHERE table_name = 'tree_node';
+--------------+------------+----------------+---------------------+--------------+-----------+
| Db_name      | Table_name | Partition_name | Update_time         | Modify_count | Row_count |
+--------------+------------+----------------+---------------------+--------------+-----------+
| user_manager | tree_node  |                | 2025-03-04 01:02:43 |            0 |       416 |
+--------------+------------+----------------+---------------------+--------------+-----------+
1 row in set (0.005 sec)

MySQL [user_manager]> show  stats_healthy where  table_name like 'tree_node';
+--------------+------------+----------------+---------+
| Db_name      | Table_name | Partition_name | Healthy |
+--------------+------------+----------------+---------+
| user_manager | tree_node  |                |     100 |
+--------------+------------+----------------+---------+
1 row in set (0.008 sec)

MySQL [user_manager]> analyze table tree_node;
Query OK, 0 rows affected, 1 warning (4.338 sec)

MySQL [user_manager]> show  stats_healthy where  table_name like 'tree_node';
+--------------+------------+----------------+---------+
| Db_name      | Table_name | Partition_name | Healthy |
+--------------+------------+----------------+---------+
| user_manager | tree_node  |                |     100 |
+--------------+------------+----------------+---------+
1 row in set (0.007 sec)

MySQL [user_manager]> SHOW STATS_META WHERE table_name = 'tree_node';
+--------------+------------+----------------+---------------------+--------------+-----------+
| Db_name      | Table_name | Partition_name | Update_time         | Modify_count | Row_count |
+--------------+------------+----------------+---------------------+--------------+-----------+
| user_manager | tree_node  |                | 2025-03-05 14:06:34 |            0 |     11892 |
+--------------+------------+----------------+---------------------+--------------+-----------+
1 row in set (0.002 sec)

MySQL [user_manager]> select count(*) from tree_node;
+----------+
| count(*) |
+----------+
|    11892 |
+----------+
1 row in set (0.019 sec)

多次测试 需要对这个表 手动执行一次 analyze 才能变快

问题

  1. 表的健康度为100的情况下 为什么还需要再执行一次 analyze 压测的sql才能变快?
  2. 表的健康度为100 为什么统计信息中的行数 会和实际行数相差这么大?
  3. 统计信息中的行数和实际行数相差很大是否会影响sql的执行速度?
  4. 应该使用什么方式去判断是否要对某个表执行analyze table?
  5. 目前我有个定时任务去每天凌晨执行一次analyze (逻辑是先检查表的健康度是不是100,对于不是100的才执行analyze) , 但是当前来看,表的统计信息实际是不准的,但是表的健康度依然是100,就无法利用这个定时任务执行analyze,争对这个问题有没有比较好的解决方案? 若直接改成不管表的健康度是不是100都执行analyze, 这样对所有库所有表都执行analyze是否会对业务造成很大的影响?

【资源配置】进入到 TiDB Dashboard -集群信息 (Cluster Info) -主机(Hosts) 截图此页面
【复制黏贴 ERROR 报错的日志】
【其他附件:截图/日志/监控】

是分区表吗?不是的话,ANALYZE TABLE tree_node WITH SAMPLERATE 1.0;这样收集以下再看看呢?

不是分区表, 直接执行ANALYZE TABLE tree_node 后 行数变成准确的了, 但是之前行数相差很大健康度却依然是100

根据 TiDB 的设计,健康度公式为:
Healthy = (1 - Modify_count / Row_count) * 100
Modify_count(自上次统计信息收集以来的数据变更次数)为 0 时,健康度自然为 100。但这仅表示统计信息未因数据变更而过期,不保证统计信息中的行数与真实行数完全一致。例如:

  • 数据导入场景:如果表通过 LOAD DATAINSERT INTO SELECT 直接导入数据,但未触发 Modify_count 的更新,即使真实行数大幅增加,健康度仍可能显示为 100
1 个赞

那这种情况(统计信息中行数不准)会影响sql的执行耗时吗?

压测场景会执行 INSERT INTO 语句插入数据,但没有导入的操作

会的,像 LOAD DATAINSERT INTO SELECT 直接导入数据一般都建议手工anaylze下表的

目前来说,大概率会导致慢。还要看表大小和索引使用情况

我使用下面的方式 往一个执行过analyze的表中插入数据时, 也是一样的结果,健康度保持100, 统计信息中的行数 保持执行analyze后的结果, 但实际以及由原来的 1000 行变成了 10000 行

for i in $(seq 1001 10000); do
>     SQL="INSERT INTO tmp.test_table (name) VALUES ('Name_$i');"
>     mysql -h127.0.0.1 -P{port}  -uroot -p {password} -e "$SQL"
> done
MySQL [tmp]> SHOW STATS_META WHERE table_name = 'test_table';
+---------+------------+----------------+---------------------+--------------+-----------+
| Db_name | Table_name | Partition_name | Update_time         | Modify_count | Row_count |
+---------+------------+----------------+---------------------+--------------+-----------+
| tmp     | test_table |                | 2025-03-05 15:49:32 |            0 |      1000 |
+---------+------------+----------------+---------------------+--------------+-----------+
1 row in set (0.002 sec)

MySQL [tmp]> show stats_healthy where table_name like 'test_table';
+---------+------------+----------------+---------+
| Db_name | Table_name | Partition_name | Healthy |
+---------+------------+----------------+---------+
| tmp     | test_table |                |     100 |
+---------+------------+----------------+---------+
1 row in set (0.006 sec)

MySQL [tmp]> select count(*) from test_table;
+----------+
| count(*) |
+----------+

感觉insert into 也不会触发 Modify_count 的更新?

update 语句也不会触发

for i in $(seq 1 8000); do
>       SQL="UPDATE tmp.test_table SET name='Updated_Name_1_$i' WHERE id=$i;"
>       mysql -h127.0.0.1 -Pport -uroot -ppassword -e "$SQL"
>   done


这个表是通过 BR 或者 lightning 物理导入后,一直没有修改过么? 因为 Row_count 在内存里标记没超过 1000,所以并不会主动 analyze

初始表的健康度都是 100%,只有表数据存在更新时候,表的健康度才会下降。

这个表是通过业务界面触发后新增的一条条数据, 当前实际数据量10000多条 但是统计信息上只有400多条。“Row_count 在内存里标记没超过 1000,所以并不会主动 analyze” 那感觉陷入了死循环,不执行analyze就不会更新Row_count, 而Row_count不更新就一直为401 就无法自动触发analyze

我这边用下面的方式对测试的表进行了很多次测试 包括 insert update 但是表的健康度一直是 100 而且统计信息中的行数 也没有更新过

for i in $(seq 1 8000); do        SQL="UPDATE tmp.test_table SET name='Updated_Name_4_$i' WHERE id=$i;";        mysql -h127.0.0.1 -P -uroot -p -e "$SQL";    done

tidb_auto_analyze_ratio 默认是 0.5 我调整成0.1后依然没用, 测试版 test_table 一开始数据量为 1000 执行insert后实际数据量为10000 我每次测试更新8000条 当前统计信息中依然是 1000

如果是手工analyze的话,大概率会把自动的关闭掉。确认一下有没有关闭。

这个没用关闭,使用的是默认配置

表结构能发一下么?另外执行analyze的时候,看一下日志里有没有类似日志。

表结构:

MySQL [tmp]> show create table test_table\G
*************************** 1. row ***************************
       Table: test_table
Create Table: CREATE TABLE `test_table` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL,
  `new_column` varchar(255) DEFAULT '',
  PRIMARY KEY (`id`) /*T![clustered_index] CLUSTERED */,
  KEY `idx_id` (`id`),
  KEY `idx_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin AUTO_INCREMENT=30001
1 row in set (0.002 sec)

MySQL [tmp]> show create table user_manager.tree_node\G
*************************** 1. row ***************************
       Table: tree_node
Create Table: CREATE TABLE `tree_node` (
  `id` varchar(32) NOT NULL COMMENT '主键',
  `party_id` varchar(32) NOT NULL COMMENT '所属团体id,party表id',
  `parent_id` varchar(32) DEFAULT NULL COMMENT '父节点id',
  `tree_node_index` bigint(20) NOT NULL COMMENT '该分组下的位序',
  `data_id` varchar(32) NOT NULL COMMENT '节点数据id',
  `data_type` smallint(6) NOT NULL COMMENT '节点数据类型 0:虚拟分组;1:用户分组; 2:用户;3:设备分组;4:设备; 5:VMR分组; 6:VMR;7:会议室分组;8:会议室; 9:第三方设备分组;10:第三方设备; 11:管理员;',
  `parent_data_id` varchar(32) DEFAULT NULL COMMENT '父节点数据',
  `name` varchar(256) NOT NULL COMMENT '名称',
  `number` varchar(128) DEFAULT NULL COMMENT '冗余账号',
  `name_pinyin` varchar(1024) NOT NULL COMMENT '名称全拼',
  `name_pinyin_for_search` varchar(512) NOT NULL COMMENT '名称全拼、简拼等,主要用于搜索',
  `type` smallint(6) NOT NULL COMMENT '节点类型 0:非叶子节点;1:叶子节点',
`create_time` bigint(20) NOT NULL COMMENT '创建时间',
  `modify_time` bigint(20) NOT NULL COMMENT '修改时间',
  `creator_id` varchar(32) DEFAULT NULL COMMENT '创建人',
  `modifier_id` varchar(32) DEFAULT NULL COMMENT '最后修改人',
  `is_deleted` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标志位',
  `sync_directory` tinyint(4) NOT NULL DEFAULT '1' COMMENT '是否同步到联系人',
  `profile` smallint(6) DEFAULT NULL COMMENT 'VMR会议模式, 0 会议模式、1 授课模式、2 研讨会模式',
  `organizer` varchar(32) DEFAULT NULL COMMENT 'VMR组织者',
`email` varchar(128) DEFAULT NULL COMMENT '邮箱',
  `mobile` varchar(64) DEFAULT NULL COMMENT '手机号',
  `gender` smallint(6) DEFAULT NULL COMMENT '性别',
  `path` varchar(512) NOT NULL COMMENT '路径',
  `short_id` varchar(32) DEFAULT NULL COMMENT 'uuid压缩为8位的数据',
  `phone_extension` varchar(64) DEFAULT NULL COMMENT '电话分机号',
  `title` varchar(128) DEFAULT NULL COMMENT 'ְ职位',
`location_type` smallint(6) NOT NULL DEFAULT '0' COMMENT '位置类型,0:虚拟位置,1:未分配地点,2建筑,3楼层',
  `enable_scheduled` tinyint(4) NOT NULL DEFAULT '1' COMMENT '是否会议室预约,1:启用,0:禁用',
  `scheduled_mode` tinyint(4) NOT NULL DEFAULT '-1' COMMENT '会议室预约模式,-1:默认,0:自由预约,1:仅临时占用',
  `room_type` smallint(6) NOT NULL DEFAULT '-1' COMMENT '会议室类型,-1:默认,0:普通会议室,1:视频会议室',
  `capacity` int(11) DEFAULT NULL COMMENT '会议室容量',
  `room_device_id` varchar(32) DEFAULT NULL COMMENT '会议室关联的deviceId',
  `robot_type` smallint(6) DEFAULT '-1' COMMENT '机器人类型,-1:默认;0:应用机器人;1:服务号机器人',
  `mobile_show_mode` smallint(6) DEFAULT NULL COMMENT '手机号显示模式,1:明文显示,2:密文支持查看,3:密文不支持查看',
`signature` varchar(100) DEFAULT NULL COMMENT '个性签名',
  `custom_field1` varchar(140) DEFAULT NULL COMMENT '预留的自定义字段',
  `custom_field2` varchar(140) DEFAULT NULL COMMENT '预留的自定义字段',
  `custom_field3` varchar(140) DEFAULT NULL COMMENT '预留的自定义字段',
  `custom_field4` varchar(140) DEFAULT NULL COMMENT '预留的自定义字段',
  `custom_field5` varchar(140) DEFAULT NULL COMMENT '预留的自定义字段',
  `custom_field6` varchar(140) DEFAULT NULL COMMENT '预留的自定义字段',
  `custom_field7` varchar(140) DEFAULT NULL COMMENT '预留的自定义字段',
  `custom_field8` varchar(140) DEFAULT NULL COMMENT '预留的自定义字段',
  `custom_field9` varchar(140) DEFAULT NULL COMMENT '预留的自定义字段',
`custom_field10` varchar(140) DEFAULT NULL COMMENT '预留的自定义字段',
  `robot_visible_range` tinyint(4) DEFAULT '0' COMMENT '机器人被可见范围,0:所有,1:部分可见',
  `alias` varchar(128) DEFAULT NULL COMMENT '别名',
  `alias_pinyin` varchar(1024) DEFAULT NULL COMMENT '别名全拼',
  `alias_pinyin_for_search` varchar(512) DEFAULT NULL COMMENT '别名全拼、简拼等,主要用于搜索',
  `personal_phonebook_subject_id` varchar(32) DEFAULT NULL COMMENT '个人通讯录的subject_id',
  `call_type` varchar(64) DEFAULT '' COMMENT '支持呼叫类型(可多选):0:支持电话呼叫 1:支持会议呼叫,默认为空',
  PRIMARY KEY (`id`) /*T![clustered_index] CLUSTERED */,
  KEY `idx_tree_node_dataid` (`data_id`),
  KEY `idx_tree_node_parentid` (`parent_id`),
  KEY `idx_tree_node_pdid` (`parent_data_id`),
  KEY `idx_tree_node_create_time` (`create_time`),
  KEY `idx_tree_node_path` (`path`),
  UNIQUE KEY `uk_paryt_id_short_id` (`party_id`,`short_id`),
  KEY `idx_tree_node_pid_ct` (`party_id`,`create_time`),
  KEY `idx_tree_node_pid_dt` (`party_id`,`data_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='树节点'

如果是通过业务 SQL 更新的数据,那就不是我说的太小的问题,在数据量小的时候更新数据也会造成健康度下降,但是不会触发 analyze,和你的表现还不一样。
感觉你是遇到某些 BUG 了,看下 TiDB 日志里,搜一下 analyze 看是否有对应的错误日志

统计信息不准确,肯定会影响执行计划的最优选择。这个健康度与统计信息准确与否,没有必然关系吧