暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

TiDB调研手记5 -- TiDB的乐观锁与悲观锁模型

猿的话 2021-04-20
2166
点击蓝字 关注我们


TiDB的乐观锁与悲观锁模型

上次TiDB出现并发更新导致超时报错(请看:TiDB调研手记2 -- 锁冲突),排查期间总结了TiDB的乐观锁和悲观锁的模型对比,作为本次的调研手记。



TiDB的2PC过程

    2PC过程为:Prewrite 和 Commit 两个阶段

【本篇主要为总结,相关介绍同样请看:TiDB调研手记2 -- 锁冲突


TiDB的乐观锁和悲观锁模型对比


乐观锁模型

悲观锁模型

过程差异

2PC:Prewrite、Commit

2PC之前增加 Acquire Pessimistic Lock 阶段

(同乐观锁)TiDB 收到来自客户端的 begin 请求,获取当前版本号作为本事务的 StartTS。

TiDB 收到来自客户端的更新数据的请求:TiDB 向 TiKV 发起加悲观锁请求,该锁持久化到 TiKV。

(同乐观锁)客户端发起 commit,TiDB 开始执行与乐观锁一样的两阶段提交。

Prewrite 阶段

对目标 key 分别上 primary lock 和 secondary lock。

在冲突严重的场景中,会出现写冲突 (write conflict)、keyislocked 等报错

在悲观锁模式下,在事务的提交阶段沿用乐观锁模式,所以在 Prewrite 阶段乐观锁遇到的锁相关报错,在悲观锁模式同样会遇到。

读写冲突

监控信息:

KV Backoff OPS 面板:txnLockFast

 

读取数据时,会获取一个包含当前物理时间且全局唯一递增的时间戳作为当前事务的 start_ts

事务在读取时,需要读到目标 key 的 commit_ts 小于这个事务的 start_ts 的最新的数据版本。

当读取时发现目标 key 上存在 lock 时,因为无法知道上锁的那个事务是在 Commit 阶段还是 Prewrite 阶段,所以就会出现读写冲突的情况

 

处理建议:

在遇到读写冲突时会有 backoff 自动重试机制,如上述示例中 Txn1 会进行 backoff 重试,单次初始 100 ms,单次最大 3000 ms,总共最大 20000 ms

写写冲突

监控:

KV Errors下Lock Resolve OPS面板resolve

KV Backoff OPS 面板txnLock

会有比较明显的上升趋势,其中 resolve 是指尝试清锁的操作,txnLock 代表出现了写冲突。

 

KeyIsLocked错误

事务在 Prewrite 阶段的第一步就会检查是否有写写冲突,第二步会检查目标 key 是否已经被另一个事务上锁。当检测到该 key 被 lock 后,会在 TiKV 端报出 KeyIsLocked。

与读写冲突一样,在出现 KeyIsLocked 时,后台会自动进行 backoff 重试。

 

处理建议:

监控中出现少量 txnLock,无需过多关注。后台会自动进行 backoff 重试,单次初始 200 ms,单次最大 3000 ms。

如果出现大量的 txnLock,需要从业务的角度评估下冲突的原因。

使用悲观锁模式。

Commit 阶段

Prewrite 全部完成时,客户端便会取得 commit_ts,然后继续2PC的第二阶段。

由于 primary key 是否提交成功标志着整个事务是否提交成功,因而客户端需要在单独 commit primary key 之后再继续 commit 其余的 key。

悲观锁也有一个 TTL 的时间

txn heartbeat 会自动的更新事务的 TTL,以确保第二个事务不会将第一个事务的锁清掉。

其他锁相关错误

(1)锁被清除 (LockNotFound) 错误TxnLockNotFound

原因:事务提交的慢了,超过了 TTL 的时间。当要提交时,发现被其他事务给 Rollback 掉了。

在开启 TiDB 自动重试事务的情况下,会自动在后台进行事务重试(注意显示和隐式事务的差别)。

3.0.8 ~ 4.0.5 版本,若不开启显示事务,默认为乐观锁

 

报错:

TiDB:

[WARN] [session.go:446] ["commit failed"] [conn=149370] ["finished txn"="Txn{state=invalid}"] [error="[kv:6]Error: KV error safe to retry tikv restarts txn: Txn(Mvcc(TxnLockNotFound{ start_ts: 412720515987275779, commit_ts: 412720519984971777, key: [116, 128, 0, 0, 0, 0, 1, 111, 16, 95, 114, 128, 0, 0, 0, 0, 0, 0, 2] })) [try again later]"]

 

TiKV:

Error: KV error safe to retry restarts txn: Txn(Mvcc(TxnLockNotFound)) [ERROR [Kv.rs:708] ["KvService::batch_raft send response fail"] [err=RemoteStoped]

 

处理建议:

A.通过检查 start_ts 和 commit_ts 之间的提交间隔,可以确认是否超过了默认的 TTL 的时间。

B.查看提交间隔:

./pd-ctl tso [start_ts]

./pd-ctl tso [commit_ts]

建议检查下是否是因为写入性能的缓慢导致事务提交的效率差,进而出现了锁被清除的情况。

C.在关闭 TiDB 事务重试的情况下,需要在应用端捕获异常,并进行重试。

不会有TxnLockNotFound问题

(1)pessimistic lock retry limit reached

在冲突严重的场景,或者当发生write conflict时,乐观事务会直接终止,而悲观事务会尝试用最新数据重试该语句直到没有 write conflict。

TiDB的加锁操作是一个写入操作,且操作过程是先读后写,需要 2 次 RPC。如果在这中间发生了 write conflict,那么会重试。每次重试都会打印日志,不用特别关注。重试次数由 pessimistic-txn.max-retry-count 定义。

 

悲观事务模式下,如果发生 write conflict,并且重试的次数达到了上限,那么在 TiDB 的日志中会出现含有下述关键字的报错信息:

err="pessimistic lock retry limit reached"

 

处理建议:

如果上述报错出现的比较频繁,建议从业务的角度进行调整。

 

(2)Lock wait timeout exceeded

在悲观锁模式下,事务之间出现会等锁的情况。等锁的超时时间由 TiDB 的 innodb_lock_wait_timeout 参数来定义,这个是 SQL 语句层面的最大允许等锁时间,即一个 SQL 语句期望加锁,但锁一直获取不到,超过这个时间,TiDB 不会再尝试加锁,会向客户端返回相应的报错信息。

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

 

 

(3)TTL manager has timed out

除了有不能超出 GC 时间的限制外,悲观锁的 TTL 有上限,默认为 10 分钟,所以执行时间超过 10 分钟的悲观事务有可能提交失败。这个超时时间由 TiDB 参数 performance.max-txn-ttl 指定:

TTL manager has timed out, pessimistic locks may expire, please commit or rollback this transaction

 

处理建议:

当遇到该报错时,建议确认下业务逻辑是否可以进行优化,如将大事务拆分为小事务。在未使用大事务的前提下,大事务可能会触发 TiDB 的事务限制。

可适当调整相关参数,使其符合事务要求。

 

(4)Deadlock found when trying to get lock





文章推荐

TiDB调研手记4 -- TiDB的ANALYZE TABLE

TiDB调研手记3 -- TiDB集群中PD节点缓慢

TiDB调研手记2 -- 锁冲突


扫描右方二维码                关注猿的话


文章转载自猿的话,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论