Postgresql 选择直到达到一定的总量并锁定
Posted
技术标签:
【中文标题】Postgresql 选择直到达到一定的总量并锁定【英文标题】:Postgresql select until certain total amount is reached and lock 【发布时间】:2020-09-23 19:44:05 【问题描述】:我有一个用户批次表。我只想选择直到我的总金额达到一定金额。
id | user_id | balance | batch_id
----|---------|-------- |--------
1 | 1 | 2 | 1
2 | 2 | 15 | 2
3 | 1 | 8 | 3
4 | 1 | 5 | 4
5 | 2 | 7 | 5
6 | 1 | 1 | 6
7 | 2 | 5 | 7
考虑以下查询:
SELECT * FROM tb_batch_user WHERE user_id = 1 ORDER BY batch_id asc
查询结果为:
id | user_id | balance | batch_id
----|---------|-------- |--------
1 | 1 | 2 | 1
3 | 1 | 8 | 3
4 | 1 | 5 | 4
6 | 1 | 1 | 6
我想在表上做一个选择,直到余额总数为 6。那么应该只返回 ids 1、2:
id | user_id | balance | batch_id
----|---------|-------- |--------
1 | 1 | 2 | 1
3 | 1 | 8 | 3
另一个余额总计为 1 的示例。那么应该只返回 ids 1:
id | user_id | balance | batch_id
----|---------|-------- |--------
1 | 1 | 2 | 1
余额总数为 11 的示例。应仅返回 id 1、3、4:
id | user_id | balance | batch_id
----|---------|-------- |--------
1 | 1 | 2 | 1
3 | 1 | 8 | 3
4 | 1 | 5 | 4
所以,在那之后我需要用 FOR UPDATE ex 锁定这些行:
SELECT * FROM tb_batch_user WHERE user_id = 1 ORDER BY batch_id asc FOR UPDATE
我尝试使用窗口功能,但它不允许锁定(用于更新)。感谢您的帮助。
【问题讨论】:
【参考方案1】:我可以select. . . for update
使用窗口函数:
with inparms as (
select 1 as user_id, 6 as target
), rtotal as (
select t.id, i.target,
sum(t.balance) over (partition by t.user_id
order by t.id
rows between unbounded preceding
and 1 preceding) as runbalance
from tb_batch_user t
join inparms i
on i.user_id = t.user_id
)
select t.*
from rtotal r
join tb_batch_user t
on t.id = r.id
where coalesce(r.runbalance, 0) < r.target
for update of t;
Fiddle here
【讨论】:
嗯,if thebalance
of the row with id
3
was 4
it includes the row with the id
of 4
...这是@KelvinSantiago 的意图吗?
是的,确实如此。我想这意味着当 id
1
和 id 4
的行已经加起来 6
时,不包括 id 4
的行。但在这种情况下,也许 OP 确实也想要“下”一行。
@stickybit 你是对的。那应该是直的<
。谢谢你接听。
工作。拥有数百万条记录的表会出现性能问题吗?
@KelvinSantiago 您需要运行它才能找到答案。假设 user_id
被索引,这应该表现得很好。如果没有,那么您可以将 rtotal
CTE 更改为子查询。【参考方案2】:
你在找这个吗?
with w0 as (
select id, user_id, balance, batch_id,
coalesce(lag(running_balance) over (partition by user_id order by batch_id asc), 0) running_balance
from (
SELECT t.* ,
sum(balance) over (partition by user_id order by batch_id asc) running_balance
FROM tb_batch_user t
--where t.user_id = 1
) x
)
select * from w0
where running_balance < 6
PS:您可以将 user_id 添加为 where 子句。看评论
用于锁定,
select * from tb_batch_user tb
where tb.id in (select w0.id from w0 where running_balance < 6)
for update
【讨论】:
嗯,当添加FOR UPDATE
时,这似乎没有锁定任何东西。但它不会引发错误。
更新和查询工作正常。我将使用 explain 评估此查询的性能。
@Derviş Kayımbaşıoğlu 有一些方法可以不遍历整个表,即停止直到找到值。在查询 SELECT t.* 中, sum(balance) over (partition by user_id order by batch_id asc) running_balance FROM tb_batch_user t【参考方案3】:
这是一种使用窗口函数的方法:
select id, balance, user_id, batch_id
from (
select t.*,
sum(balance) over(partition by user_id order by id) sum_balance
from mytable t
where user_id = 1
) t
where sum_balance - balance < 6
您需要累积余额,直到第一个等于或超过阈值。为此,您可以使用窗口sum()
。
您可以将不等式条件更改为您喜欢的阈值。您还可以在子查询中更改(或删除)user_id
上的过滤。
我们可以很容易地用一个支持for update
的子查询来实现同样的逻辑:
select *
from mytable t
where user_id = 1 and (
select coalesce(sum(balance), 0)
from mytable t1
where t1.user_id = t.user_id and t1.id < t.id
) < 6
for update
Demo on DB Fiddle:
编号 |余额|用户身份 -: | ------: | ------: 1 | 2 | 1 3 | 8 | 1【讨论】:
添加FOR UPDATE
会引发错误“窗口函数不允许FOR UPDATE”。
@stickybit:很好,谢谢。我用与子查询相同的解决方案更新了我的答案。
我可以确认您的编辑似乎正确锁定。
嗯,但另一件事:嗯,if the balance
of the row with id
3
was 4
it includes the row with the id
of 4
and 6
... id
4
的行可能是 OP 在这种情况下想要的,但我无法想象他们也想要与id
6
排在一起。
@GMB 不工作,尝试使用数量 11,只应返回 1、3、4 的 ID【参考方案4】:
假设(user_id, batch_id)
是一个键,您可以使用相关子查询来避免窗口函数。外部子查询获取最小的batch_id
,其中balance
的总和达到或超过给定用户ID 的6
。那个总和是在内部的。
SELECT *
FROM tb_batch_user bu1
WHERE bu1.user_id = 1
AND bu1.batch_id <= (SELECT min(bu2.batch_id) batch_id
FROM tb_batch_user bu2
WHERE bu2.user_id = bu1.user_id
AND (SELECT sum(bu3.balance)
FROM tb_batch_user bu3
WHERE bu3.user_id = bu2.user_id
AND bu3.batch_id <= bu2.batch_id) >= 6)
FOR UPDATE;
安装pgrowlocks
extension 后,我们可以检查正确的行是否已锁定。
SELECT *
FROM pgrowlocks('tb_batch_user');
返回:
locked_row | locker | multi | xids | modes | pids
------------+----------+-------+------------+----------------+---------
(0,1) | 10847645 | f | 10847645 | "For Update" | 11996
(0,3) | 10847645 | f | 10847645 | "For Update" | 11996
【讨论】:
以上是关于Postgresql 选择直到达到一定的总量并锁定的主要内容,如果未能解决你的问题,请参考以下文章