Locking issue with concurrent DELETE / INSERT in PostgreSQL - 解法 advisory lock

本文涉及的产品
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: 标签PostgreSQL , 原子性 , 并行锁同行 , advisory lock背景两个会话,同时对同一个ID值的记录执行删除后插入,有一个会话会出现删除0,插入失败的现象。现象CREATE TABLE test (id INT PRIMARY KEY); INSER...

标签

PostgreSQL , 原子性 , 并行锁同行 , advisory lock


背景

两个会话,同时对同一个ID值的记录执行删除后插入,有一个会话会出现删除0,插入失败的现象。

现象

CREATE TABLE test (id INT PRIMARY KEY);  
  
INSERT INTO TEST VALUES (1);  
INSERT INTO TEST VALUES (2);  

执行如下几条SQL

begin;  
1:DELETE FROM test WHERE id=1;  
2:INSERT INTO test VALUES (1);  
3:COMMIT;  

现象

S1-1 runs (1 row deleted)  
S2-1 runs (but is blocked since S1 has a write lock)  
S1-2 runs (1 row inserted)  
S1-3 runs, releasing the write lock  
S2-1 runs, now that it can get the lock. But reports 0 rows deleted. HUH???  
S2-2 runs, reports a unique key constraint violation  

用户期望

S1-1 runs (1 row deleted)  
S2-1 runs (but is blocked since S1 has a write lock)    
S1-2 runs (1 row inserted)  
S1-3 runs, releasing the write lock  
S2-1 runs, now that it can get the lock. But reports 0 rows deleted. HUH???  用户期望这里的S1新插入的记录可以被删除  
S2-2 runs, reports a unique key constraint violation  不报错  

原因分析

行级锁。

S1-1 runs (1 row deleted)  
S2-1 runs (but is blocked since S1 has a write lock)   锁冲突,因为这条记录(ROWID)已经被S1锁住了  
S1-2 runs (1 row inserted)    
S1-3 runs, releasing the write lock  
S2-1 runs, now that it can get the lock. But reports 0 rows deleted. HUH???  S1提交,S2请求的这个ROWID已经被删除。所以DELETE 0  
S2-2 runs, reports a unique key constraint violation  报错,因为S1已经插入了同一个ID   

deferable 约束无法解决这个问题

https://www.postgresql.org/docs/11/static/sql-createtable.html

[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]  

https://www.postgresql.org/docs/11/static/sql-set-constraints.html

SET CONSTRAINTS { ALL | name [, ...] } { DEFERRED | IMMEDIATE }  

例子

postgres=# create table t1(id int primary key deferrable) ;  
CREATE TABLE  
  
postgres=# insert into t1 values (1),(2);  
INSERT 0 2  
  
postgres=# begin;  
BEGIN  
postgres=# set constraints all deferred ;  
SET CONSTRAINTS  
postgres=# delete from t1 where id=1;  
DELETE 1  
postgres=# insert into t1 values (1);  
INSERT 0 1  
postgres=# end;  
COMMIT  
  
  
postgres=# begin;  
BEGIN  
postgres=# set constraints ALL deferred ;  
SET CONSTRAINTS  
postgres=# delete from t1 where id=1;  
DELETE 0  
postgres=# insert into t1 values (1); -- 不使用deferred,这里就直接报错  
INSERT 0 1  
使用deferred,现象不一样的地方,在事务结束时判断约束。  
postgres=# end;  
ERROR:  duplicate key value violates unique constraint "t1_pkey"  
DETAIL:  Key (id)=(1) already exists.  

推荐解决方案

adlock

《advisory lock 实现高并发非堵塞式 业务锁》

《PostgreSQL 使用advisory lock实现行级读写堵塞》

《PostgreSQL 无缝自增ID的实现 - by advisory lock》

《PostgreSQL 使用advisory lock或skip locked消除行锁冲突, 提高几十倍并发更新效率》

《PostgreSQL 秒杀场景优化》

例子

S1,对ID=1进行操作

postgres=# begin;  
BEGIN  
postgres=# select pg_try_advisory_xact_lock(1);  
 pg_try_advisory_xact_lock   
----------------------  
 t  
(1 row)  
业务上判断,返回TRUE后,继续下面的请求  
  
postgres=# delete from t1 where id=1;  
DELETE 1  
postgres=# insert into t1 values (1);  
INSERT 0 1  
postgres=# end;  
COMMIT  
事务结束自动释放pg_try_advisory_xact_lock  

S2,对ID=1进行操作

postgres=# begin;  
BEGIN  
postgres=# select pg_try_advisory_xact_lock(1);  
 pg_try_advisory_xact_lock   
----------------------  
 f  
(1 row)  
返回FALSE,业务上不断重试,pg_try_advisory_xact_lock(1)   
......  
直到S1提交,返回TRUE后,继续下面的SQL请求  
  
  
postgres=# delete from t1 where id=1;  
DELETE 1  
postgres=# insert into t1 values (1);  
INSERT 0 1  
postgres=# end;  
COMMIT  

注意事项

adlock的id是库级冲突,例如lock(1),在同一个数据库再lock(1)就会冲突。

因此,如果在同一个库里面,期望对不同的表里面的数据使用同样的adlock手段,建议不同的表错开ID段,或者使用全局ID。

小结

adlock为轻量级锁,在本文提到的业务场景中,逻辑上把两个事务中的SQL做成了串行执行。达到的效果满足业务上的需求。

参考

https://dba.stackexchange.com/questions/27688/locking-issue-with-concurrent-delete-insert-in-postgresql

https://www.postgresql.org/docs/11/static/sql-createtable.html

https://www.postgresql.org/docs/11/static/sql-set-constraints.html

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
5月前
|
关系型数据库 PostgreSQL
postgresql insert into插入记录时使用select子查询
postgresql insert into插入记录时使用select子查询
|
关系型数据库 PostgreSQL
PostgreSQL INSERT INTO 语句
PostgreSQL INSERT INTO 语句
276 0
|
SQL 弹性计算 关系型数据库
PostgreSQL 大宽表,全列索引,高并发合并写入(insert into on conflict, upsert, merge insert) - 实时adhoc query
标签 PostgreSQL , 全列索引 , 大宽表 , 写测试 , insert on conflict , upsert , merge insert , adhoc query 背景 OLAP系统中,adhoc query非常场景(任意维度查询分析)。 adhoc query,通常来说,可以加GIN倒排,或者每一列都加一个索引来实现。 《PostgreSQL 设计优化case
8415 0
|
弹性计算 关系型数据库 测试技术
PostgreSQL 分区表如何支持多列唯一约束 - 枚举、hash哈希 分区, 多列唯一, insert into on conflict, update, upsert, merge insert
标签 PostgreSQL , 分区表 , native partition , 唯一 , 非分区键唯一 , 组合唯一 , insert into on conflict , upsert , merge insert 背景 PG 11开始支持HASH分区,10的分区如果要支持hash分区,可以通过枚举绕道实现。 《PostgreSQL 9.x, 10, 11 hash分区表 用法举例
2898 0
|
关系型数据库 PostgreSQL
PostgreSQL merge insert(upsert/insert into on conflict) 如何区分数据是INSERT还是UPDATE
标签 PostgreSQL , merge insert , upsert , insert into on conflict , 区分 insert update , xmin , xmax 背景 使用insert into on conflict update语法,可以支持UPSERT的功能,但是到底这条SQL是插入的还是更新的呢?如何判断 通过xmax字段的值是否不为0,可以判断,如果是UPDATE,XMAX里面会填充更新事务号。
2045 0
|
关系型数据库 PostgreSQL 测试技术
|
SQL 关系型数据库 PostgreSQL

相关产品

  • 云原生数据库 PolarDB