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;

2 个赞

Issue

https://github.com/pingcap/tiflow/issues/11744

Root Cause

Redo log 将数据和元数据保存到外部存储。但是,redo 的数据处理模块中存在一些与错误处理相关的 Bug。当 TiCDC 集群和 redo log 外部存储之间出现网络分区故障时,可能出现:

  1. 如果写meta失败,changefeed将重新启动并尝试恢复同步过程,这是预期行为。
  2. 如果写meta成功,写数据失败,即使redo的数据处理模块已经停止,changefeed也会继续正常进行。这是非预期行为,实际上禁用了 redo log 功能,并在灾难场景中导致可能的数据不一致。

注意:网络故障的持续时间决定了这些问题是否会发生。由于 redo log 模块的指数回避机制中随机添加了抖动,所以只有当失败持续时间近似等于重试模块的超时时间(5分钟)时,才会出现写文件的概率失败。

Diagnostic Steps

  1. 部署 TiDB(上游) + TiCDC + TiDB/mysql(下游)
  2. 创建 Changefeed ,打开设置 Changefeed 配置

[consistent]

level = “eventual”

  1. 在 TiCDC 集群与 Redo 外部存储间注入约 5 分钟的网络分区故障。
  2. 上游 TiDB 有写负载的过程中,观察 TiCDC-Redo 面板的监控状态:
  • 正常情况:Redo Writer rows/s and Redo Writer bytes/s 均显示存在写负载。
  • 异常情况:Redo Writer bytes/s 显示为空,而另一个监控显示存在写负载。

Affect version

  • v6.5.10, v6.5.11
  • v7.5.2, v7.5.3, v7.5.4
  • v8.1.0, v8.1.1

Resolution

升级 TiCDC 到 v6.1.12 / v7.5.5 / v8.1.2 及之后版本

Workaround

从监控中发现这个问题后,重启 changefeed

Issue

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

Impact

如果使用了 stale read,则有一定风险影响到 Async Commit / 1PC 事务的线性一致性。在这种情况下,Async Commit / 1PC 事务可能会以过大的 commit_ts 提交,导致改事务所写入的数据,对另一个开始时间严格晚于当前事务成功提交时间的读操作不可见。用户的应用读到这些不一致的结果并使用之后,有可能因此继续写入更多有错误的数据。

Root Cause

当使用 stale read 时(无论使用哪种语法),TiDB 并未保证读用的 ts 不会超过 PD 曾分配过的最大 ts (read_ts <= pd_allocated_max_ts)。所以在一些情况下,stale read 可能会使用一个未来的 ts (超过 PD 分配过的任何 ts)来进行。可能的情况包括但不限于:

  • 用户手动指定了读取的数据的时间
  • 物理时钟存在漂移
  • PD 的写入延迟比较高,造成分配 ts 和物理时间之间存在 lag。

当发送到 TiKV 的 stale read 请求未能成功处理、TiDB 因此以普通 leader read 模式重试该读请求时,新的请求不会带有 stale_read flag,以告知 TiKV 该请求应当以非 stale read 的通常模式来进行处理。

对于发送到 TiKV 的非 stale read 请求,需要推动 max_ts 到当前请求的 read_ts。这里的 max_ts 将被用于计算 Async Commit / 1PC 事务的 commit_ts,其应当被保证永远小于等于 PD 曾经分配过的最大 ts。然而,此时就有可能因为一个 stale read 操作而被推进到一个未来的 ts,从而引发问题。

Diagnostic Steps

  • 确认集群中使用了 stale read。如果可以确认用户从未使用 stale read,那么就和该问题无关。
  • 可以确认(或疑似)Async Commit / 1PC 事务有时存在线性一致性被破坏的情况:Async Commit / 1PC 事务写入的数据在成功提交后,有时不会对紧接着的读语句立即可见。如果有办法找到这种异常的 Async Commit / 1PC 事务的具体数据的话,可以发现它的 commit_ts 的 logical 部分总是等于 1。
  • 遇到这一现象的概率可能会非常低、所以可能非常难以复现。即使集群中发生了这种现象,也未必总能有办法从中检查到。

如果观察到下列现象,则可以增加符合该问题的判断的置信度:

在 TiDB 的日志中,可能会看到这一错误:

Retrying aggressive locking with ForUpdateTS (…) less than previous LockedWithConflictTS (…)

该报错信息中的 LockedWithConflictTS 所指的 ts 的 logical 部分总是等于 1。

  • 注意该问题发生时未必一定会引起这种报错。因此找不到这种报错并不意味着不是这个问题。

可以确认集群中存在时钟漂移、或者 PD 存在写入性能问题。

如需检查 ts 的 logical 部分的数值,可使用 pd-ctl tso 命令。TiDB 日志中可以看到的错误 example:

// TiDB related error log

Error 1105 (HY000): Txn 453423637808807957 Retrying aggressive locking with ForUpdateTS (453423637808807963) less than previous LockedWithConflictTS (453423637831352321)

tiup ctl:v7.5.1 pd tso 453423637831352321

// get logic of LockedWithConflictTS

Starting component ctl: /Users/wuxuelian/.tiup/components/ctl/v7.5.1/ctl pd tso 453423637831352321

system: 2024-10-23 01:58:31.405 -0700 PDT

logic: 1

Resolution

升级到该问题已经被修复的新版本。

Workaround

  • 如果有可能,可以考虑停止使用 stale read。
  • 如果不能停用 stale read,那么我们并没有其它手段来严格地保证不触发这一问题。不过,如果可以保证如下几点,那么触发该问题的概率微乎其微:
    • 避免使用 stale read 读太新的数据(即读的时间太过接近当前时间)。
    • 确保部署集群的机器上的系统时间精确且稳定。
    • 确保 PD 性能良好。

Affected Versions

  • v6.5.0~v6.5.11
  • v7.1.0~v7.1.5
  • v7.5.0~v7.5.4
  • v8.1.0~v8.1.1

Issue

GitHub Issue : #56408


➜ ~ cat /path/to/a.csv

1|aa|beijing

1|aa|beijing

1|aa|beijing

1|aa|beijing

2|bb|shanghai

2|bb|shanghai

2|bb|shanghai

3|cc|guangzhou


在使用load data … replace into … 语法导入数据时,当导入的数据与表中主键发生冲突时,正常情况会替换表中数据。但是在非聚簇主键的表中,执行 load data … replace into … 导入存在重复行的数据时,就会触发该 BUG,不会替换原有数据,而是会违反主键唯一性插入重复的数据,校验数据索引一致性的命令也会报错。


mysql> create table a(id int, name varchar(20), addr varchar(100), primary key (id) nonclustered);

Query OK, 0 rows affected (0.02 sec)

mysql> load data local infile ‘/path/to/a.csv’ replace into table a fields terminated by ‘|’ escaped by ‘’ lines terminated by ‘\n’;

Query OK, 13 rows affected (0.01 sec)

Records: 8 Deleted: 0 Skipped: 0 Warnings: 0

mysql> select * from a;

+----±-----±----------+

| id | name | addr |

+----±-----±----------+

| 1 | aa | beijing |

| 1 | aa | beijing |

| 1 | aa | beijing |

| 1 | aa | beijing |

| 2 | bb | shanghai |

| 2 | bb | shanghai |

| 2 | bb | shanghai |

| 3 | cc | guangzhou |

+----±-----±----------+

8 rows in set (0.00 sec)

mysql> admin check table a;

ERROR 8223 (HY000): data inconsistency in table: a, index: PRIMARY, handle: 3, index-values:“” != record-values:“handle: 3, values: [KindInt64 1]”


Root Cause

load data … replace into …导入处理重复的 key 的时候逻辑不完整,tidb/executor/insert_common.go at 90c735f56125ed8eebefdee406fa18268f0b5a6c · pingcap/tidb · GitHub removeRow 返回的布尔标记在handleDuplicateKey 中没有被正确处理,导致重复的行仍然插入到了表中。

Diagnostic Steps

如果导入的数据本身存在重复行,或者与表中已存在的数据有重复,都会触发这个 Bug。此时执行 load data … replace into … 不会报错,但在完成后使用 admin check table a; 命令进行一致性检查就会出现下列数据索引不一致的错误信息:


mysql> admin check table a;

ERROR 8223 (HY000): data inconsistency in table: a, index: PRIMARY, handle: 3, index-values:“” != record-values:“handle: 3, values: [KindInt64 1]”


也可以参考 Issue 描述 中的示例。

Resolution

升级到已修复此问题的版本。

Workaround

  1. 使用聚簇主键可以避免该问题。

  2. 对于非聚簇主键表,在导入数据前确保文件和表中没有重复行,

  3. 如果数据已导入且存在重复,可通过如下操作解决:

1. 删除主键
2. 删除重复行
3. 重新创建主键

Affected Version

  • v6.5.6 ~ v6.5.11
  • v7.1.0 ~ v7.1.5
  • v7.5.0 ~ v7.5.3
  • v8.1.0 ~ v8.1.1

Fixed Version

  • v6.5.12
  • v7.1.6
  • v7.5.4
  • v8.1.2
1 个赞

Issue

在上游集群存在分区表的情况下,配置项 sink.enable-partition-separator 设置为 false(默认为 true)且 sink 类型为 storage 时,CDC 的输出文件可能会被覆盖,从而导致数据丢失。

disable the config enable-partition-separator · Issue #11979 · pingcap/tiflow

Root Cause

当 sink 类型为存储时,对于分区表,CDC 在远程存储上的默认文件路径为 {scheme}://{prefix}/{schema}/{table}/{table-version-separator}/{partition-separator}/{date-separator}/CDC{num}.{extension},其中{partition-separator} 部分表示分区的物理 ID。

但是当配置项 enable-partition-separator 设置为 false 时,{partition-separator} 部分会从文件路径中移除,即路径变为 {scheme}://{prefix}/{schema}/{table}/{table-version-separator}/{date-separator}/CDC{num}.{extension}。在这种情况下,同一分区表的不同分区会写入相同的目录,而 {num} 部分成为 CDC 文件的唯一区分标识。但由于目前 CDC 为每个分区独立维护 {num}计数器,这会导致不同分区之间的文件冲突和覆盖,从而引发数据丢失。

另外此问题在后续版本也无法修复,因为同一分区表的不同分区可能由不同的 CDC 节点处理,在这种情况下,要在所有节点之间维护一个全局唯一的 {num} 计数器以避免文件名冲突,实现起来极为复杂。因此,必须将 sink.enable-partition-separator 设置为 true,以避免潜在的数据丢失风险。

Diagnostic Steps

无。

Resolution

无。

Workaround

将配置项 sink.enable-partition-separator 设置为 true。

Affect Version

v6.5 和 v8.5 之间的所有版本均受影响。