长时间运行的事务“总是”不好,还是这种情况可以?

7 postgresql

我这样做:

BEGIN;
TRUNCATE TABLE test RESTART IDENTITY;
-- Tons of INSERTS to the test table here, taking minutes or many seconds each time.
COMMIT;
Run Code Online (Sandbox Code Playgroud)

这从根本上来说并没有什么问题,不是吗?尽管事务在最终提交之前需要很长时间,但在这种情况下,整个操作只需“原子”,并且不让其他脚本看到空或半填充的测试表,对吧?

在它花很长时间完成的同时,其他查询仍然会继续工作,对吗?这就是我对交易的理解崩溃的地方。在我看来,保证我能够在该事务块运行时从表中进行选择,但是也可以插入、删除和更新吗?

我的大脑无法理解数据库到底如何处理这个问题。这就是所谓的僵局吗?或者他们会继续从“幽灵表”中插入、更新和删除,并且当长时间运行的事务最终提交时所有更改都会丢失?这听起来不对。我假设在该事务运行时它会拒绝对表进行任何“更改”操作,对吗?

是的,我今天实际上又看了一遍关于事务和死锁的手册。有些人能够阅读一次并完全理解某些内容,而其他人(例如我)可能会花很多年并反复说服自己他们已经理解了某些内容,结果却看到记录的“无意义”错误以及实时生产应用程序中发生的坏事由于误解了这样复杂的事情实际上是如何运作的。

Bra*_*don 9

这从根本上来说并没有什么问题,不是吗?尽管事务需要很长时间才能最终提交,

风险是锁。一次只有一个事务可以更新给定的行。造成这种情况的原因可能有多种。几个容易理解的案例是:

  1. 取决于当前状态的更新语句,例如update blah set count = count + 1. 如果两个进程各自读取一行count = 4,各自相加1,产生结果5,然后都写入,会发生什么情况5?其中一位的结果被破坏了。
  2. 索引 -select first_name, last_name from users where last_name = 'Jones';想象 . 上有一个索引last_name。伟大的!我们可以通过扫描索引来优化查询,然后对于每一行,进入表并检索两列。想象一下,一个事务更新了表,但尚未更新索引,然后我们出现并突然select first_name, last_name from users where last_name = 'Jones'; 返回Jane Doe。索引预计与表一致。
  3. 级联删除 - 如果表 B 定义了引用表 A 的外键约束on delete cascade,那么在级联删除发生之前查询表 B 会发生什么情况?

所有这些问题以及更多问题的解决方案就是锁定。Postgres 将锁定该行以进行任何更新,以便对该行的任何其他更新都必须等待锁被释放。在事务中运行时,锁将继续保持,直到事务提交或回滚。

在我看来,保证我能够在该事务块运行时从表中进行选择,但是也可以插入、删除和更新吗?

在 PostgreSQL 中,我相信这是正确的。

我的大脑无法理解数据库到底如何处理这个问题。这就是所谓的僵局吗?

死锁是指多个事务并发运行,每个事务都需要相同的锁,但以不同的顺序请求它们。

进程 A 想要锁定行 X 和 Y。进程 B 想要锁定行 Y 和 X。它们各自继续。进程 A 锁定 X 行,进程 B 锁定 Y 行。现在他们做什么?他们进入了互相等待的状态。因为他们都在互相等待,都没有进步,所以他们永远等待。这就是造成僵局“死局”的原因。这些进程看起来像是死了,因为它们永远处于休眠状态。

或者他们会继续从“幽灵表”中插入、更新和删除,并且当长时间运行的事务最终提交时所有更改都会丢失?这听起来不对。我认为在该事务运行时它会拒绝对表进行任何“更改”操作,对吗?

insert选择应该可以正常工作,因为、update和发生的情况delete是 Postgres 创建行的新版本并跟踪哪些事务看到哪些版本。如果您不在事务中并且只是选择一行,那么您应该在查询时看到该行的最后提交版本。然而,插入、更新和删除将会阻塞,排队等待。

不应有任何错误。在合理的程度上,阻塞是正常的。阻止是数据库保护您的数据。然而,阻塞是一个可扩展性问题,因为它意味着一次只有 1 个事务可以更改某些内容。与所有性能问题一样,这是您的工作负载以及您可以容忍的权衡的问题。阻塞是排除数据库性能故障时遇到的常见问题。

那么长时间运行的事务有什么问题呢?

事务中更新的任何行都可能导致其他事务等待。这些事务也可能持有锁,现在它们已停止,这可能会导致它们阻塞其他事务。这种模式会导致阻塞链,并且在 OLTP 工作负载中会大幅降低性能。

前提很简单:在尽可能短的时间内持有尽可能少的锁。

对于你的情况该怎么办?

这取决于。truncate 语句是在生产环境中运行还是仅在测试环境中运行?是否涉及外键?

奇怪的是,插入内容对你来说要花这么长时间。您可以将它们批处理到单个语句中,或者将行保留在第二个表中,并在截断后执行 吗insert into test select ... from second_table

这实际上取决于它给您带来了什么问题(如果有的话)。如果您的大多数查询都没有使用该表,那么也许您的事务就很好。如果您在生产中遇到阻塞问题(问题定义为影响用户的问题),那么您应该尝试优化插入。