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

本文涉及的产品
云原生数据库 PolarDB PostgreSQL 版,标准版 2核4GB 50GB
云原生数据库 PolarDB MySQL 版,通用型 2核4GB 50GB
简介: postgresql 高并发 数据库

背景
两个会话,同时对同一个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数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
目录
相关文章
|
关系型数据库 PostgreSQL
postgresql insert into插入记录时使用select子查询
postgresql insert into插入记录时使用select子查询
135 0
|
关系型数据库 PostgreSQL
PostgreSQL INSERT INTO 语句
PostgreSQL INSERT INTO 语句
373 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
8648 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分区表 用法举例
3125 0
|
SQL 关系型数据库 测试技术
PostgreSQL 流式统计 - insert on conflict 实现 流式 UV(distinct), min, max, avg, sum, count ...
标签 PostgreSQL , 流式统计 , insert on conflict , count , avg , min , max , sum 背景 流式统计count, avg, min, max, sum等是一个比较有意思的场景,可用于实时大屏,实时绘制统计图表。
5476 0
|
Web App开发 关系型数据库 PostgreSQL
|
Web App开发 SQL 关系型数据库
Locking issue with concurrent DELETE / INSERT in PostgreSQL - 解法 advisory lock
标签 PostgreSQL , 原子性 , 并行锁同行 , advisory lock 背景 两个会话,同时对同一个ID值的记录执行删除后插入,有一个会话会出现删除0,插入失败的现象。 现象 CREATE TABLE test (id INT PRIMARY KEY); INSER...
877 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里面会填充更新事务号。
2163 0