TIdb读写表冲突问题

【 TiDB 使用环境】生产环境
【 TiDB 版本】 6.5.2
【复现路径】做过哪些操作出现的问题
逻辑代码:如下:
//多线程批量插入
insert into customer_reach_standard_record valuse(…);

//多线程读取统计:
select count(*) from customer_reach_standard_record ;

或者查询等:
SELECT t1.customer_no FROM customer_reach_standard_record t1 where t1.customer_no = ? AND t1.standard_type = ? limit 1

【遇到的问题:业务侧Lock异常
应用侧错误日志:

The error may exist in class path resource [mapper/CustomerReachStandardRecordMapper.xml]

The error may involve defaultParameterMap

The error occurred while setting parameters

SQL: SELECT t1.customer_no FROM customer_reach_standard_record t1 where t1.customer_no = ? AND t1.standard_type = ? limit 1

Cause: java.sql.SQLException: other error: key is locked (backoff or cleanup) primary_lock: 74800000000096F1C55F69800000000000000103800000000003163F039721E34F1700B002 lock_version: 442039310006091794 key: 74800000000096F1C55F729721E34F1700B002 lock_ttl: 3000 txn_size: 5 use_async_commit: true min_commit_ts: 442039310006091795

; uncategorized SQLException; SQL state [HY000]; error code [1105]; other error: key is locked (backoff or cleanup) primary_lock: 74800000000096F1C55F69800000000000000103800000000003163F039721E34F1700B002 lock_version: 442039310006091794 key: 74800000000096F1C55F729721E34F1700B002 lock_ttl: 3000 txn_size: 5 use_async_commit: true min_commit_ts: 442039310006091795; nested exception is java.sql.SQLException: other error: key is locked (backoff or cleanup) primary_lock: 74800000000096F1C55F69800000000000000103800000000003163F039721E34F1700B002 lock_version: 442039310006091794 key: 74800000000096F1C55F729721E34F1700B002 lock_ttl: 3000 txn_size: 5 use_async_commit: true min_commit_ts: 442039310006091795
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:89)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:88)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:440)
at com.sun.proxy.$Proxy198.selectOne(Unknown Source)
at org.mybatis.spring.SqlSessionTemplate.selectOne(SqlSessionTemplate.java:159)
at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.execute(MybatisMapperMethod.java:90)
at com.baomidou.mybatisplus.core.override.MybatisMapperProxy$PlainMethodInvoker.invoke(MybatisMapperProxy.java:148)
at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:89)
at com.sun.proxy.$Proxy224.getByCustomerAndType(Unknown Source)
at com.yss.reward.service.impl.CustomerReachStandardRecordServiceImpl.lambda$checkCustomerBatch$0(CustomerReachStandardRecordServiceImpl.java:113)
at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1590)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.sql.SQLException: other error: key is locked (backoff or cleanup) primary_lock: 74800000000096F1C55F69800000000000000103800000000003163F039721E34F1700B002 lock_version: 442039310006091794 key: 74800000000096F1C55F729721E34F1700B002 lock_ttl: 3000 txn_size: 5 use_async_commit: true min_commit_ts: 442039310006091795
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:965)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3978)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3914)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2530)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2683)
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2495)
at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1903)
at com.mysql.jdbc.PreparedStatement.execute$original$BR1eDmhd(PreparedStatement.java:1242)
at com.mysql.jdbc.PreparedStatement.execute$original$BR1eDmhd$accessor$r4nNE4ok(PreparedStatement.java)
at com.mysql.jdbc.PreparedStatement$auxiliary$kUNBnNaX.call(Unknown Source)

你在事务里面用查询就会这样
不要用select *for update;

没看懂,你这些SQL不可能都是在一个事务里面的吧?

只读或者写有没有问题?

你是不是用RR隔离级别,你改成RC看看

拿不到锁

事务的隔离级别是是RC级别

可以理解为多个不同的人操作
任务A: 执行批量操作,插入数据,在插入之前检测是否有重复插入的情况
任务B: 用户在页面查询批量插入的数据,主要是分页查询。

没有使用select for update;

读默认应该不会加锁,除非加了for update,现在锁应该和读没关系吧,关闭读事务会不会还报错。官方文档看,tidb如果没有for update是快照读读,看不到事务来时候的update数据

请查看

TiDB 锁冲突问题处理 | PingCAP 文档中心

先查查是事务模式是悲观锁模式还是乐观锁模式,不过这两种情况下都是有可能触发读写冲突的,版本 6.5.2 默认应该是悲观锁模式。
官方文档说是

处理乐观锁冲突问题

以下介绍乐观事务模式下常见的锁冲突问题的处理方式。

读写冲突

在 TiDB 中,读取数据时,会获取一个包含当前物理时间且全局唯一递增的时间戳作为当前事务的 start_ts。事务在读取时,需要读到目标 key 的 commit_ts 小于这个事务的 start_ts 的最新的数据版本。当读取时发现目标 key 上存在 lock 时,因为无法知道上锁的那个事务是在 Commit 阶段还是 Prewrite 阶段,所以就会出现读写冲突的情况,如下图:

分析:

Txn0 完成了 Prewrite,在 Commit 的过程中 Txn1 对该 key 发起了读请求,Txn1 需要读取 start_ts > commit_ts 最近的 key 的版本。此时,Txn1 的 start_ts > Txn0 的 lock_ts,需要读取的 key 上的锁信息仍未清理,故无法判断 Txn0 是否提交成功,因此 Txn1 与 Txn0 出现读写冲突。

你可以通过如下两种途径来检测当前环境中是否存在读写冲突:

  1. TiDB 监控及日志
  • 通过 TiDB Grafana 监控分析:观察 KV Errors 下 Lock Resolve OPS 面板中的 not_expired/resolve 监控项以及 KV Backoff OPS 面板中的 tikvLockFast 监控项,如果有较为明显的上升趋势,那么可能是当前的环境中出现了大量的读写冲突。其中,not_expired 是指对应的锁还没有超时,resolve 是指尝试清锁的操作,tikvLockFast 代表出现了读写冲突。
  • 通过 TiDB 日志分析:在 TiDB 的日志中可以看到下列信息:
[INFO] [coprocessor.go:743] ["[TIME_COP_PROCESS] resp_time:406.038899ms txnStartTS:416643508703592451 region_id:8297 store_addr:10.8.1.208:20160 backoff_ms:255 backoff_types:[txnLockFast,txnLockFast] kv_process_ms:333 scan_total_write:0 scan_processed_write:0 scan_total_data:0 scan_processed_data:0 scan_total_lock:0 scan_processed_lock:0"]
* txnStartTS:发起读请求的事务的 start_ts,如上面示例中的 416643508703592451
* backoff_types:读写发生了冲突,并且读请求进行了 backoff 重试,重试的类型为 txnLockFast
* backoff_ms:读请求 backoff 重试的耗时,单位为 ms,如上面示例中的 255
* region_id:读请求访问的目标 region 的 id
  1. 通过 TiKV 日志分析:在 TiKV 的日志可以看到下列信息:
[ERROR] [endpoint.rs:454] [error-response] [err=""locked primary_lock:7480000000000004D35F6980000000000000010380000000004C788E0380000000004C0748 lock_version: 411402933858205712 key: 7480000000000004D35F7280000000004C0748 lock_ttl: 3008 txn_size: 1""]

这段报错信息表示出现了读写冲突,当读数据时发现 key 有锁阻碍读,这个锁包括未提交的乐观锁和未提交的 prewrite 后的悲观锁。

  • primary_lock:锁对应事务的 primary lock。
  • lock_version:锁对应事务的 start_ts。
  • key:表示被锁的 key。
  • lock_ttl: 锁的 TTL。
  • txn_size:锁所在事务在其 Region 的 key 数量,指导清锁方式。

处理建议:

  • 在遇到读写冲突时会有 backoff 自动重试机制,如上述示例中 Txn1 会进行 backoff 重试,单次初始 10 ms,单次最大 3000 ms,总共最大 20000 ms
  • 可以使用 TiDB Control 的子命令 decoder 来查看指定 key 对应的行的 table id 以及 rowid:
./tidb-ctl decoder "t\x00\x00\x00\x00\x00\x00\x00\x1c_r\x00\x00\x00\x00\x00\x00\x00\xfa"
format: table_row
table_id: -9223372036854775780
row_id: -9223372036854775558

虽然可以理解,但是感觉官方文档的意思确实承认会出现读写冲突,但是却没有给出解决方案。

1 个赞

show variables like ‘tidb_txn_mode’;
可以查看是悲观模式还是乐观模式

你改成RC就不会有读写冲突了

事务的隔离级别是RC级别

目前是悲观模式,但是感觉没有给出解决方案。无法解决这种问题,只能应用侧做处理

官方提供了重试机制进行处理。
但楼主的问题是count(*)和order by 的,都是对全表进行扫描,出现读写锁冲突的概率太大了。
只能从应用侧入手。

已经在应用侧通过结合Redis来实现幂等实现,规避该问题。

1 个赞

查看是哪种锁

此话题已在最后回复的 60 天后被自动关闭。不再允许新回复。