tikv原生api不提供开箱即用事务抽象,直接调用rawkv无原子性,误用transactionalkv易致锁残留、写偏或提交失败;需严格管理生命周期、显式wait、正确重试与清理。

为什么直接用 TiKV 原生 API 做分布式事务容易出错
TiKV 本身不提供“开箱即用”的事务抽象,它只暴露 RawKV 和 TransactionalKV 两层接口。直接调用 RawKV(如 Put/Get)无法保证跨 key 的原子性;而误用 TransactionalKV(比如手动管理 Begin/Commit 生命周期但没处理失败重试)会导致事务卡住、锁残留或写偏(write skew)。最常见错误是把 txn.Commit() 当作同步操作——它实际是异步提交,需显式 txn.Commit().Wait() 或检查返回 error。
用 go-tikv/client-v2 正确开启一个事务
必须使用 github.com/tikv/client-go/v2(不是旧版 v1),且初始化时传入带 PD 地址的配置。事务对象由 txn, err := client.Begin() 创建,不是手动 new。
-
client实例应复用,避免频繁重建导致 PD 连接震荡 - 事务默认超时为 30s,可通过
client.NewTxnWithStartTS(...)或设置client.WithTxnTimeOut(...)调整 - 读写操作必须全部在
txn上调用:用txn.Get(k)而非client.Get(k),否则绕过事务上下文 - 写入后必须显式
err := txn.Commit().Wait(),否则提交可能失败但无感知
如何避免事务冲突和死锁
TiKV 的乐观事务模型在高并发写同 key 区域时会触发 WriteConflict 错误("write conflict"),而非阻塞等待。不能靠重试次数硬扛,得结合业务逻辑优化。
- 按确定顺序访问 key(例如总是先更新用户余额再更新订单状态),减少循环依赖
- 对热点 key 做 client 端分片(如
user_id % 16后缀到 key),分散锁竞争 - 捕获
errors.Is(err, tikverr.ErrWriteConflict)后,建议退避 5–50ms 再重试,避免雪崩 - 慎用
txn.Scan()大范围读——它会拉取全部版本,易 OOM;改用txn.Iter()流式处理
事务失败后状态清理的关键点
事务未成功 Commit() 也不调用 Rollback() 时,TiKV 会在 TTL(默认 10min)后自动清理锁,但这期间其他事务会因锁等待而失败。生产环境必须确保每个 Begin() 都有对应清理路径。
- 用
defer txn.Rollback()不可靠——如果Commit()成功,再 Rollback 会 panic - 正确模式:只在
Commit()返回 error 时调用txn.Rollback() - 更稳妥的做法是用
txn.SetOption(tikv.WithTxnTTL(60))缩短锁持有时间,并配合监控tikv_scheduler_pending_tasks指标 - 若进程崩溃,依赖 TiKV 的
resolve_locks机制自动清理,但该过程有延迟,关键业务需设计幂等补偿
.Wait() 或漏一个 Rollback 条件,就可能在线上引发连锁超时。











