17

我一直在彻底阅读我的其他问题中建议的关于事务隔离的 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)

我了解请求 1 和请求 2 中的插入与之前的读取不冲突,因此不应启动任何错误。为什么我收到“错误:由于事务之间的读/写依赖关系而无法序列化访问”。

正如您可以想象的那样,我不能发生上述行为,因为每个并发请求都会被回滚,而不管其细节如何。在我的业务场景中,我希望并发请求仅在他们为同一个客户插入数据(根据示例设备)时回滚。

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

非常感谢!

4

2 回答 2

20

事务隔离页面:

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

...

  • 顺序扫描总是需要关系级别的谓词锁。这可能导致序列化失败率增加。

EXPLAIN它可以SELECT告诉你正在执行什么查询计划,但如果表很小(或为空!),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)

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

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)

但是,这不是正确的解决方案。让我们稍微备份一下。

可序列化旨在保证事务将具有与一个接一个运行完全相同的效果,尽管事实上您实际上是同时运行这些事务。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.

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

重要的是,使用这种技术的环境具有处理序列化失败的通用方法(总是返回 SQLSTATE 值“40001”),因为很难准确预测哪些事务可能有助于读/写依赖项,需要回滚以防止序列化异常。

于 2012-10-11T14:41:31.443 回答
1

对于那些更好奇的人,在 Postgres 9.1 源代码中,如果你看的话 src/backend/storage/lmgr/README-SSI,有很多关于谓词锁定和可序列化事务的详细描述

这是相同的片段:

可序列化快照隔离(SSI)和谓词锁定 ========================================= ===================

此代码位于 lmgr 目录中,因为其中大约 90% 是 SSI 所需的谓词锁定的实现,而不是与 SSI 本身直接相关。当谓词锁定的另一种用途证明将这两件事分开的努力是合理的时,这个 README 文件可能应该被拆分。

学分:

此功能由 Kevin Grittner 和 Dan RK Ports 开发,并得到了 Joe Conway、Heikki Linnakangas 和 Jeff Davis 的评论和建议。它基于这些论文中发表的工作:

 Michael J. Cahill, Uwe Röhm, and Alan D. Fekete. 2008.
 Serializable isolation for snapshot databases.
 In SIGMOD '08: Proceedings of the 2008 ACM SIGMOD
 international conference on Management of data,
 pages 729-738, New York, NY, USA. ACM.
 http://doi.acm.org/10.1145/1376616.1376690

 Michael James Cahill. 2009.
 Serializable Isolation for Snapshot Databases.
 Sydney Digital Theses.
 University of Sydney, School of Information Technologies.
 http://hdl.handle.net/2123/5353
于 2013-09-24T08:24:03.507 回答