3
1
1
0
专栏/.../

analyze 采样率是怎么算出来的(v6.5.3)

 Jayjlchen  发表于  2024-05-16

背景

在 TiDB 中,统计信息是查询优化器用来决定最佳查询执行计划的重要依据。准确的统计信息可以显著提高查询性能,因此定期的统计信息收集是维持数据库性能的重要任务之一。

统计信息收集过程中,analyze 操作会扫描表中的数据,并计算相关的统计数据。在此过程中,采样率(samplerate)是一个关键参数,它决定了需要扫描的数据量。理想情况下,采样率应当与表的大小成反比,以平衡统计信息的准确性和资源消耗。

最近有用户在 dashboard 发现了内存消耗高的 SQL,这是一个 analyze 收集统计信息的 SQL,涉及的表平均行长只有 600 bytes,并且采用低并发度参数的情况下,内存消耗接近 10G。我在 mysql.analyze_jobs 表中发现,当 processed_rows 高达 133 万时,采样率samplerate自适应地使用了 1。按预期,采样率应该是 11 万 / 133 万,即约 0.08,这导致实际使用的内存比预期高出许多。因此,我想了解一下 analyze 操作中的自适应采样率是如何计算出来的。

# 低并发参数下收集统计信息
set session tidb_build_stats_concurrency=1;
set session tidb_distsql_scan_concurrency=1;
set session tidb_index_serial_scan_concurrency=1;
analyze table xxx;

# mysql.analyze_jobs 查到的 job_info 信息
analyze table all columns with 256 buckets, 500 topn, 1 samplerate

源码分析过程

咨询相关产研大佬后,找到了对应的代码位置,在 getAdjustedSampleRate 模块,代码地址如下: https://github.com/pingcap/tidb/blob/v6.5.3/executor/builder.go#L2608-L2646

我们重点关注哪些情况下采样率 (sampleRate) 会被算成 1。

情形 1:stats_meta 没有值,且没有 PD 信息时,会返回默认 defaultRate (0.001)

if statsTbl == nil && !hasPD {
    return defaultRate
}

情形 2:stats_meta 的 count 值为 0 且没有 PD 信息时,返回 1 ,也就是 samplerate 是 1

if statsTbl.Count == 0 && !hasPD {
    return 1
}

情形 3:stats_meta 的 count 值乘以 5 ,如果仍小于存储获得的 approxiCount 值,那么:

  • 如果 150000/approxiCount 小于 1,那么返回值将是 150000/approxiCount,samplerate 将是一个零点几的小数。
  • 如果 150000/approxiCount 大于或等于 1,那么返回值将是 1,samplerate 是 1。
if float64(statsTbl.Count*5) < approxiCount {
    return math.Min(x: 1, 150000/approxiCount)
}

情形 4:stats_meta 的 count 值为 0 ,返回 1 ,也就是 samplerate 是 1

if statsTbl.Count == 0 {
    return 1
}

情形 5:和经验值 11W 行做对比,返回零点几或者 1

  • 如果 110000/statsTbl.Count 小于 1,那么返回值将是 110000/statsTbl.Count,samplerate 将是一个零点几的小数。
  • 如果 110000/statsTbl.Count 大于或等于 1,那么返回值将是 1,samplerate 是 1。

注:可以简单理解为表数据量小于 11W,采样率就是 1,大于 11W 就是零点几。

return math.Min(x: 1, config.DefRowsForSampleRate/float64(statsTbl.Count))

相关实验

注:为方便理解,这里准备了“情形 3”、“情形 4”和“情形 5”的 demo

情形 1-2 几乎没有或不易演示,略

情形 3

# lightning 设置 analyze = "off" 情况下导数 100w
# 此时查询 Approximate Count for table TAB1 为 200w
MySQL [test]> show stats_meta;
+---------+------------+----------------+---------------------+--------------+-----------+
| Db_name | Table_name | Partition_name | Update_time         | Modify_count | Row_count |
+---------+------------+----------------+---------------------+--------------+-----------+
| test    | TAB1       |                | 2024-05-16 21:48:44 |            0 |         0 |
+---------+------------+----------------+---------------------+--------------+-----------+
1 row in set (0.03 sec)

MySQL [test]> analyze table tab1;
Query OK, 0 rows affected, 1 warning (3.75 sec)

# 第一次收集走“情形3”,采样率: 15w/200w=0.075
MySQL [test]> show warnings;
+-------+------+--------------------------------------------------------------------+
| Level | Code | Message                                                            |
+-------+------+--------------------------------------------------------------------+
| Note  | 1105 | Analyze use auto adjusted sample rate 0.075000 for table test.tab1 |
+-------+------+--------------------------------------------------------------------+
1 row in set (0.00 sec)

MySQL [test]> show stats_meta;
+---------+------------+----------------+---------------------+--------------+-----------+
| Db_name | Table_name | Partition_name | Update_time         | Modify_count | Row_count |
+---------+------------+----------------+---------------------+--------------+-----------+
| test    | TAB1       |                | 2024-05-16 21:49:51 |            0 |   1000000 |
+---------+------------+----------------+---------------------+--------------+-----------+
1 row in set (0.00 sec)

MySQL [test]> analyze table tab1;
Query OK, 0 rows affected, 1 warning (4.42 sec)

# 第二次收集走“情形5”,采样率: 11w/100w=0.11
MySQL [test]> show warnings;
+-------+------+--------------------------------------------------------------------+
| Level | Code | Message                                                            |
+-------+------+--------------------------------------------------------------------+
| Note  | 1105 | Analyze use auto adjusted sample rate 0.110000 for table test.tab1 |
+-------+------+--------------------------------------------------------------------+
1 row in set (0.00 sec)

情形 4

MySQL [test]> create table tab2(id int);
Query OK, 0 rows affected (0.12 sec)

MySQL [test]> show stats_meta where table_name='tab2';
+---------+------------+----------------+---------------------+--------------+-----------+
| Db_name | Table_name | Partition_name | Update_time         | Modify_count | Row_count |
+---------+------------+----------------+---------------------+--------------+-----------+
| test    | tab2       |                | 2024-05-15 16:57:34 |            0 |         0 |
+---------+------------+----------------+---------------------+--------------+-----------+
1 row in set (0.03 sec)

MySQL [test]> analyze table tab2;
Query OK, 0 rows affected, 1 warning (0.04 sec)

MySQL [test]> show warnings;
+-------+------+--------------------------------------------------------------------+
| Level | Code | Message                                                            |
+-------+------+--------------------------------------------------------------------+
| Note  | 1105 | Analyze use auto adjusted sample rate 1.000000 for table test.tab2 |
+-------+------+--------------------------------------------------------------------+
1 row in set (0.00 sec)

情形 5

# tab2 插入数据到17W
MySQL [test]> show stats_meta where table_name='tab2';
+---------+------------+----------------+---------------------+--------------+-----------+
| Db_name | Table_name | Partition_name | Update_time         | Modify_count | Row_count |
+---------+------------+----------------+---------------------+--------------+-----------+
| test    | tab2       |                | 2024-05-15 17:13:20 |            0 |    170001 |
+---------+------------+----------------+---------------------+--------------+-----------+
1 row in set (0.00 sec)

MySQL [test]> analyze table tab2;
Query OK, 0 rows affected, 1 warning (0.37 sec)

# 采样率 = 110000/170001 = 0.647055
MySQL [test]> show warnings;
+-------+------+--------------------------------------------------------------------+
| Level | Code | Message                                                            |
+-------+------+--------------------------------------------------------------------+
| Note  | 1105 | Analyze use auto adjusted sample rate 0.647055 for table test.tab2 |
+-------+------+--------------------------------------------------------------------+
1 row in set (0.00 sec)

结论

我原本预计这个案例会走“情形 5”,但实际上它走了“情形 4”(通过排除法得出)。在 master 版本中,这部分代码已得到改进,并且会打印原因。目前的版本不容易排查,推测可能遇到了某些未知问题,导致统计信息被错误地覆盖成 0。很可能是遇到以下 issue 的问题,该问题将在 6.5.10 版本中得到修复:

https://github.com/pingcap/tidb/issues/52294

最后感谢 weizhen 大佬的支持!

3
1
1
0

版权声明:本文为 TiDB 社区用户原创文章,遵循 CC BY-NC-SA 4.0 版权协议,转载请附上原文出处链接和本声明。

评论
暂无评论