【问题标题】:Prevent race condition and lock tables in hotel booking system PHP/MySQL防止酒店预订系统 PHP/MySQL 中的竞争条件和锁定表
【发布时间】:2017-08-13 18:15:24
【问题描述】:

我正在建立一个酒店式的预订系统,但我迷路了。我在添加预订的确切预订时刻卡住了,我担心接下来的情况。

我的流程非常简单,它允许多个用户完成同一个房间的预订过程,直到其中一个人先于其他人按下预订按钮。预订信息保留在会话中,并且在有人到达最后一步并按“预订”之前永远不会存储在数据库中。在每一步中,系统都会检查房间是否可用,如果没有,则会给出错误页面。

现在的问题是防止竞争条件结合最后一个强制检查是否还有足够的房间:

当用户在最后一步时,他/她选择付款方式并按下预订按钮。检查所选支付选项是否正常(表单数据未更改)后,它会重定向到预订控制器,其中:

预订控制器

<?php
ReservationController extends JControllerLegacy{

public function placeReservation(){

    //Do some checks to prevent direct access, booking existence and user completed all steps

    $reservatioModel = $this ->getModel('Reservation')
    if(!$reservationModel->placeReservation()){
         //set errors and redirect to error page
         return false;
    }

    //booking had been placed, passes the data to payment plugin
}

?>

预订模式:

 <?php
 ReservationModel extends JModelLegacy{

public function placeReservation(){

    //Get all variables and objects

    //Lock the tables to prevent any insert while the last check
    $db ->lockTable(#__bookings);
    $db ->lockTable(#__booking_room);

    //Perform the last mandatory check with tables locked to prevent read the table right one moment before another transaction insert the booking and allowing overbooking. THIS CHECK MUST BE 100% SURE NO ONE ELSE IS MANIPULATING/READING THE TABLE
    if(!$this ->checkRoomsAvailability($data))
        return false;

    try{
        $db ->transactionStart();

        //Add the reservation in the booking table
        if(!$this ->_saveBooking()){
           $db ->rollBack();
           return false;
        }

        //Add the room/s to the middle table #__booking_room
        if(!$this -> _saveRooms())
           $db ->rollBack();
           return false;
        }

        $db ->transactionCommit();

    }catch(Exception $e){

        $db ->rollBack();
        $this ->setError('We were not able to complete your reservation.');
        return false;
    }

    $db ->unlockTables();

}
 ?>

上面提到的表是 InnoDB,#__bookings 保存预订,#__booking_room 保存所选房间的信息(如 avg_price、房间数量等),它有一个外键到 #__rooms 和一个到 #__bookings

我在担心什么?

1 - 在最后一次可用性检查期间,我使用了其他表,而不是两个锁定的表,并且给了我错误。

2 - 当第二个用户的执行到达表锁定点时,它是等到他们获得空闲还是引发错误/异常?我可以捕获异常并将用户重定向到错误页面,但 locket 表并不意味着已放置预订,在保存信息时可能会出现任何问题。

我希望我的工作流程没问题,如果我可以继续,请告诉我,或者我应该改进我的代码。

谢谢

编辑:

需要提及的是,商业理念就像 Expedia、预订等,您可以在每次预订时选择更多的房间类型和单位。事实上,我的房间桌子不是针对特定/真实房间的,而是针对房间类型,如果尚未预订,则每天都有固定数量的可用单元。

【问题讨论】:

    标签: php mysql mysqli race-condition table-locking


    【解决方案1】:

    我的方法会更简单。在存储实际预订的表中,我将有一个用于预订特定房间的外键,并有一个用于字段roomID, dateUNIQUE 索引。这将确保相同的 roomID 不会在同一日期被记录两次。

    接下来,当客户确认预订时,我将按照您当前的操作运行交易中的所有内容。当代码到达最后一个位置并尝试插入新的预订时,如果在另一位客户预订该房间之前的片刻,DB 将不允许您在该日期为该房间插入另一个预订。那时我会:

    • 回滚并抛出错误,或者
    • 如果还有其他相同类型的房间仍然可用,请使用另一个房间(使用自己的 roomID)重试

    【讨论】:

    • 如果每个房间在#__rooms 表中都有一行,并且用户每次预订都在预订房间,那么我可以接受您的回答,但正如您所知,这不是一个好的选择。我想让用户预订多个房间是同一个预订,即使我正在管理具有一定数量的房间类型,而不仅仅是特定房间。
    • @MarcoC 如果用户预订了多个房间,那么每个房间的预订都在自己的记录中。它将在一个单独的表中(不是reservations),并且所有这些记录都将具有相同的reservationID——一个外键。在这种情况下,UNIQUE 索引仍将是 roomID, date。由于您有其他住宿(例如:公寓),也许roomID 不是最好的字段名称,但概念是相同的:保留特定空间的唯一标识符
    • a unique identifier for the specific space being reserved - 最短但最准确的答案
    • @Nils 让我们看一个例子。我有一个有 3 个房间的公寓:2 个特大号房间和 1 个全尺寸房间。房间是按类型而不是按房间单元保存的。我只有两条记录: - ID: 0 TITLE: room king size UNITS: 2 - ID: 1 TITLE: room full size UNITS: 1 如果 2 对夫妇想在同一天预订两个特大号床房,请按照您的评论,由于约束“RoomID,Date”,这是不可能的。由于我的逻辑是基于房间单位的,我需要最后一次检查我是否有足够的单位用于用户想要预订的所有日期。关于唯一标识符的任何建议?
    • 这都是关于单位的定义。如果单位 = 房间,您可以将约束映射到房间和日期。但是如果 room != unit 你将需要另一个单位表并将约束映射到单位和日期。
    【解决方案2】:

    我不能说您的工作流程(即业务领域)是否是正确的选择,因为这取决于您的业务。但从技术上讲,您希望进行预订交易,该交易还提供针对双重预订的检查。最简单的方法可能是一个房间预订日期表,它对房间是和日期有唯一约束。因此,在您的预订交易期间,您还可以在表中插入带有房间 ID 的每一天,唯一约束将确保第二次尝试将失败。

    PS:如果一个表被锁定,PHP 将等待解锁。不会抛出异常(至少如果 x 秒后连接没有关闭)

    【讨论】:

    • 嗨@nils,谢谢你的回答。我编辑了指定业务类型的帖子,并补充说房间选择是每种类型的,因为我也在租公寓,所以有不同数量的单位和类型(特大号床、双床等)
    • 还是一样的答案。您必须确保数据永远不会处于“不可能”状态。约束和事务是你的朋友。如果您只是锁定表,您将确保预订会一个接一个地发生,但这不会阻止您插入重复数据。
    • 有什么方法可以聊天吗?我知道已经一年多了,但我仍然对这个话题感兴趣
    猜你喜欢
    • 1970-01-01
    • 2020-03-14
    • 1970-01-01
    • 1970-01-01
    • 2016-03-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多