事务如何在读取数据库的上下文中工作?

How do transactions work in the context of reads to the database?

我正在使用事务来更改 SQL 数据库。据我了解,这意味着对数据库的更改将以全有或全无的方式发生。我想知道的是,这对读取有任何保证吗?例如,假设我有一些这样的(伪)代码:

1) start TRANSACTION
2) INSERT INTO users ... // insert some data
3) count = SELECT COUNT(*) FROM ... // count something in the database
4) if count > 10: // do something based on the read
5)     INSERT INTO other_table ... // write based on the read
6) COMMMIT TRANSACTION

在这段代码中,我正在执行一个 INSERT,然后是一个 SELECT,然后根据 SELECT 的结果有条件地执行另一个 INSERT .

所以我的问题是,如果另一个进程在步骤 (3) 和 (5) 之间修改了数据库,那么 count 变量和我的事务会怎样?

如果有区别,我正在使用 PostgreSQL。

我没有在 postgreSQL 上工作,但我想我可以回答你的问题。想想每个查询都是并行的。我这么说是因为有两笔交易:当你插入一个;其他人可以插入b;然后当你检查 b;您是否可以看到新数据取决于您的隔离设置(读取已提交或只是脏读)。

另请注意,在数据库中,有一种称为锁的技术:您可以锁定一个table,以防止在提交您的事务之前更改它。

希望

正如 Xin 指出的那样,这取决于 isolation level

在默认的 READ COMMITTED 级别,来自其他会话的记录在提交时将变得可见;如果您根本没有启动事务,您会看到相同的记录(当然,其他进程会看到您的插入出现在不同的时间)。

使用 REPEATABLE READ,您的查询在事务开始后将看不到其他会话提交的任何记录。但是,虽然您不必担心 SELECT COUNT(*) 的结果在您的交易过程中发生变化,但您不能假设这个结果在您提交时仍然是准确的。

使用 SERIALIZABLE 提供最强有力的保证:如果您的脚本在被授予对数据库的独占访问权限时做正确的事情,那么它会在存在其他可序列化事务的情况下做正确的事情(否则它将失败彻底)。然而,这意味着所有可能会干扰您的事务都必须使用相同的隔离级别(这是有代价的),并且所有事务都必须准备好在序列化失败的情况下重试它们的事务。

当可序列化事务不是一个选项时,您通常通过显式锁定事物以防止并发写入来防止竞争条件。锁定记录的选择通常就足够了,但是您不能完全锁定 COUNT(*); 的结果。在你的情况下,你可能需要 lock the whole table.