# [Critical bug]TiDB encodes the value of an auto increment column incorrectly

## Issue

### 问题现象

1. 执行删改查时，有类似 “Failed to decode datum payload as f64” 的报错

2. 查询报错的表包含非整型自增列

3. 日志中能够看到事务自动重试日志 “retrying txnStartTS”

## 触发条件

1. 集群开启了事务自动重试(tidb_disable_txn_auto_retry = false)，此时 TiDB 对于乐观事务发生冲突时，会尝试自动重试提交事务。

2. tidb_txn_mode='optimistic' 或 begin optimistic 时，TiDB 使用乐观事务。

3. tidb_txn_mode=`pessimistic` ，非显式事务且 autocommit=1 时，单条语句失败后仍然会重试。 

4. 表定义了非整数类型自增列（例如 Double / Float ）。

5. 使用 INSERT INTO ... SELECT 语法写入数据，并由数据库隐式分配自增 ID。

6. 使用上述语法提交事务时发生冲突自动重试，并在重试后成功提交。即：

以上条件满足时，TiDB 会由于未能正确处理自增列的数据类型导致写入了错误编码的自增列数据。

### 相关链接

<https://internal.pingcap.net/jira/browse/ONCALL-4058>

<https://github.com/pingcap/tidb/issues/29892>

## Root Cause

TiDB 在重试事务时，由于未能正确处理自增列的数据类型导致写入了错误编码的自增列数据。随后在查询时解码失败。

## Diagnostic Steps

### 定位步骤

TiDB 日志中能搜索到 “Failed to decode datum payload as”

上述报错日志中包含执行错误的 SQL，SQL 中有表的自增列为非整型

TiDB 日志中能搜到日志 “retrying txnStartTS”

如果已定位错误的行，通过 HTTP MVCC 接口查询到 start_ts，在 TiDB 日志中搜索该 start_ts，搜索结果中包含 “retrying txnStartTS”

如果已定位错误的行，通过 HTTP MVCC 接口查询到 short_value，本地集群新建一张使用 BIGINT 型自增列的表，用 tidb-ctl 连上本地集群解码 short_value，能够解码成功

### 复现步骤

使用乐观事务，打开事务自动重试：

```sql
Session 1:

session1> create table t(a double key auto_increment, b int);Query OK, 0 rows affected (0.03 sec)session1> insert into t values (146576794, 1);Query OK, 1 row affected (0.01 sec)session1> begin;session1> insert into t(b) select 1; 

跳到 Session 2:

session2> begin;Query OK, 0 rows affected (0.00 sec)session2> insert into t values (146576795, 1);Query OK, 1 row affected (0.00 sec)session2> insert into t values (146576796, 1);                             Query OK, 1 row affected (0.00 sec)session2> commit;Query OK, 0 rows affected (0.01 sec)

跳回 Session1>

session1> commit;Query OK, 0 rows affected (2.63 sec)session1> select * from t;ERROR 1105 (HY000): Failed to decode datum payload as f64
```

可以看到日志：

```golang
[2021/11/17 21:36:40.142 +08:00] [WARN] [session.go:483] [sql] [conn=3] [label=general] [error="[kv:9007]Write conflict, txnStartTS=429170669270335488, conflictStartTS=429170671639855104, conflictCommitTS=429170674261557248, key={tableID=66, indexID=1, indexValues={146576796, }} primary=[]byte(nil) [try again later]"] [txn="Txn{state=invalid}"][2021/11/17 21:36:40.143 +08:00] [WARN] [session.go:691] [retrying] [conn=3] [schemaVersion=51] [retryCnt=0] [queryNum=0] [sql=begin] [isPessimistic=false][2021/11/17 21:36:40.143 +08:00] [INFO] [2pc.go:1399] ["2PC clean up done"] [conn=3] [txnStartTS=429170669270335488][2021/11/17 21:36:40.143 +08:00] [WARN] [session.go:691] [retrying] [conn=3] [schemaVersion=51] [retryCnt=0] [queryNum=1] [sql="insert into t(b) select 1"] [isPessimistic=false][2021/11/17 21:36:42.761 +08:00] [WARN] [session.go:691] [retrying] [conn=3] [schemaVersion=51] [retryCnt=0] [queryNum=2] [sql=commit] [isPessimistic=false][2021/11/17 21:36:42.762 +08:00] [WARN] [session.go:716] ["transaction association"] [conn=3] ["retrying txnStartTS"=429170674930286592] ["original txnStartTS"=429170669270335488] [isPessimistic=false]
```

## Resolution

### 修复版本

v4.0.16

v5.0.5

v5.1.3

v5.2.3

v5.3.1

### 解决方案

如果使用源文件导入的数据且源文件存在，使用源文件重新导入数据

如果自增列的值不重要，导出正常列的值到新表，新表的自增列重新分配

如果自增列的值重要，导出正常行到新表，尝试手动插入不正常的行

### 最佳实践

我们建议使用 BIGINT 作为自增列的类型。

## Workaround