# [Critical bug] Optimistic transaction data inconsistency problem explanation

## Issue

<https://internal.pingcap.net/jira/browse/TIDB-7559>
<https://internal.pingcap.net/jira/browse/ONCALL-1614>
<https://internal.pingcap.net/jira/browse/ONCALL-1724>

乐观事务，在事务内写入且删除自己写入的数据，Primary Key 是排序后的首个 Key，非删除首 Key 和剩余 Key 不在一个 Region（如果在一个 region 会一起 Commit），有其他事务在写并删除事务提交过程中冲突。

### session 1

```sql
drop table if exists t1;

create table t1(c1 int, c2 varchar(20), unique key uk(c2), key k1(c1));

split table t1 index k1 between (0) and (5000) regions 2;

begin; insert

into t1 values(1, 'pkrow');

insert into t1 values(4000, 'secRow2');

delete from t1 where c1 = 1;

commit;
```

### session 2

```sql
delete from t1 where c2 = 'secRow2'; -- 与 session 1 的 commit 并发执行
```

### 可能导致的现象

#### 1. 事务 T1 写入的部分 Key 数据丢失，即数据或索引缺失

可能的报错：

```sql

Error 8021: inconsistent extra index PRIMARY, handle 60195209 not found in table

Error 1105: can not be duplicated row, due to old row not found. handle 7321690 not found

Error 1062: Duplicate entry '23' for key 'kid'
```

#### 2. 数据本应该被删除，但是被错误回滚后导致后续插入报重复键错误

* 或者通过日志查找 2PC primary key committed 这种信息

#### 3. 事务 T2 解锁失败，导致 SQL 失败，没有数据缺失。可能的报错

```log
unexpected resolve err: abort:"Txn(Mvcc(Committed { commit_ts: TimeStamp(417066463405801519) }))"
```

#### 影响版本

3.0.12 及以后的 3.0

3.1.0-rc 及以后的 3.1

4.0.0-rc 及以后的 4.0

#### 修复版本

3.0.16

3.1.3

4.0.2

## Root Cause

启动乐观事务 T1 写入记录并在提交前删除新记录（或更新引起的删除）并提交事务，如果插入并删除的记录 Key0 在整个事务修改 keys 中排首个，会在两阶段提交 prewrite 阶段被选作事务的 primary key，标识事务 T1 的最终状态。在 commit 阶段， 由于 key0 不需要提交，会重新整理需要提交的 keys，会导致 key0 被忽略，且 commit 提交阶段使用的 primary key 指向另外一个 key1，当 key1 成功提交之后，T1 即认为事务提交成功，应答 client，再异步提交其他 keys。而实际上此时，prewrite lock 中记录的 primary key 仍然是 key0，会导致如下情况：

在 T1 事务提交二阶段完成 key1 的 Commit 后，在其他 Keys 完成 Commit 前， 其他事务 T2 访问遇到 T1 事务未提交的 lock 根据 lock 中记录 primary key 查询事务 T1 的状态，如果查询操作发现事务 T1 的 key0 已经过期，会尝试写 rollback 记录到 key0 回滚事务 T1，然后再 resolve rollback 掉碰到的 lock。至此，T1 已经应答了 client 事务提交成功，但 T1 事务两阶段提交结果实际上部分 key 成功部分 key 被回滚，数据产生不一致。

## Diagnostic Steps

## Resolution

常见场景排查

该问题常见于 DM 同步场景，如果使用版本与影响版本一致，并使用乐观事务模式进行同步，则出现问题的几率较大。

确定数据损失程度

* 对所有可能出问题的表进行 admin check table，确认数据索引的一致性

* 如果系统有上游数据，可以通过某些手段确定表数据是否有损失，如，跟上游数据进行比对（sync-diff-inspector） 修复数据

* 如上下游数据已经存在差异，则推荐通过上游重新同步来恢复数据，下游 TiDB 改为悲观事务也可避免再次出现该问题。

* 如果上下游数据一致，仅有数据索引不一致，可以使用 admin recover index 来修复索引数据

* 如果没有上游数据做参照，则可以先使用 admin recover index 来修复索引数据，再通过 admin cleanup index 来删除多余的索引

## Workaround

N/A
