我需要对记录集合进行Postgres更新。我试图阻止压力测试中出现的死锁。
对此的典型解决方案是按照特定顺序更新记录,例如ID - 但似乎Postgres不允许ORDER BY进行更新。
假设我需要进行更新,例如:
UPDATE BALANCES WHERE ID IN (SELECT ID FROM some_function() ORDER BY ID);
当您同时运行200个查询时,会导致死锁。怎么办?
我正在寻找一般解决方案,而不是UPDATE with ORDER BY
中的特定案例解决方法感觉必须有比编写游标功能更好的解决方案。此外,如果没有更好的方法,那么光标功能如何最佳?逐个记录更新
答案 0 :(得分:10)
据我所知,没有办法直接通过UPDATE
声明完成此任务;保证锁定顺序的唯一方法是使用SELECT ... ORDER BY ID FOR UPDATE
显式获取锁,例如:
UPDATE Balances
SET Balance = 0
WHERE ID IN (
SELECT ID FROM Balances
WHERE ID IN (SELECT ID FROM some_function())
ORDER BY ID
FOR UPDATE
)
这具有在ID
表上重复Balances
索引查找的缺点。在您的简单示例中,您可以通过在锁定查询期间获取物理行地址(由ctid
system column表示)并使用它来驱动UPDATE
来避免此开销:
UPDATE Balances
SET Balance = 0
WHERE ctid = ANY(ARRAY(
SELECT ctid FROM Balances
WHERE ID IN (SELECT ID FROM some_function())
ORDER BY ID
FOR UPDATE
))
(使用ctid
时要小心,因为值是暂时的。我们在这里很安全,因为锁会阻止任何更改。)
不幸的是,规划人员只会在一组狭窄的案例中使用ctid
(您可以通过在EXPLAIN
输出中查找“Tid Scan”节点来判断它是否正常工作)。要在单个UPDATE
语句中处理更复杂的查询,例如如果您的新余额由some_function()
和ID一起返回,则您需要回退到基于ID的查找:
UPDATE Balances
SET Balance = Locks.NewBalance
FROM (
SELECT Balances.ID, some_function.NewBalance
FROM Balances
JOIN some_function() ON some_function.ID = Balances.ID
ORDER BY Balances.ID
FOR UPDATE
) Locks
WHERE Balances.ID = Locks.ID
如果性能开销是个问题,那么您需要使用游标,这看起来像这样:
DO $$
DECLARE
c CURSOR FOR
SELECT Balances.ID, some_function.NewBalance
FROM Balances
JOIN some_function() ON some_function.ID = Balances.ID
ORDER BY Balances.ID
FOR UPDATE;
BEGIN
FOR row IN c LOOP
UPDATE Balances
SET Balance = row.NewBalance
WHERE CURRENT OF c;
END LOOP;
END
$$
答案 1 :(得分:2)
一般来说,并发很难。特别是有200个语句(我假设你不只是查询= SELECT)甚至是事务(实际上每个发出的语句都包含在一个事务中,如果它不在事务中)。
一般的解决方案概念是(这些)的组合:
要注意可能发生死锁,请在应用程序中捕获它们,检查class 40
40P01
或SELECT ... FOR UPDATE
并重试该事务。
预订锁。使用READ COMMITED
。尽可能地避免显式锁定。锁将强制其他事务等待锁释放,这会损害并发性,但可以防止事务进入死锁。在第13章中检查示例是否存在死锁。尤其是事务A等待B和B等待A(银行账户)的那个。
如果可能,请选择其他Error Codes,例如LOST UPDATE
等较弱的Isolation Level。请注意READ COMMITED
模式下的REPEATABLE READ
。使用LOCK / USE A -- Transaction 1
LOCK / USE B -- Transaction 1
LOCK / USE C -- Transaction 1
-- D not used -- Transaction 1
-- A not used -- Transaction 2
LOCK / USE B -- Transaction 2
-- C not used -- Transaction 2
LOCK / USE D -- Transaction 2
预防他们。
在每个事务中使用相同的顺序编写带有锁的语句,例如按字母顺序按表名称。
A B C D
使用常规锁定顺序a1 b1 a2 a3 a4 b2 b3
。这样,事务可以以任何相对顺序交错,并且仍然有很好的机会不会死锁(取决于您的语句,您可能会遇到其他序列化问题)。事务的语句将按照它们指定的顺序运行,但可以是事务1运行它们的前2个,然后xact 2运行第一个,然后1个结束,最后xact 2结束。
此外,您应该意识到在并发情况下不会以原子方式执行涉及多行的语句。换句话说,如果您有两个涉及多行的语句A和B,那么它们可以按以下顺序执行:
EXPLAIN
但不是b' s后面的一块。
这同样适用于具有子查询的语句。
您是否使用UPDATE BALANCES WHERE ID IN (
SELECT ID FROM some_function() FOR UPDATE -- LOCK using FOR UPDATE
-- other transactions will WAIT / BLOCK temporarily on conc. write access
);
查看了查询计划?
在您的情况下,您可以尝试
LOST UPDATE
如果可能的话,你也可以使用SELECT ... FOR UPDATE SKIP LOCK,它将跳过已锁定的数据以恢复并发,这会因为等待另一个事务释放锁(FOR UPDATE)而丢失。但是这不会对应用程序逻辑可能需要的锁定行应用UPDATE。所以稍后运行(参见第1点)。
另请阅读LOST UPDATE关于SKIP LOCKED
和。{
SKIP LOCKED关于SKIP LOCKED
。在您的情况下,队列可能是一个想法,在 <section id="skills" class="skills-section">
<div>
<h1>MY SKILLS</h1>
</div>
<ul class="chart" id="chart-line">
<li>
<span style="height:5%; background: rgba(0, 102, 255, 0.80);" title="ActionScript"></span>
</li>
<li>
<span style="height:70%; background: rgba(204, 51, 51, 0.80);" title="JavaScript"></span>
</li>
<li>
<span style="height:50%; background: rgba(255, 186, 2, 0.80);" title="CoffeScript"></span>
</li>
<li>
<span style="height:75%; background: rgba(0, 153, 102, 0.80);" title="HTML"></span>
</li>
<li>
<span style="height:90%; background: rgba(0, 102, 255, 0.80);" title="HTML"></span>
</li>
<li>
<span style="height:15%; background: rgba(204, 51, 51, 0.80);" title="HTML"></span>
</li>
<li>
<span style="height:40%; background: rgba(255, 186, 2, 0.80);" title="HTML"></span>
</li>
<li>
<span style="height:55%; background: rgba(0, 153, 102, 0.80);" title="HTML"></span>
</li>
</ul>
<div class="button-down">
<a class="page-scroll" href="#projects"><button class="button-down-text">TESTO</button></a>
</div>
</section>
<!-- Projects Section --> <!-- This is the begin of the section below -->
<section id="projects" class="projects-section">
引用中可以很好地解释,尽管关系DBMS并不是队列。
HTH