PostgreSQL中应该没有引发序列化访问错误

时间:2018-03-29 10:32:15

标签: postgresql

在类似于下面描述的情况下,我对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 4SELECT查询无法看到另一个事务插入的新行,因为它尚未提交。因此,INSERT处的以下Step 6查询依赖于陈旧数据。

我希望SERIALIZABLE隔离级别可以解决此问题,并且不允许我在Step 7成功提交事务并返回40001错误...但令我惊讶的是,它确实引发任何错误。

为什么会这样,我怎样才能实现我需要的行为?

1 个答案:

答案 0 :(得分:1)

步骤4中的SELECT 依赖陈旧数据,因为尚未提交修改。在事务提交之前,数据库就好像它什么也没发生一样。

这由READ COMMITTED隔离级别和所有更高级别保证。 (PostgreSQL不允许脏读,因此即使您请求READ UNCOMMITTED隔离级别,也会出现此行为。)

此外,SERIALIZABLE表示所有事务的结果都保证与事务的某些序列化执行的结果相同。这显然就是这种情况:如果我们通过在连接#2上的连接#1之前执行事务来序列化,我们会得到相同的结果。

它永远不应该是必要的,但如果您想通过强制执行序列化执行来减少并发性,请在每个START TRANSACTION之后立即添加以下语句:

LOCK TABLE concurrency_test IN ACCESS EXCLUSIVE MODE;