使用Serializable隔离在PostgreSQL 9.2.1中进行谓词锁定

Jav*_*cia 15 postgresql locking transactions

我一直在仔细阅读其他问题中提到的关于事务隔离的postgres文档,但我还没有设法理解"谓词锁定"的东西.

我希望有人能开导我:-)

根据文档:PostgreSQL中的谓词锁与大多数其他数据库系统一样,基于事务实际访问的数据

这听起来不错,那为什么会发生以下情况呢?

CREATE TABLE mycustomer(cid integer PRIMARY KEY, licenses integer);
CREATE TABLE mydevice(id integer PRIMARY KEY, cid integer REFERENCES 
mycustomer (cid), status varchar(10));

INSERT INTO mycustomer(cid, licenses) VALUES (1, 5);
INSERT INTO mycustomer(cid, licenses) VALUES (2, 5);

    Request 1                            Request2
BEGIN TRANSACTION ISOLATION 
LEVEL SERIALIZABLE;
                                         BEGIN TRANSACTION ISOLATION 
                                         LEVEL SERIALIZABLE;
SELECT * from mydevice where cid = 1;

                                         SELECT * from mydevice where cid = 2;
INSERT INTO mydevice(id, cid, status) 
VALUES (1, 1, 'ok');

                                         INSERT INTO mydevice(id, cid, status)         
                                         VALUES (2, 2, 'ok');
commit;
(=ok)                                 
                                         commit;
                                         (=rollback)
Run Code Online (Sandbox Code Playgroud)

我理解来自请求1和请求2的插入与先前的读取不冲突,因此不应该发起任何错误.为什么我得到"错误:由于事务之间的读/写依赖性而无法序列化访问".

您可以想象我不会发生上述行为,因为无论其详细信息如何,每个并发请求都将被回滚.在我的业务场景中,我希望并发请求仅在为同一个客户插入数据(根据示例设备)时进行回滚支持.

这些操作是从Java应用程序执行的.原则上我正在考虑创建一个锁定表来满足我的需求.有任何想法吗?

非常感谢!

wil*_*ynn 17

从" 事务隔离"页面:

在执行查询期间获取的特定锁将取决于查询使用的计划,并且多个细粒度锁(例如,元组锁)可以在过程期间组合成更少的粗粒度锁(例如,页锁).该事务用于防止用于跟踪锁的内存耗尽.

...

  • 顺序扫描将始终需要关系级谓词锁.这可能导致序列化失败率增加.

EXPLAINSELECT可以电话你正在采取的查询计划,但如果表很小(或空!),PostgreSQL将几乎肯定挑引用该索引的顺序扫描来代替.这将导致整个表上的谓词锁定,导致序列化失败,只要另一个事务对表执行任何操作.

在我的系统上:

isolation=# EXPLAIN SELECT * from mydevice where cid = 1;
                        QUERY PLAN                        
----------------------------------------------------------
 Seq Scan on mydevice  (cost=0.00..23.38 rows=5 width=46)
   Filter: (cid = 1)
(2 rows)
Run Code Online (Sandbox Code Playgroud)

您可以尝试添加索引并强制它使用它:

isolation=# CREATE INDEX mydevice_cid_key ON mydevice (cid);
CREATE INDEX
isolation=# SET enable_seqscan = off;
SET
isolation=# EXPLAIN SELECT * from mydevice where cid = 1;
                                    QUERY PLAN                                    
----------------------------------------------------------------------------------
 Index Scan using mydevice_cid_key on mydevice  (cost=0.00..8.27 rows=1 width=46)
   Index Cond: (cid = 1)
(2 rows)
Run Code Online (Sandbox Code Playgroud)

但是,这不是正确的解决方案.让我们回顾一下.

Serializable旨在保证事务具有与它们一个接一个地运行完全相同的效果,尽管事实上您实际上是在同时运行这些事务.PostgreSQL没有无限的资源,所以虽然它确实将谓词锁定放在查询实际访问的数据上,但"数据"可能意味着"返回的行".

PostgreSQL选择在认为可能存在问题时标记序列化失败,而不是在确定时.(因此它如何将行锁概括为页锁.)此设计选择会导致误报,例如示例中的误报.误报不太理想,但是,它不会影响隔离语义的正确性.

错误消息是:

ERROR:  could not serialize access due to read/write dependencies among transactions
DETAIL:  Reason code: Canceled on identification as a pivot, during commit attempt.
HINT:  The transaction might succeed if retried.
Run Code Online (Sandbox Code Playgroud)

这个提示很关键.您的应用程序需要捕获序列化失败并重试整个操作.无论何时SERIALIZABLE发挥都是如此- 它保证了串行正确性,尽管并发,但如果没有应用程序的帮助它就无法做到这一点.换句话说,如果您实际上正在进行并发修改,PostgreSQL满足隔离要求的唯一方法是让您的应用程序自行序列化.从而:

重要的是,使用此技术的环境具有处理序列化失败的通用方法(总是返回SQLSTATE值为'40001'),因为很难准确预测哪些事务可能对读/写有影响依赖关系并需要回滚以防止序列化异常.