Skip to content

Commit 96c1e1e

Browse files
authored
Create 20161018_01.md
1 parent 9bd159e commit 96c1e1e

File tree

1 file changed

+181
-0
lines changed

1 file changed

+181
-0
lines changed

201610/20161018_01.md

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
## PostgreSQL 使用advisory lock或skip locked消除行锁冲突, 提高几十倍并发更新效率
2+
3+
### 作者
4+
digoal
5+
6+
### 日期
7+
2016-10-18
8+
9+
### 标签
10+
PostgreSQL , advisory lock , 高并发更新
11+
12+
----
13+
14+
## 背景
15+
通常在数据库中最小粒度的锁是行锁,当一个事务正在更新某条记录时,另一个事务如果要更新同一条记录(或者申请这一条记录的锁),则必须等待锁释放。
16+
17+
通常持锁的时间需要保持到事务结束,也就是说,如果一个长事务持有了某条记录的锁,其他会话要持有这条记录的锁,可能要等很久。
18+
19+
如果某张表的全表或者大部分记录要被更新的话,有几种做法。
20+
21+
1\. 在一个事务中更新需要更新的记录,很显然时间可能很长,因为没有了并发。
22+
23+
2\. 在多个事务中更新不同的记录,使用高并发来缩短更新的时间,但是就需要解决并发更新时存在的行锁冲突的问题。
24+
25+
本文将要给大家介绍两种解决并发更新行锁冲突问题的方法。
26+
27+
## 场景描述
28+
测试表,单条记录越大,更新单条记录的时间越久(例如更新亿级别的超长BIT类型)。
29+
30+
每个人群都有一个唯一的ID,即parallel_update_test.id。
31+
32+
```
33+
create unlogged table parallel_update_test(id int primary key, info int[]);
34+
```
35+
36+
测试数据
37+
38+
```
39+
insert into parallel_update_test select generate_series(1,10000), (select array_agg(id) from generate_series(1,100000) t(id));
40+
41+
postgres=# \dt+ parallel_update_test
42+
List of relations
43+
Schema | Name | Type | Owner | Size | Description
44+
--------+----------------------+-------+----------+---------+-------------
45+
public | parallel_update_test | table | postgres | 3961 MB |
46+
(1 row)
47+
```
48+
49+
更新需求,每条记录都有更新
50+
51+
例如我存储的数组是USERID,每条记录代表某个属性的人群数据,这个属性的人群数据不断的在变化,因此会不断的需要更新。
52+
53+
```
54+
update parallel_update_test set info=array_append(info,1);
55+
56+
单个事务更新耗时80秒
57+
postgres=# begin;
58+
postgres=# update parallel_update_test set info=array_append(info,1);
59+
UPDATE 10000
60+
Time: 80212.641 ms
61+
postgres=# rollback;
62+
ROLLBACK
63+
Time: 0.131 ms
64+
postgres=# vacuum parallel_update_test ;
65+
```
66+
67+
使用并发的手段提高更新效率。
68+
69+
## 方法1 advisory lock
70+
每个人群都有一个唯一的ID,即parallel_update_test.id。
71+
72+
所以只要保证并行的会话更新的是不同的ID对应的数据即可,同时需要避免单次重复更新。
73+
74+
### 如何避免更新同一个ID?
75+
使用advisory lock可以避免并发更新同一条记录。
76+
77+
### 如何避免重复更新同一条记录。
78+
使用扫描式的获取advisory lock,保证不会重复获取即可。
79+
80+
代码如下:
81+
82+
```
83+
create or replace function update() returns void as $$
84+
declare
85+
v_id int;
86+
begin
87+
for v_id in select id from parallel_update_test -- 扫描式
88+
loop
89+
if pg_try_advisory_xact_lock(v_id) then -- 获取到ID的LOCK才会实施更新,否则继续扫描
90+
update parallel_update_test set info=array_append(info,1) where id=v_id;
91+
end if;
92+
end loop;
93+
end;
94+
$$ language plpgsql strict;
95+
```
96+
97+
设计上尽量保证ID全局唯一,否则获取advisory lock的冲突可能性会增多。
98+
99+
测试,使用100个并行度
100+
101+
```
102+
vi test.sql
103+
select update();
104+
105+
pgbench -M prepared -n -r -f ./test.sql -c 100 -j 100 -t 1
106+
```
107+
108+
并行更新耗时4秒
109+
110+
```
111+
pgbench -M prepared -n -r -f ./test.sql -c 100 -j 100 -t 1
112+
transaction type: ./test.sql
113+
scaling factor: 1
114+
query mode: prepared
115+
number of clients: 100
116+
number of threads: 100
117+
number of transactions per client: 1
118+
number of transactions actually processed: 100/100
119+
latency average = 4407.490 ms
120+
tps = 22.688650 (including connections establishing)
121+
tps = 22.708546 (excluding connections establishing)
122+
script statistics:
123+
- statement latencies in milliseconds:
124+
3078.170 select update();
125+
```
126+
127+
## 方法2 skip locked
128+
这个方法需要9.5以及以上版本支持
129+
130+
代码如下:
131+
132+
```
133+
create or replace function update() returns void as $$
134+
declare
135+
v_id int;
136+
begin
137+
select id into v_id from parallel_update_test order by id limit 1 for update skip locked;
138+
update parallel_update_test set info=array_append(info,1) where id=v_id;
139+
loop
140+
select id into v_id from parallel_update_test where id>v_id order by id limit 1 for update skip locked;
141+
if found then
142+
update parallel_update_test set info=array_append(info,1) where id=v_id;
143+
else
144+
return;
145+
end if;
146+
end loop;
147+
end;
148+
$$ language plpgsql strict;
149+
```
150+
151+
使用100个并行度
152+
153+
并行更新耗时4秒
154+
155+
```
156+
pgbench -M prepared -n -r -f ./test.sql -c 100 -j 100 -t 1
157+
transaction type: ./test.sql
158+
scaling factor: 1
159+
query mode: prepared
160+
number of clients: 100
161+
number of threads: 100
162+
number of transactions per client: 1
163+
number of transactions actually processed: 100/100
164+
latency average = 4204.439 ms
165+
tps = 23.784386 (including connections establishing)
166+
tps = 23.813193 (excluding connections establishing)
167+
script statistics:
168+
- statement latencies in milliseconds:
169+
3074.488 select update();
170+
```
171+
172+
## 小结
173+
在实时推荐系统中,通常可以使用数组或者比特位来标记人群,而每个人群都在不断的发生变化,也就是说,整张表都是热表。
174+
175+
为了提高更新的效率,本文给大家提供了两种并行消除行锁冲突更新的方法。
176+
177+
使用PostgreSQL提供的skip locked 或者advisory lock特性,消除行锁冲突,提高并行度,从而提高更新效率,发挥机器的最大能力。
178+
179+
[Count](http://info.flagcounter.com/h9V1)
180+
181+

0 commit comments

Comments
 (0)