write conflict vs commited 错误

今天遇到一个错误,在事务写的的时候(乐观锁,事务中包含2个put动作),第一次发生了resolve lock错误: [There was a write conflict or resolve lock error: Failed to resolve lock],乐观锁冲突的时候很正常,那我就重试嘛,然后第二次碰到了这个错误:

[There was a problem when accessing the underlying store: KeyError { locked: None, retryable: "", abort: "Error(Txn(Error(Mvcc(Error(Committed{ start_ts: TimeStamp(461847508362986035), commit_ts: TimeStamp(461847508362986058), key: [116, 0, 0, 0, 15, 95, 114, 1, 53, 55, 97, 98, 102, 55, 56, 97, 255, 45, 100, 50, 99, 48, 45, 52, 55, 255, 57, 54, 45, 97, 50, 55, 54, 45, 255, 52, 52, 98, 102, 49, 97, 53, 50, 255, 99, 100, 50, 102, 46, 53, 52, 53, 255, 56, 54, 55, 52, 46, 49, 46, 114, 255, 103, 119, 46, 109, 97, 105, 110, 0, 254, 1, 49, 54, 52, 51, 0, 0, 0, 0, 251] })))))", conflict: None, already_exist: None, deadlock: None, commit_ts_expired: None, txn_not_found: None, commit_ts_too_large: None, assertion_failed: None, primary_mismatch: None }]

Committed这个错误我是第一次见,之前都是resolve lock error 和 write conflict错误,不太能理解committed错误到底是什么意思,和write conflict (start_ts < committed_ts) 相比,committed代表了什么呢?

可能要检查一下 存储完整性,看看是否之前出现服务器掉电或者存储异常,这个问题不是常规逻辑问题,可能是 mvcc 版本一致性问题,最好通过对应的 region 检查一下 table 的状态,可以通过 admin check table 检查 table 数据完整性等问题。

我看了一下admin check table,这个命令似乎是检查主表和其索引的一致性(index条目数量以及对应列的值是否正确),我的问题可能与此无关,我补充下我们的问题场景:我们没有用tidb,而是直接用的tikv_client,向tikv写入kv对,使用了tikv_client 提供的事务接口(不是raw),如tx.set, tx.commit等,上层也没有实现索引相关的东西。另外我想问一下,committed错误报错的场景是什么?是哪一步检查没能通过导致了该问题?

你补充的真好 :+1: 我就说没有遇见过类似问题,可以把这个标签增加一个 TiKV 或者 Raw TiKV 模式了。

Slack @米波五兄弟 来这里看看有没有 TiKV 的社区专家可以帮助到你

1 个赞

感谢,我需要 tikv-wg 的工作区管理员邀请才能登陆这个网站,如果可以的话,请邀请下这个email:ytypy9348@gmail.com

有请大佬登场~~

再次补充:
我们尝试通过tikv_rust_client进行一个乐观事务请求(包含一个删除和一个更新)

  1. 第一次请求发生resolve_lock error
  2. 第二次重试发生committed错误,并且在tikv日志中我们也发现了这个错误的日志!日志如下:

[2025/10/30 07:16:31.509 +00:00] [ERROR] [errors.rs:487] [“txn aborts”] [err_code=KV:Storage:Committed] [err=“Error(Txn(Error(Mvcc(Error(Committed { start_ts: TimeStamp(461847508362986035), commit_ts: TimeStamp(461847508362986058), key: [116, 0, 0, 0, 15, 95, 114, 1, 53, 55, 97, 98, 102, 55, 56, 97, 255, 45, 100, 50, 99, 48, 45, 52, 55, 255, 57, 54, 45, 97, 50, 55, 54, 45, 255, 52, 52, 98, 102, 49, 97, 53, 50, 255, 99, 100, 50, 102, 46, 53, 52, 53, 255, 56, 54, 55, 52, 46, 49, 46, 114, 255, 103, 119, 46, 109, 97, 105, 110, 0, 254, 1, 49, 54, 52, 51, 0, 0, 0, 0, 251] })))))”] [thread_id=7]
按理来说committed错误表示这个事务已经提交了?也就是说此时这个事务实际上应该执行成功了?但是后续我们发现,需要删除的数据依然存在,但是更新命令似乎生效了?

我现在有点怀疑是客户端侧的代码我们写的可能有些问题,比如在tikv反错后(timeout,grpc error, connection refused等等),我们主动执行了rollback命令,按理来说,乐观锁似乎并不需要额外的rollback操作?
但是在rollback tikv_rust_client实现中,除了没有任何mutation的场景,哪怕是乐观锁,依然会发送new_batch_rollback_request命令。
我的问题是,async write 乐观锁在tikv反错后,是否需要rollback操作?

提交写一致

不能强一致,对数据都不安全

要保证数据的强一致

检查一下完整性

事务一致性?

ACID。永久性

两者均属于乐观锁机制下的并发冲突,核心解决方式都是重试事务,通过新的 start_ts 避开冲突窗口

Committed错误是指你重试的事务,其start_ts(事务启动时间戳)早于某个已提交事务的 commit_ts,且两者修改了同一 key—— 相当于你的事务启动后,目标 key 已被其他事务成功提交修改,此时重试的事务因数据已被覆盖,无法再提交

如果是start_ts < commit_ts,错误码不应该是write conflict吗?
另外在看tikv源码时,看到了一部分关于committed错误的源码:

pub fn check_txn_status_missing_lock(
    txn: &mut MvccTxn,
    reader: &mut SnapshotReader<impl Snapshot>,
    primary_key: Key,
    mismatch_lock: Option<Lock>,
    action: MissingLockAction,
    resolving_pessimistic_lock: bool,
) -> Result<TxnStatus> {
    MVCC_CHECK_TXN_STATUS_COUNTER_VEC.get_commit_info.inc();
 
    match reader.get_txn_commit_record(&primary_key)? {
        TxnCommitRecord::SingleRecord { commit_ts, write } => {
            if write.write_type == WriteType::Rollback {
                Ok(TxnStatus::RolledBack)
            } else {
                Ok(TxnStatus::committed(commit_ts))
            }
        }

...

    fn get_txn_commit_record(&mut self, key: &Key, start_ts: TimeStamp) -> Result<TxnCommitRecord> {
        // It's possible a txn with a small `start_ts` has a greater `commit_ts` than a
        // txn with a greater `start_ts` in pessimistic transaction.
        // I.e., txn_1.commit_ts > txn_2.commit_ts > txn_2.start_ts > txn_1.start_ts.
        //
        // Scan all the versions from `TimeStamp::max()` to `start_ts`.
        let mut seek_ts = TimeStamp::max();
        let mut gc_fence = TimeStamp::from(0);
        while let Some((commit_ts, write)) = self.seek_write(key, seek_ts)? {
            if write.start_ts == start_ts {
                return Ok(TxnCommitRecord::SingleRecord { commit_ts, write });
            }

可以看到在这里面,当write表的start_ts = 当前事务的start_ts,且不是rollback的才会反这个错误,不过其他地方可能也会反这个错误。

用的是原生 tidb 集群,使用 sql 防晒提交的么?
还是用的裸 KV?

这是 TiDB 的分布式事务 MVCC(多版本并发控制)层错误,核心原因是事务提交时发现目标 Key 已被其他事务锁定或提交 ,导致当前事务提交失败。这个错误通常与并发事务冲突、事务超时或 TiKV 存储层的 MVCC 版本管理异常相关。可以试试切换乐观锁?

resolve lock是乐观锁?