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

背景

通常在数据库中最小粒度的锁是行锁,当一个事务正在更新某条记录时,另一个事务如果要更新同一条记录(或者申请这一条记录的锁),则必须等待锁释放。

通常持锁的时间需要保持到事务结束,也就是说,如果一个长事务持有了某条记录的锁,其他会话要持有这条记录的锁,可能要等很久。

如果某张表的全表或者大部分记录要被更新的话,有几种做法。

1. 在一个事务中更新需要更新的记录,很显然时间可能很长,因为没有了并发。

2. 在多个事务中更新不同的记录,使用高并发来缩短更新的时间,但是就需要解决并发更新时存在的行锁冲突的问题。

本文将要给大家介绍两种解决并发更新行锁冲突问题的方法。

场景描述

测试表,单条记录越大,更新单条记录的时间越久(例如更新亿级别的超长BIT类型)。

每个人群都有一个唯一的ID,即parallel_update_test.id。

create unlogged table parallel_update_test(id int primary key, info int[]);

测试数据

insert into parallel_update_test select generate_series(1,10000), (select array_agg(id) from generate_series(1,100000) t(id));

postgres=# \dt+ parallel_update_test 
                            List of relations
 Schema |         Name         | Type  |  Owner   |  Size   | Description 
--------+----------------------+-------+----------+---------+-------------
 public | parallel_update_test | table | postgres | 3961 MB | 
(1 row)

更新需求,每条记录都有更新

例如我存储的数组是USERID,每条记录代表某个属性的人群数据,这个属性的人群数据不断的在变化,因此会不断的需要更新。

update parallel_update_test set info=array_append(info,1);

单个事务更新耗时80秒   
postgres=# begin;
postgres=# update parallel_update_test set info=array_append(info,1);
UPDATE 10000
Time: 80212.641 ms
postgres=# rollback;
ROLLBACK
Time: 0.131 ms
postgres=# vacuum parallel_update_test ;

使用并发的手段提高更新效率。

方法1 advisory lock

每个人群都有一个唯一的ID,即parallel_update_test.id。

所以只要保证并行的会话更新的是不同的ID对应的数据即可,同时需要避免单次重复更新。

如何避免更新同一个ID?

使用advisory lock可以避免并发更新同一条记录。

如何避免重复更新同一条记录。

使用扫描式的获取advisory lock,保证不会重复获取即可。

代码如下:

create or replace function update() returns void as $$
declare
  v_id int;
begin
  for v_id in select id from parallel_update_test  -- 扫描式
  loop  
    if pg_try_advisory_xact_lock(v_id) then -- 获取到ID的LOCK才会实施更新,否则继续扫描
      update parallel_update_test set info=array_append(info,1) where id=v_id;
    end if;
  end loop;
end;
$$ language plpgsql strict;

设计上尽量保证ID全局唯一,否则获取advisory lock的冲突可能性会增多。

测试,使用100个并行度

vi test.sql
select update();

pgbench -M prepared -n -r -f ./test.sql -c 100 -j 100 -t 1

并行更新耗时4秒

pgbench -M prepared -n -r -f ./test.sql -c 100 -j 100 -t 1
transaction type: ./test.sql
scaling factor: 1
query mode: prepared
number of clients: 100
number of threads: 100
number of transactions per client: 1
number of transactions actually processed: 100/100
latency average = 4407.490 ms
tps = 22.688650 (including connections establishing)
tps = 22.708546 (excluding connections establishing)
script statistics:
 - statement latencies in milliseconds:
      3078.170  select update();

方法2 skip locked

这个方法需要9.5以及以上版本支持

代码如下:

create or replace function update() returns void as $$
declare
  v_id int;
begin
  select id into v_id from parallel_update_test order by id limit 1 for update skip locked;
  update parallel_update_test set info=array_append(info,1) where id=v_id;
  loop
    select id into v_id from parallel_update_test where id>v_id order by id limit 1 for update skip locked;
    if found then
      update parallel_update_test set info=array_append(info,1) where id=v_id;
    else
      return;
    end if;
  end loop;
end;
$$ language plpgsql strict;

使用100个并行度

并行更新耗时4秒

pgbench -M prepared -n -r -f ./test.sql -c 100 -j 100 -t 1
transaction type: ./test.sql
scaling factor: 1
query mode: prepared
number of clients: 100
number of threads: 100
number of transactions per client: 1
number of transactions actually processed: 100/100
latency average = 4204.439 ms
tps = 23.784386 (including connections establishing)
tps = 23.813193 (excluding connections establishing)
script statistics:
 - statement latencies in milliseconds:
      3074.488  select update();

小结

在实时推荐系统中,通常可以使用数组或者比特位来标记人群,而每个人群都在不断的发生变化,也就是说,整张表都是热表。

为了提高更新的效率,本文给大家提供了两种并行消除行锁冲突更新的方法。

使用PostgreSQL提供的skip locked 或者advisory lock特性,消除行锁冲突,提高并行度,从而提高更新效率,发挥机器的最大能力。

原文发布于微信公众号 - CSDN技术头条(CSDN_Tech)

原文发表时间:2016-10-18

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java架构

阿里P9架构师谈:高并发网站的监控系统选型、比较、核心监控指标

在高并发分布式环境下,对于访问量大的业务、接口等,需要及时的监控网站的健康程度,防止网站出现访问缓慢,甚至在特殊情况出现应用服务器雪崩等场景,在高并发场景下网站...

44540
来自专栏哲学驱动设计

OEA 2.11 支持单机版数据库 - SQLite与SQLCE对比

在 OEA 平台 设计中,需要支持多种数据库,这至少包括了 SqlServer、Oracle、一个单机文件数据库。而之前对于这一块,我们一直没有实现,只是预留了...

26670

获取漫游到云中的数据

以下文章由微软高级内容发布主管Katrina Lyon-Smith撰写并贡献给博客。

61860
来自专栏杨建荣的学习笔记

曲折的dump导入及问题分析(r5笔记第47天)

今天下午的时候得到反馈,说开发在导入一个dump的时候报了错误,他们尝试连接数据库,发现连接都有问题,让我们赶紧看看。 这是一个测试环境的库,在服务器上同时还跑...

37150
来自专栏杨建荣的学习笔记

MySQL的double write和Oracle对比学习(r4笔记第47天)

之前有网友希望我对mysql的double write和oracle能够做一个对比,其实这种对比方式挺好,能够触类旁通,举一反三。不过限于本人水平有限,欢迎拍砖...

33850
来自专栏腾讯云数据库(TencentDB)

CynosDB for PostgreSQL 架构浅析

作者介绍:林锦,腾讯云数据库团队高级工程师,曾任云计算初创公司系统架构师,从事分布式系统研发7年,2017年加入腾讯云,从事NewSQL研发工作,目前主要负责C...

3.4K210
来自专栏吴生的专栏

不停机分库分表迁移

类似订单表,用户表这种未来规模上亿甚至上十亿百亿的海量数据表,在项目初期为了快速上线,一般只是单表设计,不需要考虑分库分表。随着业务的发展,单表容量超过千万甚至...

57370
来自专栏FreeBuf

MacOS再次出现漏洞,号称牢不可破的系统也有弱点

本文讲述了我在苹果的macOS系统内核中发现的几个堆栈和缓冲区溢出漏洞,苹果官方将这几个漏洞归类为内核中的远程代码执行漏洞,因此这些漏洞的威胁级别非常高。攻击者...

11020
来自专栏Albert陈凯

hbase大规模数据写入的优化历程

业务背景:由于需要将ngix日志过滤出来的1亿+条用户行为记录存入Hbase数据库,以此根据一定的条件来提供近实时查询,比如根据用户id及一定的时间段等条件来过...

50860
来自专栏抠抠空间

数据库简介

一 数据库管理软件的由来 基于我们之前所学,数据要想永久保存,都是保存于文件中,毫无疑问,一个文件仅仅只能存在于某一台机器上。 如果我们暂且忽略直接基于文件来存...

38070

扫码关注云+社区

领取腾讯云代金券