本文介绍行锁问题的排查思路,并提供部分排查案例。
适用版本
OceanBase 数据库所有版本
锁冲突问题的排查思路
在业务环境中,可以将锁抽象为两个与行锁有密切关系的对象,即行锁的持有者与等待者。而行锁的持有者与等待者都是事务的一部分,因此在监控活跃事务中监控锁的持有者与等待者会对锁冲突的问题排查提供很大的帮助。
OceanBase 数据库提供了以下虚拟表,分别用于监控活跃事务、行锁持有者与行锁等待者。
__all_virtual_trans_stat:用于监控活跃事务。各主要表列的说明如下表所示。
列名 说明 svr_ip 表示创建该事务上下文的 OBServer 的 IP 地址。 session_id 表示该事务上下文所属会话的唯一 ID。 proxy_id 表示客户端 OBProxy/Java Client 对应的 IP 地址与端口号。 trans_id 表示该事务的唯一 ID。 is_existing 表示当前事务上下文是否正在退出。 partition 表示当前事务上下文在哪个分区上创建。 participants 当前事务的参与者列表。 ctx_create_time 事务上下文的创建时间。 ref 表示事务上下文当前的引用计数。 sql_no 表示当前事务上下文上最后一次执行的 SQL 的 sql_no。state 表示事务上下文当前的状态。 可能的状态有 INIT、PREPARE、COMMIT、ABORT与CLEAR。part_trans_action 表示该事务上下文上最后执行的动作。 可能的状态有 START_TASK、END_TASK、COMMIT。lock_for_read_entry_count 表示该事务在表扫描过程中遇到锁冲突重试的次数。 __all_virtual_trans_lock_stat:记录了当前集群中所有活跃事务持有行锁的相关信息。各主要表列的说明如下表所示。
列名 说明 rowkey 表示持有锁的行的 rowkey。 session_id 持有锁的事务所属的会话唯一 ID。 proxy_id 表示持有锁的事务所属客户端 OBProxy/Java Client 对应的 IP 地址与端口号。 trans_id 表示持有锁的事务的唯一 ID。 __all_virtual_lock_wait_stat:统计了当前集群中所有等待行锁的请求或语句的相关信息。各主要表列的说明如下表所示。
列名 说明 session_id 等待锁的事务所属的会话唯一 ID。 proxy_id 表示等待锁的事务所属客户端 OBProxy/Java Client 对应的 IP 地址与端口号。 trans_id 表示等待锁的事务的唯一 ID。 lock_ts 表示该请求开始等待锁的时间点。 abs_timeout 表示等待锁的语句的绝对超时时间。 try_lock_times 表示等待锁的语句重试加锁的次数。 block_session_id 表示在该行第一个等待事务的 session_id。
说明
等待锁分为两种情况,即写写冲突与读写冲突。对于读写冲突的情况,是由于事务中读语句的 read_snapshot_version 大于写语句的 prepare_version,但 commit_version 不能确定,因此需要等待写事务完成后才能读到写语句的修改。
_all_virtual_lock_wait_stat 表中主要用于展示写写冲突的情况,对于读写冲突,仅会使用 _all_virtual_trans_stat 中的 part_trans_action 与 lock_for_read_retry_count 字段来表示。如果part_trans_action 的值为 START_TASK,且 lock_for_read_retry_count 的值非零,则可以判断是发生了读写冲突。
锁冲突问题的排查案例
场景一
业务使用了较大的超时事件,且存在一个会话中的未知长事务持有锁,阻塞了其他事务的执行,需要找到并停止该长事务。
方案一:通过无法加锁的事务查询持有锁的事务。
通过无法加锁的事务的
session_id,找到等待锁的行的rowkey。可以发现等待锁的事务在等待主键为
pk的行。obclient> select * from __all_virtual_lock_wait_stat where session_id = 3221580756\G *************************** 1. row *************************** svr_ip: xxx.xxx.xx.xxx svr_port: xxxx table_id: 1101710651081554 rowkey: table_id=1101710651081554 hash=779dd9b202397d7 rowkey_object=[{"VARCHAR":"pk", collation:"utf8mb4_general_ci"}] addr: 140433355180784 need_wait: 1 recv_ts: 1600440077959302 lock_ts: 1600440077960167 abs_timeout: 1600450077859302 try_lock_times: 1 time_after_recv: 1307610861 session_id: 3221580756 block_session_id: 3221580756 type: 0 lock_mode: 0 1 row in set (0.01 sec)通过主键找到对应持有该行行锁的事务的
session_id。发现持有行锁的事务的
session_id为3221577520。obclient> select * from __all_virtual_trans_lock_stat where rowkey like '%pk%'\G *************************** 1. row *************************** tenant_id: 1002 trans_id: {hash:6605492148156030705, inc:3284929, addr:"xxx.xxx.xx.xxx:xxxx", t:1600440036535233} svr_ip: xxx.xxx.xx.xxx svr_port: xxxx partition: {tid:1101710651081554, partition_id:0, part_cnt:0} table_id: 1101710651081554 rowkey: table_id=1101710651081554 hash=779dd9b202397d7 rowkey_object=[{"VARCHAR":"pk", collation:"utf8mb4_general_ci"}] session_id: 3221577520 proxy_id: NULL ctx_create_time: 2020-09-18 22:41:03.583285 expired_time: 2020-09-19 01:27:16.534919 1 row in set (0.05 sec)根据查到的
session_id停止该事务的会话。obclient> KILL 3221577520
方案二:通过查询长事务。
根据事务执行时间,找到执行时间最长且未结束事务的
trans_id。obclient> select * from __all_virtual_trans_lock_stat order by ctx_create_time limit 5\G *************************** 1. row *************************** tenant_id: 1002 trans_id: {hash:6605492148156030705, inc:3284929, addr:"xxx.xxx.xx.xxx:xxxx", t:1600440036535233} svr_ip: xxx.xxx.xx.xxx svr_port: xxxx partition: {tid:1101710651081554, partition_id:0, part_cnt:0} table_id: 1101710651081554 rowkey: table_id=1101710651081554 hash=779dd9b202397d7 rowkey_object=[{"VARCHAR":"pk", collation:"utf8mb4_general_ci"}] session_id: 3221577520 proxy_id: NULL ctx_create_time: 2020-09-18 22:41:03.583285 expired_time: 2020-09-19 01:27:16.534919 1 row in set (0.05 sec)通过事务的
trans_id找到其所持有的所有锁,并根据rowkey明确哪一个是需要停止的服务。下例中,第一行的
rowkey与上面查询的rowkey相同,因此该事务即是持有锁的事务。obclient> select * from __all_virtual_trans_lock_stat where trans_id like '%hash:6605492148156030705, inc:3284929%'\G *************************** 1. row *************************** tenant_id: 1002 trans_id: {hash:6605492148156030705, inc:3284929, addr:"xxx.xxx.xx.xxx:xxxx", t:1600440036535233} svr_ip: xxx.xxx.xx.xxx svr_port: xxxx partition: {tid:1101710651081554, partition_id:0, part_cnt:0} table_id: 1101710651081554 rowkey: table_id=1101710651081554 hash=779dd9b202397d7 rowkey_object=[{"VARCHAR":"pk", collation:"utf8mb4_general_ci"}] session_id: 3221577520 proxy_id: NULL ctx_create_time: 2020-09-18 22:41:03.583285 expired_time: 2020-09-19 01:27:16.534919 *************************** 2. row *************************** tenant_id: 1002 trans_id: {hash:6605492148156030705, inc:3284929, addr:"xxx.xxx.xx.xxx:xxxx", t:1600440036535233} svr_ip: xxx.xxx.xx.xxx svr_port: xxxx partition: {tid:1101710651081554, partition_id:0, part_cnt:0} table_id: 1101710651081554 rowkey: table_id=1101710651081554 hash=89413aecf767cd7 rowkey_object=[{"VARCHAR":"ob", collation:"utf8mb4_general_ci"}] session_id: 3221577520 proxy_id: NULL ctx_create_time: 2020-09-18 22:41:03.583285 expired_time: 2020-09-19 01:27:16.534919 2 rows in set (0.05 sec)确认要停止的事务的
session_id后,停止对应事务的会话。obclient> KILL 3221577520; Query OK, 0 rows affected (0.00 sec)
场景二
业务反馈某一行上执行的事务总超时,已知该行的 rowkey,本场景以 rowkey 包含字符串 test 为例。
根据
rowkey查询__all_virtual_trans_lock_stat表找到对应的持锁事务。obclient> select * from __all_virtual_trans_lock_stat where memtable_key like '%test%'\G; *************************** 1. row *************************** tenant_id: 1 trans_id: {hash:6124095709354809361, inc:249476, addr:"xxx.xxx.xx.xxx:xxxx", t:1529075759177984} svr_ip: xxx.xxx.xx.xxx svr_port: xxxx partition: {tid:1099511677778, partition_id:0, part_cnt:0} memtable_key: {table_id:1099511677778, hash_val:7893135555906369137, buf:"table_id=1099511677778 hash=3fb183d083d6d9f1 rowkey_object=[{"VARCHAR":"test", collation:"utf8mb4_general_ci"}] "} session_id: 2147549190 proxy_id: NULL ctx_create_time: 2018-06-15 23:16:35.695821 expired_time: 2018-06-15 23:32:39.177533 1 row in set (0.03 sec)根据上述结果,查询活跃事务虚拟表
__all_virtual_trans_stat找到对应的事务的trans_id等信息。OceanBase (root@oceanbase)> select * from __all_virtual_trans_stat where session_id= 2147549190 \G; *************************** 1. row *************************** tenant_id: 1 svr_ip: xxx.xxx.xx.xxx svr_port: xxxx inc_num: 249476 session_id: 2147549190 proxy_id: NULL trans_type: 0 trans_id: {hash:6124095709354809361, inc:249476, addr:"xxx.xxx.xx.xxx:xxxx", t:1529075759177984} is_exiting: 0 is_readonly: 0 is_decided: 0 active_memstore_version: 0-0-0 partition: {tid:1099511677778, partition_id:0, part_cnt:0} participants: [{tid:1099511677778, partition_id:0, part_cnt:0}] autocommit: 0 trans_consistency: 0 ctx_create_time: 2018-06-15 23:16:35.695821 expired_time: 2018-06-15 23:32:39.177533 refer: 1073741826 sql_no: 1 state: 0 part_trans_action: 2 lock_for_read_retry_count: 1 1 row in set (0.01 sec)确认要停止的事务的
session_id后,停止对应事务的会话。obclient> KILL 2147549190; Query OK, 0 rows affected (0.00 sec)