6

TiDB Write Conflict问题分析

 2 years ago
source link: https://www.leftpocket.cn/post/mysql/db_tidb_write_conflict/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

TiDB Write Conflict问题分析

2020-08-06 约 1475 字 预计阅读 3 分钟 次阅读

原文地址:码农在新加坡的个人博客

我们线上使用TiDB来储存数据,最近DBA报告TiDB发生了write conflict.

[2020/08/25 06:01:44.781 +08:00] [WARN] [session.go:623] [retrying] [conn=26799778] [schemaVersion=194] [retryCnt=0] [queryNum=0] [sql="UPDATE user_tab SET `region` = ?, `status` = ?, `update_time` = ? WHERE `from_userid` = ? AND `to_userid` = ? AND `type` = ? [arguments: (ID, 0, 1598306504, 118049824, 8572364, 1)]"]
[2020/08/25 06:01:44.782 +08:00] [WARN] [session.go:644] ["transaction association"] [conn=26799778] ["retrying txnStartTS"=418986460378563450] ["original txnStartTS"=418986460352348716]
[2020/08/25 06:01:44.783 +08:00] [INFO] [2pc.go:1104] ["2PC clean up done"] [conn=26799778] [txnStartTS=418986460352348716]
[2020/08/25 06:01:45.612 +08:00] [WARN] [client_batch.go:577] ["send request is cancelled"] [to=10.65.234.201:20160] [cause="context canceled"]
[2020/08/25 06:01:45.612 +08:00] [WARN] [session.go:425] [sql] [conn=26799680] [label=general] [error="[kv:9007]Write conflict, txnStartTS=418986459434844923, conflictStartTS=418986459434844925, conflictCommitTS=418986460457205920, key={tableID=89, handle=8646911292299313507} primary={tableID=89, handle=1729382264526793474} [try again later]"] [txn="Txn{state=invalid}"]

以下就针对以上log进行分析。

TiDB乐观锁

在 v3.0.8 版本之前,TiDB 默认采用乐观事务模型,在事务执行过程中并不会做冲突检测,而是在事务最终 COMMIT 提交时触发两阶段提交,并检测是否存在写写冲突。当出现写写冲突,并且开启了事务重试机制,则 TiDB 会在限定次数内进行重试,最终重试成功或者达到重试次数上限后,会给客户端返回结果。因此,如果 TiDB 集群中存在大量的写写冲突情况,容易导致集群的 Duration 比较高。

出现锁冲突的原因

TiDB 中使用 Percolator 事务模型来实现 TiDB 中的事务。Percolator 总体上就是一个二阶段提交的实现。具体的二阶段提交过程可参考乐观事务文档。

当客户端发起 COMMIT 请求的时候,TiDB 开始两阶段提交:

  1. TiDB 从所有要写入的 Key 中选择一个作为当前事务的 Primary Key
  2. TiDB 向所有的本次提交涉及到的 TiKV 发起 prewrite 请求,TiKV 判断是否所有 Key 都可以 prewrite成功
  3. TiDB 收到所有 Key 都 prewrite 成功的消息
  4. TiDB 向 PD 请求 commit_ts
  5. TiDB 向 Primary Key 发起第二阶段提交。Primary Key 所在的 TiKV 收到 commit 操作后,检查数据合法性,清理 prewrite 阶段留下的锁
  6. TiDB 收到两阶段提交成功的信息
  7. 写写冲突发生在 prewrite 阶段,当发现有其他的事务在写当前 Key (data.commit_ts >txn.start_ts),则会发生写写冲突。

TiDB 会根据 tidb_disable_txn_auto_retry 和 tidb_retry_limit 参数设置的情况决定是否进行重试,如果设置了不重试,或者重试次数达到上限后还是没有 prewrite 成功,则向 TiDB 返回 Write Conflict 错误。

write_conflict

txnlock 表示集群中存在写写冲突,txnLockFast 表示集群中存在读写冲突。

也可以通过 TiDB 日志查看是否有 [kv:9007]Write conflict 关键字,如果搜索到对应关键字,则可以表明集群中存在写写冲突。

关于日志的解释如下:

[kv:9007]Write conflict:表示出现了写写冲突 txnStartTS=416617006551793665:表示当前事务的 start_ts 时间戳,可以通过 pd-ctl 工具将时间戳转换为具体时间 conflictStartTS=416617018650001409:表示冲突事务的 start_ts 时间戳,可以通过 pd-ctl 工具将时间戳转换为具体时间 conflictCommitTS=416617023093080065:表示冲突事务的 commit_ts 时间戳,可以通过 pd-ctl 工具将时间戳转换为具体时间 key={tableID=47, indexID=1, indexValues={string, }}:表示当前事务中冲突的数据,tableID 表示发生冲突的表的 ID,indexID 表示是索引数据发生了冲突。如果是数据发生了冲突,会打印 handle=x 表示对应哪行数据发生了冲突,indexValues 表示发生冲突的索引数据 primary={tableID=47, indexID=1, indexValues={string, }}:表示当前事务中的 Primary Key 信息

通过 pd-ctl 将时间戳转换为可读时间:

./pd-ctl -u https://127.0.0.1:2379 tso {TIMESTAMP}

通过 tableID 查找具体的表名:

curl http://{TiDBIP}:10080/db-table/{tableID}

通过 indexID 查找具体的索引名:

SELECT * FROM INFORMATION_SCHEMA.TIDB_INDEXES WHERE TABLE_SCHEMA='{table_name}' AND TABLE_NAME='{table_name}' AND INDEX_ID={indexID};

另外在 v3.0.8 及之后版本默认使用悲观事务模式,从而避免在事务提交的时候因为冲突而导致失败,无需修改应用程序。悲观事务模式下会在每个 DML 语句执行的时候,加上悲观锁,用于防止其他事务修改相同 Key,从而保证在最后提交的 prewrite 阶段不会出现写写冲突的情况。

TiDB的乐观锁,会导致使用普通的SQL语句更新的时候。

UPDATE user_tab set name = `***` where userid = `a`

TiDB中当有并发请求对同一个unique key进行update操作的时候,由于乐观锁的机制,会出现Write Conflict. 而MySQL使用的是悲观锁,不会出现Write Conflict。

<全文完>

欢迎关注我的微信公众号:码农在新加坡,有更多好的技术分享。

pic

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK