为什么MariaDB加莱拉集群抛出了一个僵局,而不是等待?

pu3pd22g  于 2023-02-16  发布在  其他
关注(0)|答案(1)|浏览(126)

我试图将我的应用程序从单个服务器数据库移动到Galera集群,此应用程序使用一些FOR UPDATE锁。
考虑以下情况:

create table rooms ( id int not null, owner int null, PRIMARY KEY (`id`));
insert into rooms values (1, null);

我想确保在并发查询的情况下不覆盖房间所有者。只有当当前所有者为NULL时,我才会设置房间中的所有者ID。

    • 第1次测试-单数据库服务器**

TRX1-服务器A

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
SELECT * FROM rooms WHERE id=1 FOR UPDATE;

TRX2-服务器A

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
SELECT * FROM rooms WHERE id=1 FOR UPDATE;
  • 〉此处TRX2处于保持状态,等待TRX1。
    TRX1-服务器A
UPDATE rooms SET owner=1 WHERE id=1;
COMMIT;
  • 〉这里TRX2被释放,房间被从TRX1中更新的所有者取回。这是正确并发处理的当前正确行为。
    • 第2次测试-具有2个数据库服务器的Galera群集**

TRX1-服务器A

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
SELECT * FROM rooms WHERE id=1 FOR UPDATE;

TRX2-服务器B

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
SELECT * FROM rooms WHERE id=1 FOR UPDATE;
  • 〉此处TRX2暂停!
    TRX1-服务器A
UPDATE rooms SET owner=1 WHERE id=1;
COMMIT;

TRX2-服务器B

UPDATE rooms SET owner=2 WHERE id=1;
COMMIT;
  • 〉此处TRX2失败,并出现DEADLOCK错误。
    是否有任何解决方案可以让第二个事务等待第一个trx的提交,即使它正在第二个clusetered服务器上运行?
mkshixfv

mkshixfv1#

加莱拉是乐观锁定。此模式假设提交期间没有冲突,如果有冲突,则在COMMIT SQL上抛出死锁。乐观的假设意味着没有通过集群通信的预锁定机制,只有在提交时才进行通信。
这也意味着,如果另一个加莱拉节点作为事务正在处理本地更新的相同行,则不存在锁等待或早期死锁。
正如所观察到的,这意味着本地事务和跨加莱拉集群中多个节点的事务之间存在行为差异。
由于整个模式是乐观的,虽然您可以通过使用一致的获取锁的顺序来避免本地场景中的死锁,但在加莱拉集群中这是不可能的。最小化技术(如具有与更新查询匹配的正确索引)在Galera中是有效的,因为它们是本地的,所以在所问的问题中,确保id是主键或辅助索引的第一个元素。
死锁处理代码必须在加莱拉集群中完成,以处理冲突。死锁是一个例外,因为SQL不能确定操作的逻辑过程。它取决于业务逻辑来决定在这些情况下发生了什么。由于这是一个关于房间的预订系统,逻辑(由我决定,我是一个没有商业案例意识的观察者),是给予用户错误,他们太慢了,无法预订房间,并提供其他选择。
为了避免死锁,您需要让应用程序在可预测的加莱拉节点上执行房间预订,以获得与锁等待相同的效果。
请注意,select for update .. skip locked是一种在不严格依赖于哪一行的保留中很有用的模式,也仅在本地应用。

相关问题