在酒店预订系统 PHP/MySQL 中防止竞争条件和锁表

问题描述 投票:0回答:3

我正在建设一个酒店式的预订系统,我正在迷路。我在添加预订的确切预订时刻卡住了,我担心会出现这种情况。

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

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

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

预订管理员

<?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 - 当第二个用户的执行到达表锁定点时,它是等到他们获得空闲还是引发错误/异常?我可以捕获异常并将用户重定向到错误页面,但小盒表并不意味着保留,它可能在保存信息时发生任何问题。

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

谢谢

编辑:

要提到的是,商业理念就像 Expedia,预订等,您可以在每次预订中选择更多房间类型和单位。事实上,我的房间表不是针对特定/实际房间的,而是针对每天有固定数量的单元的房间类型,如果尚未预订的话。

php mysql mysqli race-condition table-locking
3个回答
1
投票

我的方法会更简单。在存储实际预订的表中,我将有一个用于预订特定房间的外键,并有一个用于字段

UNIQUE
roomID, date
索引。这将确保同一 roomID 不会在同一日期被记录两次。

接下来,当客户确认预订时,我将像您目前所做的那样在交易中运行所有内容。当代码到达最后一个位置并尝试插入新预订时,如果在另一位客户预订该房间之前的片刻,数据库将不允许您为该房间插入该日期的另一个预订。那时候我要么:

  • 回滚并抛出错误,或者
  • 如果还有另一个相同类型的房间可用,请再尝试另一个房间(有自己的
    roomID

0
投票

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

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


-1
投票

我找到了一篇很好的文章,解释了解决并发预订系统中数据库的悲观锁定方法。

https://medium.com/javarevisited/booking-system-with-pessimistic-locks-4ec107e4bd5

© www.soinside.com 2019 - 2024. All rights reserved.