SQL 具有 READ_COMMITED 隔离级别的并发 'select then update' 模式

SQL concurrent 'select then update' pattern with READ_COMMITED isolation level

我们有一个集群 Java 应用程序 运行 在 mysql 5.5 数据库 InnoDb 引擎上运行。该应用程序使用 Spring,事务隔离级别设置为 SERIALIZABLE

在可能 并发执行 的方法中,无论是在一个节点的不同线程中还是在单独的节点中,都有一个典型的 SELECT-then-UPDATE 模式.这是伪代码:

old_status = null;
do {
    old_status = SELECT status 
                 FROM bookings 
                 WHERE code=123;

    affected_rows = UPDATE bookings 
                    SET status=<new_status> 
                    WHERE code=123 AND status=<old_status>;

} while (affected_rows == 0);

// Now we can do stuff with <old_status> value

code是PK; <old_status><new_status>总是 不同。

SELECTUPDATE运行单独交易。

这很好用。我什至认为 AND status=<old_status> 检查 UPDATE 语句的 WHERE 子句是不必要的,因为事务隔离级别是 SERIALIZABLE,这意味着发出SELECT 然后是 UPDATE 而不检查 status 字段的值。

所以,第一个问题:(1) 当事务隔离级别为 SERIALIZABLE 时,我可以不需要所有这些吗?

现在,发生的事情是,几天前,DBA 突然出现在我的工作场所,并带来了 'proved' 我们应该开始使用的参数和指标READ_COMMITED 事务隔离级别。由于他几乎从不离开他的地下墓穴,我立即知道他是认真的。他说,如果我们将事务隔离级别从 SERIALIZABLE 更改为 READ_COMMITED,cpu 的使用率会下降 10%,查询会 运行 快很多,等等。简而言之,他有理由让我们切换到 READ_COMMITED 事务隔离级别。

那么,第二个问题:(2) 我们是否可以愉快地切换到 READ_COMMITED 事务隔离级别,因为我们已经在检查 status 字段在 UPDATE 句子的 WHERE 子句中通过 AND status=<old_status>?

我想我们可以,但我想先与社区核实一下。提前致谢!

注意:我不能使用原生构造,例如mysql的SELECT ... FOR UPDATE等

首先。在序列化隔离中比较旧的状态是不必要的。如果读锁被同一数据元素上的另一个事务的写锁覆盖,或者写锁被延迟,这取决于数据库中使用的实现,序列化事务将失败。

此外,我非常怀疑 10% 的已提交读取声明。

我也不是read committed的朋友。我在一家保险公司工作,那里的新系统全部使用 Read Committed。他们使用 Select 进行更新。作为一名接受过事务管理和数据库(大学主修科目)培训的人,我非常不喜欢对写事务使用读提交隔离。对于仅使用 select 的读取事务,没问题,但重点在哪里。

我认为 read committed 只会为您保存撤消信息(可能)并且会减少事务需要回滚的可能性(令人怀疑)。

还有第三个建议。使用两个数据源。一种使用读取提交隔离级别,第二种使用序列化。使用第二个执行所有写入事务,第一个执行其余操作。

但是你又遇到了各种各样的麻烦。由于使用 read committed,相同的数据有可能会失败。正如我自己所知,与工程师在没有意识到新限制的情况下在事务代码中制作的所有错误相比,10% 是微不足道的。

在您收集的成百上千笔交易中只需要一个错误假设,您很可能会遇到一个错误,这将使您花费数十到数千小时的搜索时间。我知道在我们记录隔离级别并看到拼图中缺失的部分之前从未发现的缺陷。

写交易很容易,但要做好却很难。

所以我的建议是:只要您的数据库的 10% 没有任何性能问题,就不要更改隔离级别。如果您遇到性能问题,请升级您的硬件。只要不是来自 IBM、Oracle 或 HP 或其他公司的硬件,它就很便宜。

并记住 100 小时的时间,您可以将 1 TB 的 RAM 添加到您的机器中(或者 100 GB,如果它来自 IBM、Oracle 或 HP 或其他公司),这 100 GB 将比隔离级别提高您的性能.

除非你需要它或者你是大师,否则不要乱用它。对于保险软件,他们使用 SQL 行进行悲观锁定,并使用 select 行进行更新。每个都是性能杀手,因为它们是瓶颈。

你的解决方案很好,但你不需要比较。

最佳解决方案是:

` [开始阅读交易] select * .... [/结束读取事务]

计算变化

[开始写入事务] 使用 update ... where select ... 进行比较和设置 [结束写交易] `

我认为您在 while 循环中执行此事务处理,否则您的代码不会那么好:)。

如果两者在不同的交易中,您需要比较旧值。您所做的是乐观应用程序事务中的一种简单技术,称为比较和设置。所以是的,你可以随心所欲。

关于 Read committed 隔离级别,它在事务中唯一有趣。如果你只有一个 select 或更新,如果我正确理解 Oracles 实现,它应该没有任何区别。 Oracle 仅访问 table 中的单行一次,并将重用那些已经获得的信息。但它们可能是无法重用这些信息的情况(会话缓存外的条款等)。

因此,这需要高度的理解,不应该在不考虑与特定变化的影响相关的每笔交易的情况下完成,我的意思是所有潜在的变化。因此,如果您希望正确完成此操作,您会发现成本会飙升。

我绝不会轻易更改隔离级别,尤其是如果您没有针对隔离级别进行设计。