在类似于下面描述的情况下,我对SERIALIZABLE
隔离级别行为表示担忧。
表格:
CREATE TABLE concurrency_test (
id serial PRIMARY KEY,
sum INT NOT NULL
);
查询:
+------+-------------------------------------------------+-------------------------------------------------+
| Step | Connection #1 | Connection #2 |
+------+-------------------------------------------------+-------------------------------------------------+
| 1 | | START TRANSACTION ISOLATION LEVEL SERIALIZABLE; |
| 2 | | INSERT INTO concurrency_test (sum) VALUES(400); |
| 3 | START TRANSACTION ISOLATION LEVEL SERIALIZABLE; | |
| 4 | SELECT SUM("sum") FROM concurrency_test; | |
| 5 | | COMMIT; |
| 6 | INSERT INTO concurrency_test (sum) VALUES(300); | |
| 7 | COMMIT; | |
+------+-------------------------------------------------+-------------------------------------------------+
在Step 4
,SELECT
查询无法看到另一个事务插入的新行,因为它尚未提交。因此,INSERT
处的以下Step 6
查询依赖于陈旧数据。
我希望SERIALIZABLE
隔离级别可以解决此问题,并且不允许我在Step 7
成功提交事务并返回40001
错误...但令我惊讶的是,它确实不引发任何错误。
为什么会这样,我怎样才能实现我需要的行为?
答案 0 :(得分:1)
步骤4中的SELECT
不依赖陈旧数据,因为尚未提交修改。在事务提交之前,数据库就好像它什么也没发生一样。
这由READ COMMITTED
隔离级别和所有更高级别保证。 (PostgreSQL不允许脏读,因此即使您请求READ UNCOMMITTED
隔离级别,也会出现此行为。)
此外,SERIALIZABLE
表示所有事务的结果都保证与事务的某些序列化执行的结果相同。这显然就是这种情况:如果我们通过在连接#2上的连接#1之前执行事务来序列化,我们会得到相同的结果。
它永远不应该是必要的,但如果您想通过强制执行序列化执行来减少并发性,请在每个START TRANSACTION
之后立即添加以下语句:
LOCK TABLE concurrency_test IN ACCESS EXCLUSIVE MODE;