背景

客户现场执行压测时候,发生周期性的TPS大幅下降,通过查看kwr报告发现DBcpu时间占DBtime时间很少,百分之90的DBtime花费在tuple锁等待上,等待事件类型是lock。

等待时间最多的语句是select fd_id,ctid,xmin from ... for update

含义

select for update语句目的在查询时,避免其他用户对该表进行dml等操作,造成表的不一致性。

select for update语句需要等待表锁,以及表对应行锁释放之后才能返回查询结果。

表级锁模式

常见锁模式以及应用场景:

ACCESS SHARE:select操作获取该模式锁资源,通常情况下所有只读取不修改表的查询都会获取该模式锁资源

ROW SHARE: select for update 和 select for share 命令获取该模式锁资源

ROW EXCLUSIVE: DML操作通过会获取该模式锁资源,通常情况下任何需要修改表数据的操作都会持有该模式锁资源

SHARE UPDATE EXCLUSIVE:对于 vacuum(非full)、create index concurrnetly、reindex concurrently、create statistics、alter index相关、alter table相关操作会持有该模式锁资源,该模式锁资源主要是为了范围并发对同一张表DDL变更以及vacuum操作

SHARE: create index (非concurrently)操作会持有该模式锁资源,该模式锁资源可阻止并发对表的创建索引操作

SHARE ROW EXCLUSIVE: cerate trigger、一些alter table操作会持有该模式锁资源

EXCLUSIVE: refresh materialized view concurrently操作会持有该模式锁资源

ACCESS EXCLUSIVE:lock table模式锁定模式,drop table、truncate、reindex、 cluster、vacuum full、refresh materialized view (without concurrently) 还有一些alter table、alter index操作需要获取该模式锁资源,该模式锁资源保证了持有者唯一访问表

测试

测试环境隔离级别是 read committed,数据库版本是KingbaseESV8R6C7

1.update语句阻塞select for update语句

开启一个会话,不提交
begin;
update t3 set name='qite' where id=1; 开启另一个会话执行,发现被阻塞
select id,name from t3 for update; 在另一个会话查看锁情况。
查看表t3有关的锁,这里表t3持有了tuple上的AccessExclusiveLock锁,这是最高级别的锁,阻塞一切sql语句。
pid4203持有的锁row share表示正在执行select for update语句,AccessExclusiveLock最高级别锁也是pid4203需要获取的。
pid4205持有的RowExclusiveLock锁表示执行dml语句需要获取的锁,这里可以看出pid4205会话很可能执行了update语句。
SELECT
pg_locks.pid,
a.datname,
locktype,
virtualtransaction,
transactionid,
nspname,
relname,
mode,
granted,
cast(date_trunc('second',query_start) AS timestamp) AS query_start
FROM
pg_locks
LEFT OUTER JOIN pg_class ON (pg_locks.relation = pg_class.oid)
LEFT OUTER JOIN pg_namespace ON (pg_namespace.oid = pg_class.relnamespace),
pg_stat_activity a
WHERE NOT pg_locks.pid = pg_backend_pid()
AND pg_locks.pid=a.pid
and pg_class.relname='t3';
pid | datname | locktype | virtualtransaction | transactionid | nspname | relname | mode | granted | query_start
------+---------+----------+--------------------+---------------+---------+---------+---------------------+---------+---------------------
4203 | test | tuple | 4/43 | | public | t3 | AccessExclusiveLock | t | 2023-05-16 15:39:11
4203 | test | relation | 4/43 | | public | t3 | RowShareLock | t | 2023-05-16 15:39:11
4205 | test | relation | 5/10 | | public | t3 | RowExclusiveLock | t | 2023-05-16 15:39:08
4205 | test | relation | 5/10 | | public | t3 | AccessShareLock | t | 2023-05-16 15:39:08
(4 rows) 执行另一个更直观的sql语句查看锁情况,开启第三个会话执行如下sql。
SELECT blocked_locks.pid AS blocked_pid,
blocked_activity.usename AS blocked_user,
blocking_locks.pid AS blocking_pid,
blocking_activity.usename AS blocking_user,
blocked_activity.query AS blocked_statement,
blocking_activity.query AS current_statement_in_blocking_process
FROM sys_catalog.sys_locks blocked_locks
JOIN sys_catalog.sys_stat_activity blocked_activity ON blocked_activity.pid = blocked_locks.pid
JOIN sys_catalog.sys_locks blocking_locks
ON blocking_locks.locktype = blocked_locks.locktype
AND blocking_locks.DATABASE IS NOT DISTINCT FROM blocked_locks.DATABASE
AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation
AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page
AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple
AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid
AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid
AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid
AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid
AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid
AND blocking_locks.pid != blocked_locks.pid
JOIN sys_catalog.sys_stat_activity blocking_activity ON blocking_activity.pid = blocking_locks.pid
WHERE NOT blocked_locks.GRANTED;
blocked_pid | blocked_user | blocking_pid | blocking_user | blocked_statement | current_statement_in_blocking_process
-------------+--------------+--------------+---------------+------------------------------------+---------------------------------------
4203 | system | 4205 | system | select id,name from t3 for update; | update t3 set name='qite' where id=1;
(1 row) 在操作系统查看有关pid,4203pid中的select语句被阻塞,阻塞源头是第一个会话执行的update语句,pid是4205。
kingbas+ 4203 4154 0 15:27 ? 00:00:00 kingbase: system test [local] SELECT waiting
kingbas+ 4205 4154 0 15:27 ? 00:00:00 kingbase: system test [local] idle in transaction 第一个会话中的update语句commit或rollback后,事务结束后,第二个会话中的select for update语句可以执行成功,原因是blocking_pid被释放了。

2.select for update语句阻塞update语句

开启一个会话执行如下sql
begin;
select id,name from t3 for update; 开启另一个会话执行,发现被阻塞
update t3 set name='qitu' where id=1; 在另一个会话查看锁情况
pid4203对应RowExclusiveLock锁表示执行dml语句需要获取的锁,pid4205持有的RowShareLock锁说明此会话在执行select for update语句。
SELECT
pg_locks.pid,
a.datname,
locktype,
virtualtransaction,
transactionid,
nspname,
relname,
mode,
granted,
cast(date_trunc('second',query_start) AS timestamp) AS query_start
FROM
pg_locks
LEFT OUTER JOIN pg_class ON (pg_locks.relation = pg_class.oid)
LEFT OUTER JOIN pg_namespace ON (pg_namespace.oid = pg_class.relnamespace),
pg_stat_activity a
WHERE NOT pg_locks.pid = pg_backend_pid()
AND pg_locks.pid=a.pid
and pg_class.relname='t3';
pid | datname | locktype | virtualtransaction | transactionid | nspname | relname | mode | granted | query_start
------+---------+----------+--------------------+---------------+---------+---------+------------------+---------+---------------------
4203 | test | tuple | 4/44 | | public | t3 | ExclusiveLock | t | 2023-05-16 15:54:16
4203 | test | relation | 4/44 | | public | t3 | RowExclusiveLock | t | 2023-05-16 15:54:16
4203 | test | relation | 4/44 | | public | t3 | AccessShareLock | t | 2023-05-16 15:54:16
4205 | test | relation | 5/11 | | public | t3 | RowShareLock | t | 2023-05-16 15:54:00
(4 rows) 执行如下sql,果然阻塞源是pig4205执行的语句select for update。
SELECT blocked_locks.pid AS blocked_pid,
blocked_activity.usename AS blocked_user,
blocking_locks.pid AS blocking_pid,
blocking_activity.usename AS blocking_user,
blocked_activity.query AS blocked_statement,
blocking_activity.query AS current_statement_in_blocking_process
FROM sys_catalog.sys_locks blocked_locks
JOIN sys_catalog.sys_stat_activity blocked_activity ON blocked_activity.pid = blocked_locks.pid
JOIN sys_catalog.sys_locks blocking_locks
ON blocking_locks.locktype = blocked_locks.locktype
AND blocking_locks.DATABASE IS NOT DISTINCT FROM blocked_locks.DATABASE
AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation
AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page
AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple
AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid
AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid
AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid
AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid
AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid
AND blocking_locks.pid != blocked_locks.pid
JOIN sys_catalog.sys_stat_activity blocking_activity ON blocking_activity.pid = blocking_locks.pid
WHERE NOT blocked_locks.GRANTED;
blocked_pid | blocked_user | blocking_pid | blocking_user | blocked_statement | current_statement_in_blocking_process
-------------+--------------+--------------+---------------+---------------------------------------+---------------------------------------
4203 | system | 4205 | system | update t3 set name='qitu' where id=1; | select id,name from t3 for update;
(1 row) 操作系统查看对应pid情况,pid4203执行update语句被阻塞,阻塞源是pid4205。
kingbas+ 4203 4154 0 15:27 ? 00:00:00 kingbase: system test [local] UPDATE waiting
kingbas+ 4205 4154 0 15:27 ? 00:00:00 kingbase: system test [local] idle in transaction 解决方法是持有锁会话结束事务(提交或回滚),随后被阻塞的update语句执行成功,或杀掉持有锁的进程。

总结

select for update几种其他用法:

select * from t for update nowait 不等待行锁释放,提示锁冲突,不返回结果。

select * from t for update wait 5 等待5秒,若行锁仍未释放,则提示锁冲突,不返回结果。

select * from t for update skip locked 查询返回查询结果,但忽略有行锁的记录。

使用FOR UPDATE WAIT”子句有几个优点:

1.防止无限期地等待被锁定的行。

2.允许应用程序中对锁的等待时间进行控制。

3.在不确定sql执行前表是否被锁,wait关键字起到关键作用。

4.若使用了skip locked,则可以越过锁定的行,而不会报错。

此案例提供了数据库中锁阻塞的排查方法,如生产环境中遇到其他类型lock重型锁阻塞,也可用以上方法分析。

KingbaseES 中select for update语句引起的锁问题的更多相关文章

  1. 数据库中Select For update语句的解析

    ----------- Oracle -----------------– Oracle 的for update行锁 键字: oracle 的for update行锁 SELECT-FOR UPDAT ...

  2. KingbaseES 中select distinct on 语句

    用法 SELECT DISTINCT ON ( expression [, ...] ) 把记录根据[, -]的值进行分组,分组之后仅返回每一组的第一行. 需要注意的是,如果不指定ORDER BY子句 ...

  3. mysql 多列唯一索引在事务中select for update是不是行锁?

    在表中有这么一索引 UNIQUE KEY `customer_id` (`customer_id`,`item_id`,`ref_id`) 问1. 这种多列唯一索引在事务中select for upd ...

  4. Select For update语句浅析 (转)

    Select … for update语句是我们经常使用手工加锁语句.通常情况下,select语句是不会对数据加锁,妨碍影响其他的DML和DDL操作.同时,在多版本一致读机制的支持下,select语句 ...

  5. MySQL中select * for update锁表的范围

    MySQL中select * for update锁表的问题 由于InnoDB预设是Row-Level Lock,所以只有「明确」的指定主键,MySQL才会执行Row lock (只锁住被选取的资料例 ...

  6. MySQL中select * for update锁表的问题

    MySQL中select * for update锁表的问题 由于InnoDB预设是Row-Level Lock,所以只有「明确」的指定主键,MySQL才会执行Row lock (只锁住被选取的资料例 ...

  7. sql server中同时执行select和update语句死锁问题

    原始出处 http://oecpby.blog.51cto.com/2203338/457054 最近在项目中使用SqlServer的时候发现在高并发情况下,频繁更新和频繁查询引发死锁.通常我们知道如 ...

  8. 数据库:Mysql中“select ... for update”排他锁分析

    Mysql InnoDB 排他锁 用法: select … for update; 例如:select * from goods where id = 1 for update; 排他锁的申请前提:没 ...

  9. Select For update语句浅析

    Select -forupdate语句是我们经常使用手工加锁语句.通常情况下,select语句是不会对数据加锁,妨碍影响其他的DML和DDL操作.同时,在多版本一致读机制的支持下,select语句也不 ...

  10. Mysql中“select ... for update”排他锁(转)

    原帖地址 https://blog.csdn.net/claram/article/details/54023216 Mysql InnoDB 排他锁 用法: select … for update; ...

随机推荐

  1. Spring源码之-AOP

    目录 一.大话AOP 1.AOP的概念 2.必要的准备工作 什么是代理模式? 3.大话AOP 那么AOP 具体是什么呢? 实现AOP的方式 二.动态AOP自定义标签 1.JDK动态代理 2.CGLIB ...

  2. File.delete()和Files.delete(Path path)的区别

    文件删除时可以选择File.delete()和Files.delete(Path path),这两个方法到底有什么区别呢? //删除暂存的pdfFile file =new File(pdfFilen ...

  3. 硬件开发笔记(三):硬件开发基本流程,制作一个USB转RS232的模块(二):设计原理图库

    前言   上一篇了解了基本的过程,选型了相关的芯片,本篇描述原理图的设计过程,在原理图设计之前或者过程中需要不断新增原理图元器件.   Allegro.OrCad   Cadence公司针对PCB方面 ...

  4. AI 让观众成为 3D 版《老友记》的导演了?

    <老友记>上线 3D 版了? 允许用户旋转镜头,且从近景切换到全景观看故事? 今年出炉的 3D 方向 AI 项目 SitCom3D,能够自动补齐<老友记>原剧中的三维拍摄空间, ...

  5. 【Azure 应用服务】 在App Service中无法上传证书[Private Key Certificates (.pfx)],导入Azure Key Vault中的证书也无法成功

    问题描述 在App Service的TLS/SSL settings页面,切换到Private Key Certificates (.pfx),通过Import Key Vault Certifica ...

  6. 【Azure Function】Function App和Powershell 集成问题, 如何安装PowerShell的依赖模块

    问题描述 在Azure Function中创建一个PowerShell的函数后,其中使用了Get-AzMaintenanceUpdate,New-AzApplyUpdate 等指令,但是在执行时错误. ...

  7. 【Azure API 管理】APIM的容量指标(Capacity)数据异常高的情况记录

    问题描述 APIM从标准版降级到基础版,在没有用户使用的情况,Capacity的指标平均显示在80%以上. 这是什么异常情况呢? 问题分析 APIM的容量指标(Capacity)是 API 管理实例中 ...

  8. 隐私计算在释放万亿美元 SaaS 市场的重要作用

    PrimiHub一款由密码学专家团队打造的开源隐私计算平台,专注于分享数据安全.密码学.联邦学习.同态加密等隐私计算领域的技术和内容. 过去十年间数字领域经历了一场颠覆性的转变.这一演变的前沿是软件即 ...

  9. 3.1蓝桥杯每日知识点,全排列permutation

    next_permutation()函数 适用于生成当前序列的下一个排列 如果存在下一个排列,则将当前序列更改为下一个排列,并返回true 如果当前序列已经是最后一个排列,则将序列更改为第一个排列,并 ...

  10. Java 子类对象实例化的全过程

    2 /* 3 * 子类对象实例化的全过程 4 * 5 *1.结果上来看:(继承性) 6 * 子类继承父类以后,就获取了父类中声明的属性或方法 7 * 创建子类的对象,在堆空间中,就会加载所有父类声明的 ...