|
| 1 | +--- |
| 2 | +title: 乐观事务模型下写写冲突问题排查 |
| 3 | +summary: 介绍 TiDB 中乐观锁下写写冲突出现的原因以及解决方案。 |
| 4 | +category: troubleshooting |
| 5 | +--- |
| 6 | + |
| 7 | +# 乐观事务模型下写写冲突问题排查 |
| 8 | + |
| 9 | +本文介绍 TiDB 中乐观锁下写写冲突出现的原因以及解决方案。 |
| 10 | + |
| 11 | +在 v3.0.8 版本之前,TiDB 默认采用乐观事务模型,在事务执行过程中并不会做冲突检测,而是在事务最终 COMMIT 提交时触发两阶段提交,并检测是否存在写写冲突。当出现写写冲突,并且开启了事务重试机制,则 TiDB 会在限定次数内进行重试,最终重试成功或者达到重试次数上限后,会给客户端返回结果。因此,如果 TiDB 集群中存在大量的写写冲突情况,容易导致集群的 Duration 比较高。 |
| 12 | + |
| 13 | +## 出现写写冲突的原因 |
| 14 | + |
| 15 | +TiDB 中使用 [Percolator](https://www.usenix.org/legacy/event/osdi10/tech/full_papers/Peng.pdf) 事务模型来实现 TiDB 中的事务。Percolator 总体上就是一个二阶段提交的实现。具体的二阶段提交过程可参考[乐观事务文档](/optimistic-transaction.md)。 |
| 16 | + |
| 17 | +当客户端发起 `COMMIT` 请求的时候,TiDB 开始两阶段提交: |
| 18 | + |
| 19 | +1. TiDB 从所有要写入的 Key 中选择一个作为当前事务的 Primary Key |
| 20 | +2. TiDB 向所有的本次提交涉及到的 TiKV 发起 prewrite 请求,TiKV 判断是否所有 Key 都可以 prewrite 成功 |
| 21 | +3. TiDB 收到所有 Key 都 prewrite 成功的消息 |
| 22 | +4. TiDB 向 PD 请求 commit_ts |
| 23 | +5. TiDB 向 Primary Key 发起第二阶段提交。Primary Key 所在的 TiKV 收到 commit 操作后,检查数据合法性,清理 prewrite 阶段留下的锁 |
| 24 | +6. TiDB 收到两阶段提交成功的信息 |
| 25 | + |
| 26 | +写写冲突发生在 prewrite 阶段,当发现有其他的事务在写当前 Key (data.commit_ts > txn.start_ts),则会发生写写冲突。 |
| 27 | + |
| 28 | +TiDB 会根据 `tidb_disable_txn_auto_retry` 和 `tidb_retry_limit` 参数设置的情况决定是否进行重试,如果设置了不重试,或者重试次数达到上限后还是没有 prewrite 成功,则向 TiDB 返回 `Write Conflict` 错误。 |
| 29 | + |
| 30 | +## 如何判断当前集群存在写写冲突 |
| 31 | + |
| 32 | +可以通过 Grafana 监控查看集群写写冲突的情况: |
| 33 | + |
| 34 | +* 通过 TiDB 监控面板中 KV Errors 监控栏中 KV Backoff OPS 监控指标项,查看 TiKV 中返回错误信息的数量 |
| 35 | + |
| 36 | +  |
| 37 | + |
| 38 | + txnlock 表示集群中存在写写冲突,txnLockFast 表示集群中存在读写冲突。 |
| 39 | + |
| 40 | +* 通过 TiDB 监控面板中 KV Errors 监控栏中 Lock Resolve OPS 监控指标项,查看事务冲突相关的数量 |
| 41 | + |
| 42 | +  |
| 43 | + |
| 44 | + expired、not_expired、wait_expired 表示对应的 lock 状态 |
| 45 | + |
| 46 | +* 通过 TiDB 监控面板中 KV Errors 监控栏中 KV Retry Duration 监控指标项,查看 KV 重试请求的时间 |
| 47 | + |
| 48 | +  |
| 49 | + |
| 50 | +也可以通过 TiDB 日志查看是否有 `[kv:9007]Write conflict` 关键字,如果搜索到对应关键字,则可以表明集群中存在写写冲突。 |
| 51 | + |
| 52 | +## 如何解决写写冲突问题 |
| 53 | + |
| 54 | +如果通过以上方式判断出集群中存在大量的写写冲突,建议找到冲突的数据,以及写写冲突的原因,看是否能从应用程序修改逻辑,加上重试的逻辑。当出现写写冲突的时候,可以在 TiDB 日志中看到类似的日志: |
| 55 | + |
| 56 | +```log |
| 57 | +[2020/05/12 15:17:01.568 +08:00] [WARN] [session.go:446] ["commit failed"] [conn=3] ["finished txn"="Txn{state=invalid}"] [error="[kv:9007]Write conflict, txnStartTS=416617006551793665, conflictStartTS=416617018650001409, conflictCommitTS=416617023093080065, key={tableID=47, indexID=1, indexValues={string, }} primary={tableID=47, indexID=1, indexValues={string, }} [try again later]"] |
| 58 | +``` |
| 59 | + |
| 60 | +关于日志的解释如下: |
| 61 | + |
| 62 | +* `[kv:9007]Write conflict`:表示出现了写写冲突 |
| 63 | +* `txnStartTS=416617006551793665`:表示当前事务的 start_ts 时间戳,可以通过 pd-ctl 工具将时间戳转换为具体时间 |
| 64 | +* `conflictStartTS=416617018650001409`:表示冲突事务的 start_ts 时间戳,可以通过 pd-ctl 工具将时间戳转换为具体时间 |
| 65 | +* `conflictCommitTS=416617023093080065`:表示冲突事务的 commit_ts 时间戳,可以通过 pd-ctl 工具将时间戳转换为具体时间 |
| 66 | +* `key={tableID=47, indexID=1, indexValues={string, }}`:表示当前事务中冲突的数据,tableID 表示发生冲突的表的 ID,indexID 表示是索引数据发生了冲突。如果是数据发生了冲突,会打印 `handle=x` 表示对应哪行数据发生了冲突,indexValues 表示发生冲突的索引数据 |
| 67 | +* `primary={tableID=47, indexID=1, indexValues={string, }}`:表示当前事务中的 Primary Key 信息 |
| 68 | + |
| 69 | +通过 pd-ctl 将时间戳转换为可读时间: |
| 70 | + |
| 71 | +{{< copyable "" >}} |
| 72 | + |
| 73 | +```shell |
| 74 | +./pd-ctl -u https://127.0.0.1:2379 tso {TIMESTAMP} |
| 75 | +``` |
| 76 | + |
| 77 | +通过 tableID 查找具体的表名: |
| 78 | + |
| 79 | +{{< copyable "" >}} |
| 80 | + |
| 81 | +```shell |
| 82 | +curl http://{TiDBIP}:10080/db-table/{tableID} |
| 83 | +``` |
| 84 | + |
| 85 | +通过 indexID 查找具体的索引名: |
| 86 | + |
| 87 | +{{< copyable "sql" >}} |
| 88 | + |
| 89 | +```sql |
| 90 | +SELECT * FROM INFORMATION_SCHEMA.TIDB_INDEXES WHERE TABLE_SCHEMA='{table_name}' AND TABLE_NAME='{table_name}' AND INDEX_ID={indexID}; |
| 91 | +``` |
| 92 | + |
| 93 | +另外在 v3.0.8 及之后版本默认使用悲观事务模式,从而避免在事务提交的时候因为冲突而导致失败,无需修改应用程序。悲观事务模式下会在每个 DML 语句执行的时候,加上悲观锁,用于防止其他事务修改相同 Key,从而保证在最后提交的 prewrite 阶段不会出现写写冲突的情况。 |
0 commit comments