事务中的 Postgres 锁
Postgres locks within a transaction
我无法理解锁如何与 Postgres 中的事务交互。
当我 运行 这个(长)查询时,我对发生的高度锁定感到惊讶:
BEGIN;
TRUNCATE foo;
\COPY foo FROM 'backup.txt';
COMMIT;
documentation for \COPY
doesn't mention what level of lock it requires, but this post表示只获取一个RowExclusiveLock。但是当我 运行 这个查询在 \COPY
:
SELECT mode, granted FROM pg_locks
WHERE relation='foo'::regclass::oid;
我明白了:
mode granted
RowExclusiveLock true
ShareLock true
AccessExclusiveLock true
AccessExclusiveLock 到底从哪里来的?我假设它来自 TRUNCATE
,requires an AccessExclusiveLock。但是 TRUNCATE
很快完成,所以我希望锁也能很快释放。这让我有几个问题。
当事务中的命令获取锁时,是否会在命令结束时(事务结束前)释放该锁?如果是这样,为什么我会观察到上述行为?如果不是,为什么不呢?事实上,既然 ,为什么交易中的 TRUNCATE
需要完全阻止 table?
我在 PG 的 documentation for transactions 中没有看到任何对此的讨论。
这里有几个误解需要澄清。
首先,事务确实在提交之前触及table。您引用的评论说 ROLLBACK
(还有 COMMIT
)不要触及 table,这是不同的。他们在commit log(在pg_clog
)中记录事务状态,并且COMMIT
将事务日志刷新到磁盘(一个没有table异常到这是 TRUNCATE
,这与您的问题相关:旧的 table 一直保留到交易结束,并在 COMMIT
期间被删除。
如果所有更改都推迟到 COMMIT
并且不进行任何锁定,那么 COMMIT
将非常昂贵并且通常会因为并发修改而失败。事务必须记住数据库之前的状态,并检查更改是否仍然适用。这种处理并发的方法称为 optimistic concurreny control,虽然它对应用程序来说是一个不错的策略,但它不适用于关系数据库,其中 COMMIT
应该是高效的并且不应该失败(除非基础设施存在重大问题)。
所以关系数据库使用的是悲观并发控制或锁定,即它们在访问数据库对象之前锁定它以防止并发activity 不妨碍他们。
其次,关系型数据库使用two-phase locking,其中锁(至少是用户可见的,所谓重量级锁) 一直持有到交易结束。
这对于保持事务的逻辑顺序 (serializable) 和一致是必要的(但还不够)。如果您释放锁,而其他人删除了您插入但未提交的行通过外键约束引用的行怎么办?
问题的答案
所有这一切的结果是您的 table 将从 TRUNCATE
开始保持 ACCESS EXCLUSIVE
锁定,直到事务结束。为什么这是必要的不是很明显吗?如果允许其他事务甚至在(尚未提交的)TRUNCATE
之后读取 table,他们会发现它是空的,因为 TRUNCATE
确实清空了 table 并且确实不允许不遵守 MVCC semantics. Such a dirty read(可能尚未回滚的未提交数据)。
如果在重新填充期间您确实需要对 table 的读取权限,您可以使用 DELETE
而不是 TRUNCATE
。不利的一面是,这是一个更昂贵的操作,它会给 table 留下很多必须通过 autovacuum 删除的“死元组”,从而导致很多空的 space (table 臃肿)。但是,如果您愿意忍受 table 和膨胀的索引,以至于 table 和索引扫描至少需要两倍的时间,这是一个选择。
我无法理解锁如何与 Postgres 中的事务交互。
当我 运行 这个(长)查询时,我对发生的高度锁定感到惊讶:
BEGIN;
TRUNCATE foo;
\COPY foo FROM 'backup.txt';
COMMIT;
documentation for \COPY
doesn't mention what level of lock it requires, but this post表示只获取一个RowExclusiveLock。但是当我 运行 这个查询在 \COPY
:
SELECT mode, granted FROM pg_locks
WHERE relation='foo'::regclass::oid;
我明白了:
mode granted
RowExclusiveLock true
ShareLock true
AccessExclusiveLock true
AccessExclusiveLock 到底从哪里来的?我假设它来自 TRUNCATE
,requires an AccessExclusiveLock。但是 TRUNCATE
很快完成,所以我希望锁也能很快释放。这让我有几个问题。
当事务中的命令获取锁时,是否会在命令结束时(事务结束前)释放该锁?如果是这样,为什么我会观察到上述行为?如果不是,为什么不呢?事实上,既然 TRUNCATE
需要完全阻止 table?
我在 PG 的 documentation for transactions 中没有看到任何对此的讨论。
这里有几个误解需要澄清。
首先,事务确实在提交之前触及table。您引用的评论说 ROLLBACK
(还有 COMMIT
)不要触及 table,这是不同的。他们在commit log(在pg_clog
)中记录事务状态,并且COMMIT
将事务日志刷新到磁盘(一个没有table异常到这是 TRUNCATE
,这与您的问题相关:旧的 table 一直保留到交易结束,并在 COMMIT
期间被删除。
如果所有更改都推迟到 COMMIT
并且不进行任何锁定,那么 COMMIT
将非常昂贵并且通常会因为并发修改而失败。事务必须记住数据库之前的状态,并检查更改是否仍然适用。这种处理并发的方法称为 optimistic concurreny control,虽然它对应用程序来说是一个不错的策略,但它不适用于关系数据库,其中 COMMIT
应该是高效的并且不应该失败(除非基础设施存在重大问题)。
所以关系数据库使用的是悲观并发控制或锁定,即它们在访问数据库对象之前锁定它以防止并发activity 不妨碍他们。
其次,关系型数据库使用two-phase locking,其中锁(至少是用户可见的,所谓重量级锁) 一直持有到交易结束。 这对于保持事务的逻辑顺序 (serializable) 和一致是必要的(但还不够)。如果您释放锁,而其他人删除了您插入但未提交的行通过外键约束引用的行怎么办?
问题的答案
所有这一切的结果是您的 table 将从 TRUNCATE
开始保持 ACCESS EXCLUSIVE
锁定,直到事务结束。为什么这是必要的不是很明显吗?如果允许其他事务甚至在(尚未提交的)TRUNCATE
之后读取 table,他们会发现它是空的,因为 TRUNCATE
确实清空了 table 并且确实不允许不遵守 MVCC semantics. Such a dirty read(可能尚未回滚的未提交数据)。
如果在重新填充期间您确实需要对 table 的读取权限,您可以使用 DELETE
而不是 TRUNCATE
。不利的一面是,这是一个更昂贵的操作,它会给 table 留下很多必须通过 autovacuum 删除的“死元组”,从而导致很多空的 space (table 臃肿)。但是,如果您愿意忍受 table 和膨胀的索引,以至于 table 和索引扫描至少需要两倍的时间,这是一个选择。