MySQL触发器+ SELECT FOR UPDATE不锁定

JYX*_*JYX 5 mysql sql triggers jmeter jenkins

我\xe2\x80\x99已经得到了一个带有开始和结束时间的预订表,并且两个预订不能重叠。

\n\n

我需要检查新预订是否不会与任何现有预订重叠。然而,我们\xe2\x80\x99的负载非常高,因此存在\xe2\x80\x99的竞争条件:两个重叠的预订都可以成功插入,因为第一个预订是在第二个预订检查重叠之后插入的。

\n\n

I\xe2\x80\x99m 尝试通过使用 BEFORE INSERT 数据库触发器锁定相关资源来解决此问题。

\n\n
DELIMITER //\n\nCREATE TRIGGER booking_resource_double_booking_guard BEFORE INSERT ON booking_resource\nFOR EACH ROW BEGIN\n      DECLARE overlapping_booking_resource_id INT DEFAULT NULL;\n      DECLARE msg VARCHAR(255);\n\n      -- Take an exclusive lock on the resource in question for the duration of the current\n      -- transaction. This will prevent double bookings.\n      DECLARE ignored INT DEFAULT NULL;\n      SELECT resource_id INTO ignored\n        FROM resource\n       WHERE resource_id = NEW.resource_id\n         FOR UPDATE;\n\n      -- Now we have the lock, check for optimistic locking conflicts:\n      SELECT booking_resource_id INTO overlapping_booking_resource_id\n        FROM booking_resource other\n        WHERE other.booking_from < NEW.booking_to\n        AND other.booking_to > NEW.booking_from\n        AND other.resource_id = NEW.resource_id\n        LIMIT 1;\n\n      IF overlapping_booking_resource_id IS NOT NULL THEN\n        SET msg = CONCAT('The inserted times overlap with booking_resource_id: ', overlapping_booking_resource_id);\n       SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = msg;\n     END IF;\n  END\n//\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果我将此触发器放入数据库中并从命令行异步插入两个预订,则此触发器会成功阻止重叠预订。我\xe2\x80\x99已经尝试过在触发器中的最后一个IF语句之前放置SLEEP,以确保锁确实已被取出。

\n\n

但是,我在 Jenkins 中有一个负载测试环境,它使用 jMeter 同时运行大量预订。当我将此触发器放在那里并运行负载测试时,不会捕获重叠的预订,即进行双重预订。

\n\n

我完成了一些检查:

\n\n
    \n
  • 我\xe2\x80\x99已经注销了创建预订时负载测试脚本生成的SQL查询,它与我在命令行中使用的SQL相同。
  • \n
  • 触发器肯定是在负载测试环境中触发的,并且绝对不会捕获任何重叠的预订。我通过将触发器中的 \xe2\x80\x9coverlapping_booking_resource_id\xe2\x80\x9d 变量插入另一个表来确定这一点。所有值均为空。
  • \n
  • 当从命令行插入预订时,触发器在负载测试环境中工作,即它可以防止插入重叠预订。
  • \n
  • 如果我对 \xe2\x80\x9cdouble booking\xe2\x80\x9d 的约束稍微太严格,即相邻预订算作双重预订,那么我确实看到触发器 \xe2\x80\x93 捕获了一些东西也就是说,apache 日志记录了几个错误,消息为 \xe2\x80\x98插入的时间与 booking_resource_id 重叠:\xe2\x80\x99
  • \n
\n\n

我\xe2\x80\x99m想知道锁是否只在触发器结束时才被取出,并且触发器结束和实际插入表之间仍然存在竞争条件。然而,这并不能解释为什么没有重叠的预订被捕获。

\n\n

我\xe2\x80\x99m 现在真的卡住了。有人对我做错了什么有任何想法吗?

\n

小智 0

一种不太优雅但更可靠的方法是使用用于锁定整个系统记录的表。

DELIMITER //

CREATE TRIGGER booking_resource_double_booking_guard BEFORE INSERT ON booking_resource
FOR EACH ROW BEGIN
      DECLARE overlapping_booking_resource_id INT DEFAULT NULL;
      DECLARE msg VARCHAR(255);

      -- Take an exclusive lock on the resource in question for the duration of the current
      -- transaction. This will prevent double bookings.

    ---CHANGED HERE
    REPEAT
      BEGIN
        DECLARE CONTINUE HANDLER FOR SQLWARNING
          BEGIN
            SET locked = FALSE;
          END;
        locked=TRUE;
        INSERT INTO lockresource values(NEW.resource_id);
      END;
    UNTIL LOCKED END REPEAT;
    ---TIL HERE

      -- Now we have the lock, check for optimistic locking conflicts:
      SELECT booking_resource_id INTO overlapping_booking_resource_id
        FROM booking_resource other
        WHERE other.booking_from < NEW.booking_to
        AND other.booking_to > NEW.booking_from
        AND other.resource_id = NEW.resource_id
        LIMIT 1;

      IF overlapping_booking_resource_id IS NOT NULL THEN
        SET msg = CONCAT('The inserted times overlap with booking_resource_id: ', overlapping_booking_resource_id);
       SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = msg;
     END IF;
  END
//

---ADDED FROM HERE 
DELIMITER //

CREATE TRIGGER booking_resource_double_booking_guard_after AFTER INSERT ON booking_resource
FOR EACH ROW BEGIN
        DECLARE CONTINUE HANDLER FOR SQLWARNING BEGIN END;
        delete from lockresource where lockid=NEW.resource_id;
  END
//
Run Code Online (Sandbox Code Playgroud)

不管怎样,这就是想法,并且肯定会防止任何锁定丢失,直到完成验证。