vek*_*tor 5 postgresql concurrency locking update postgresql-9.6
在 PostgreSQL 9.6 我有一个T这样的表
category | id | data
---------+----+------
A | 1 | foo
A | 2 | bar
A | 3 | baz
B | 4 | eh
B | 5 | whatcomesafterfoobarbaz
Run Code Online (Sandbox Code Playgroud)
有一个视图V为我提供了数据T,所以它有列category, id, data。T本质上是 的物化视图V,除了我需要以比“刷新所有内容”更多的粒度来刷新它。
所以我会选择V例如
SELECT * FROM V WHERE category = 'A';
Run Code Online (Sandbox Code Playgroud)
或者
SELECT * FROM V WHERE category = 'A' AND id = 2;
Run Code Online (Sandbox Code Playgroud)
并T用任何data V给我的东西替换相关的行。不幸的是,我不能做一个简单的UPDATE:问V例如。因为WHERE category = 'A'可能会给我一组与以前完全不同的行。因此我需要做这个序列:
DELETE FROM T WHERE <condition>;
INSERT INTO T (SELECT FROM V WHERE <condition>);
Run Code Online (Sandbox Code Playgroud)
<condition>是WHERE category = ?或WHERE category = ? AND id = ?。
我该怎么做才能满足以下条件?
<condition>应该不受影响。<condition>应该看到旧行集或新行集,而不是混合。注意:与此问题不同,我不想一次替换整个表 - 只有受影响的行。
读取次数比写入次数多,大约是写入次数的 10-100 倍。每次写入后都会读取相邻类别。该应用程序是在看一组categories,ids并data和更新data的一个或多个categories在同一时间。紧接着它会重新获取那些categories并显示它们,它必须看到新鲜的data. 所有的ids 总是用 "their" 获取category。
每个category都会有 1-10 ids 之类的东西,会有数以万计的categories.
事务可以并发运行。肯定会出现两个事务以DELETE FROM T WHERE category = 'A';.
有一个表categories可以锁定行FOR UPDATE。还有一个id可以锁定 s的表FOR UPDATE。
RETURNING在这里没有多大意义,因为我需要获取的不仅仅是更改的行。因此,使用单独的SELECT.
并发读取不是问题。在默认READ COMMITTED隔离级别中,写入器不会阻止读取器,反之亦然。将DELETE并包含INSERT在单个事务中以使操作具有原子性(全部应用或不应用)。
如果可以有多个事务尝试同时写入,那就是游戏规则的改变者。单个事务可以保护您免受不一致的更新,但它不能保护您免受并发事务之间的竞争条件:死锁。
假设我们有两个事务T1和T2,类别“A”有 10 个 ID:
T1: DELETE FROM T WHERE category = 'A';
-- starts taking row locks in arbitrary order: id 1,2,3,4,5,6,7 ...
T2: DELETE FROM T WHERE category = 'A';
-- starts taking row locks in arbitrary order: id 10, 9, 8, ...
T1: wait for T2 to release lock on id 8
T2: wait for T1 to release lock on id 7
DEADLOCK.
Run Code Online (Sandbox Code Playgroud)
Postgres 会在一段时间后检测到死锁并终止两个事务之一。(报告死锁错误。)
您可以切换到SERIALIZABLE事务隔离。但这要昂贵得多,您需要为序列化失败做好准备并在这种情况下重试。
或者您可以通过始终以相同的确定性顺序删除行来避免该问题。喜欢:
WITH del AS (
SELECT category, id
FROM T
WHERE category = 'A'
ORDER BY category, id -- enforce this order in *all* writing queries
FOR UPDATE
)
DELETE FROM T
USING del
WHERE T.category = del.category
AND T.id = del.id;
Run Code Online (Sandbox Code Playgroud)
但通常情况下,有一个更方便的选择。如果您有一个单独的表,其中包含名为的唯一类别,例如cat,您可以使用以下命令锁定单个父行cat:
SELECT * FROM cat WHERE category = 'A' FOR UPDATE;
Run Code Online (Sandbox Code Playgroud)
然后(在同一事务中)随意写入类别“A”行T(仍封装在单个事务中以避免中间的、不一致的状态可见)。当然,所有的写查询都必须遵循相同的协议。然后,并发事务将cat在写入之前等待锁定T,一切都很好......
在 Postgres 9.4或更高版本中,请考虑FOR NO KEY UPDATE:
关于:
每次写入后都会读取相邻类别。
你知道这个RETURNING条款,对吧?如果您只是插入给定类别的所有行,则无需单独读取。例子:
| 归档时间: |
|
| 查看次数: |
1136 次 |
| 最近记录: |