TiDB Bug List=

Issue

问题描述

由于 LOCK 语义未被正确执行,上述问题可能造成丢失更新等并发异常,以及并发事务写入结果不正确写入数据丢失。

根因分析

如下例子所述,当以 autocommit 方式在事务外执行 “select for update” 语句时,”for update” 语义不会生效(参考 https://docs.pingcap.com/zh/tidb/stable/pessimistic-transaction#和-mysql-innodb-的差异 第五点),最终生成的执行计划不包含 lock 语义(见下方例子中 Point_Get_1 算子)。

后续 session 开启显示事务后,执行相同 “select for update” 语句,前面的执行计划被复用,导致无 LOCK 语义的计划被使用,造成 LOCK 语义丢失,使得事务内 “select for update” 语句按照 “select” 语义执行。

mysql> select @@autocommit; – enable autocommit
±-------------+
| @@autocommit |
±-------------+
| 1 |
±-------------+
create table t (pk int, a int, primary key(pk)); – create a table with PK
prepare st from ‘select * from t where pk=? for update’; – prepare a PointPlan statement
set @pk=1;
execute st using @pk; – execute this statement to generate a PointPlan cached in Plan Cache
– plan of this exec-statement, Lock operations for “for update” are optimized by auto-commit
±------------±--------±--------±-----±--------------±-----------------------------------------------------------±--------------±-------±-----+
| id | estRows | actRows | task | access object | execution info | operator info | memory | disk |
±------------±--------±--------±-----±--------------±-----------------------------------------------------------±--------------±-------±-----+
| Point_Get_1 | 1.00 | 0 | root | table:t | time:94.1µs, loops:1, Get:{num_rpc:1, total_time:42.5µs} | handle:2 | N/A | N/A |
±------------±--------±--------±-----±--------------±-----------------------------------------------------------±--------------±-------±-----+
begin;
set @pk=1;
execute st using @pk; – the optimizer decided to reuse the prior PointPlan, which is incorrect.
mysql> select @@last_plan_from_cache;
±-----------------------+
| @@last_plan_from_cache |
±-----------------------+
| 1 |
±-----------------------+

诊断方法

使用上述例子中类似步骤,查看后执行的 “select for update” 语句对应执行计划是否从 plan cache 中获取,以及 operator info 中是否包含 lock 标记。

影响版本

v6.1.0 - v6.1.7

v6.5.0 - v6.5.10

v7.1.0 - v7.1.5

v7.5.0 - v7.5.2

v8.1.0

问题修复版本

修复 PR:

修复版本:v6.1.8, v6.5.11, v7.1.6, v7.5.3, v8.1.1

Workaround 方法

两种方式

2 个赞

请问BR log backup这个critical bug,solution中6.5.10是数据库版本,还是BR的版本?
若用6.5.10的BR备份6.5.7的tidb数据库,是否会有这个问题?

BR 版本需要和 Tidb 保持一直

[Critical bug] TiDB 并行aggregation spill 可能会导致结果出错
问题

对于并行模式的 hash aggregation 算子(hash aggregation 算子默认即为并行模式),如果触发了 spil,对于特定的aggregation 函数(count 和 avg ),会返回错误的结果

根因
在 TiDB aggregtion 算子的并行 spill 实现中,存在一个 bug 导致在以下两种情况下,最终的结果会是错误的

如果 hash aggregation 算子中带有 count 函数

如果 hash aggregation 算子没有被下推到 TiKV/TiFlash,且 hash aggregation 算子中带有 avg 函数

诊断步骤
判断 hash aggregation 算子是否在 parallel 模式下触发了 spill.

这个判断主要依据是 explain analyze 的结果。在 explain analyze 结果中,对于 hash aggregation 算子,如果 partial_worker 或者 final_worker 的并发度大于 1,说明这个 aggregation 运行在 parallel 模式。如果 hash aggregation 算子的 disk usage 不是 N/A, 说明该 hash aggregation 触发了 spill。

以下面的 explain analyze 结果为例,该 query 中的 hash aggregation 触发了 parallel spill,因为:

partial_worker 和 final_worker 的并发度是 5

hash aggregation 算子的 disk usage 是 1.85 GB

mysql> explain analyze select count(), value from spill_test group by value having count() > 0;
±-----------------------------±------------±---------±----------±-----------------±----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------±------------------------------------------------------------------------------------------------------------------------------±--------±--------+
| id | estRows | actRows | task | access object | execution info | operator info | memory | disk |
±-----------------------------±------------±---------±----------±-----------------±----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------±------------------------------------------------------------------------------------------------------------------------------±--------±--------+
| Selection_7 | 6848921.60 | 0 | root | | time:2m33.6s, loops:1, RU:44821.700304 | gt(Column#4, 0) | 61.7 KB | N/A |
| └─HashAgg_12 | 8561152.00 | 17099136 | root | | time:2m33.3s, loops:16702, partial_worker:{wall_time:15.445530168s, concurrency:5, task_num:625, tot_wait:32.672876439s, tot_exec:44.553148301s, tot_time:1m17.22698924s, max:15.445404668s, p95:15.445404668s}, final_worker:{wall_time:2m33.608198045s, concurrency:5, task_num:5, tot_wait:2.8µs, tot_exec:0s, tot_time:12m45.060517323s, max:2m33.608064545s, p95:2m33.608064545s} | group by:test.spill_test.value, funcs:count(Column#7)->Column#4, funcs:firstrow(test.spill_test.value)->test.spill_test.value | 3.24 GB | 1.85 GB |
| └─TableReader_13 | 8561152.00 | 17099216 | root | | time:3.07s, loops:626, cop_task: {num: 625, max: 632.4ms, min: 1.04ms, avg: 135.4ms, p95: 444.5ms, max_proc_keys: 51200, p95_proc_keys: 51200, tot_proc: 1m12.3s, tot_wait: 252ms, copr_cache_hit_ratio: 0.00, build_task_duration: 48.8µs, max_distsql_concurrency: 15}, rpc_info:{Cop:{num_rpc:625, total_time:1m24.6s}} | data:HashAgg_8 | 40.3 MB | N/A |
| └─HashAgg_8 | 8561152.00 | 17099216 | cop[tikv] | | tikv_task:{proc max:620ms, min:0s, avg: 123.8ms, p80:240ms, p95:400ms, iters:16740, tasks:625}, scan_detail: {total_process_keys: 17122304, total_process_keys_size: 1338451200, total_keys: 17122929, get_snapshot_time: 68.6ms, rocksdb: {key_skipped_count: 17122304, block: {cache_hit_count: 4366, read_count: 47582, read_byte: 126.9 MB, read_time: 28.3s}}}, time_detail: {total_process_time: 1m12.3s, total_suspend_time: 7.42s, total_wait_time: 252ms, total_kv_read_wall_time: 52.1s, tikv_wall_time: 1m20.8s} | group by:test.spill_test.value, funcs:count(1)->Column#7 | N/A | N/A |
| └─TableFullScan_11 | 17122304.00 | 17122304 | cop[tikv] | table:spill_test | tikv_task:{proc max:450ms, min:0s, avg: 83.4ms, p80:160ms, p95:300ms, iters:16740, tasks:625} | keep order:false | N/A | N/A |
±-----------------------------±------------±---------±----------±-----------------±----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------±------------------------------------------------------------------------------------------------------------------------------±--------±--------+
5 rows in set (2 min 33.87 sec)
判断 hash aggregation 是否下推到 TiKV/TiFlash

这个判断主要基于 query 的 plan。如果一个 hash aggregation 被下推到了 TiKV/TiFlash,它会被转化成一个两阶段的 hash aggregation。其中一个 hash aggregation 算子的 task type 是 cop[tikv]/cop[tiflash]/batchcop[tiflash]/mpp[tiflash],另一个 hash aggregation 算子的 task type 是 root

以下面两个 query 的 plan 为例,plan 1 中 hash aggregation 被下推到了 TiKV,plan 2 中 hash aggregation 没有被下推到TiKV

plan 1

mysql> explain select count(), value from spill_test group by value having count() > 0;
±-----------------------------±------------±----------±-----------------±------------------------------------------------------------------------------------------------------------------------------+
| id | estRows | task | access object | operator info |
±-----------------------------±------------±----------±-----------------±------------------------------------------------------------------------------------------------------------------------------+
| Selection_7 | 6848921.60 | root | | gt(Column#4, 0) |
| └─HashAgg_12 | 8561152.00 | root | | group by:test.spill_test.value, funcs:count(Column#7)->Column#4, funcs:firstrow(test.spill_test.value)->test.spill_test.value |
| └─TableReader_13 | 8561152.00 | root | | data:HashAgg_8 |
| └─HashAgg_8 | 8561152.00 | cop[tikv] | | group by:test.spill_test.value, funcs:count(1)->Column#7 |
| └─TableFullScan_11 | 17122304.00 | cop[tikv] | table:spill_test | keep order:false |
±-----------------------------±------------±----------±-----------------±------------------------------------------------------------------------------------------------------------------------------+
plan 2

mysql> explain select count(), value from spill_test group by value having count() > 0;
±---------------------------±------------±----------±-----------------±-----------------------------------------------------------------------------------------------------------------------+
| id | estRows | task | access object | operator info |
±---------------------------±------------±----------±-----------------±-----------------------------------------------------------------------------------------------------------------------+
| Selection_7 | 13697843.20 | root | | gt(Column#4, 0) |
| └─HashAgg_10 | 17122304.00 | root | | group by:test.spill_test.value, funcs:count(1)->Column#4, funcs:firstrow(test.spill_test.value)->test.spill_test.value |
| └─TableReader_15 | 17122304.00 | root | | data:TableFullScan_14 |
| └─TableFullScan_14 | 17122304.00 | cop[tikv] | table:spill_test | keep order:false |
±---------------------------±------------±----------±-----------------±-----------------------------------------------------------------------------------------------------------------------+
检查 hash aggregation 算子中是否包含可能会输出错误结果的 aggregation 函数

当 hash aggregation 触发了 parallel spill 时

如果 hash aggregation 被下推到 TiKV/TiFlash: count 函数会返回错误结果

如果 hash aggregation 没有被下推到 TiKV/TiFlash: count 和 avg 函数都会返回错误的结果

解决方法
在 v8.1.0 中,除了关闭 hash aggregation 的 parallel spill 外没有其他解决方法

这个 bug 会在 v8.1.1 中修复

绕过方法
关闭 hash aggregation 的 parallel spill 功能:

set tidb_enable_parallel_hashagg_spill=0;
影响版本
v8.0.0

v8.1.0

v8.2.0

修复版本
v8.1.1

v8.3.0

补充说明
Parallel hash agg spill 在 v8.1.0 中并未 GA,所以不推荐在 v8.1.x 集群中在生产环境中默认启用 parallel hash agg spill 的特性

在 v8.1.1 中我们会将 tidb_enable_parallel_hashagg_spill 的默认值改成 off。所以对于 v8.1.x(x != 0) 的新装集群,parallel hash agg spill 功能都会默认关闭。但是对于从 v8.0.0/v8.1.0 升级到 v8.1.x 的集群,tidb_enable_parallel_hashagg_spill 会保持集群的原有值(默认是 on),可以通过以下命令来显式关闭 parallel hash agg 的 spill

set @@global.tidb_enable_parallel_hashagg_spill=0;

1 个赞