如何用SQLite从零搭建一个酒店客房预订系统?

来源:Docker教程作者:小诸葛头衔:草根站长
导读:本期聚焦于小伙伴创作的《如何用SQLite从零搭建一个酒店客房预订系统?》,敬请观看详情。客房状态混乱、重复预订、退房结算出错,是小型旅馆管理中最常见的数据痛点。直接用文件或Excel记录房态,并发写入时极易丢失更新。SQLite作为单文件嵌入式数据库,无需独立服务进程,事务支持完整,非常适合单机或轻量联网的酒店前台系统。本文从表结构规划讲起,用房间表、客户表、预订表三个核心实体消除冗余;借助约束与索引防止房号冲突;通过事务包裹入住与记账操作保证金额一致。你会看到如何编写触发器自动释放过期预订,以及用一条联表查询快速产出某日可售房清单。方案兼顾易部署与可维护,前台电脑重装后拷走db文件即可恢复全部业务数据。

构建一个酒店客房预订系统,核心目标是准确记录房间、客户以及两者之间的预订关系,并在任何时刻都能快速回答“今天哪些房能卖”。SQLite以单个文件存储全部数据,支持外键、事务和触发器,足以支撑几十间客房规模的前台日常运营,且免安装、易备份。

如何用SQLite从零搭建一个酒店客房预订系统?

一、数据库设计与建表

在动手写代码前,先理清业务实体。酒店里有“房间”“客户”和“预订”三个主体。房间有编号、类型、价格和状态;客户有姓名、手机和证件号;预订则关联客户与房间,并记录入住、离店时间和支付金额。如果把这些字段全塞进一张表,不仅冗余,还会在客户多次预订时反复拷贝手机号,更新也容易漏。

因此采用三张表,通过外键约束保证引用完整。下面语句在SQLite中开启外键支持并建表,注意SQLite默认不强制外键,需要在连接后执行PRAGMA foreign_keys=ON

PRAGMA foreign_keys=ON;

CREATE TABLE room (
    room_id    INTEGER PRIMARY KEY,
    room_no    TEXT NOT NULL UNIQUE,
    room_type  TEXT NOT NULL,
    price      REAL NOT NULL,
    status     TEXT NOT NULL DEFAULT 'available'
);

CREATE TABLE customer (
    cust_id    INTEGER PRIMARY KEY,
    name       TEXT NOT NULL,
    phone      TEXT,
    id_card    TEXT
);

CREATE TABLE booking (
    book_id    INTEGER PRIMARY KEY,
    cust_id    INTEGER NOT NULL,
    room_id    INTEGER NOT NULL,
    check_in   DATE NOT NULL,
    check_out  DATE NOT NULL,
    amount     REAL NOT NULL,
    created_at TEXT DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (cust_id) REFERENCES customer(cust_id),
    FOREIGN KEY (room_id) REFERENCES room(room_id)
);

CREATE INDEX idx_booking_date ON booking(check_in, check_out);

上面的room_no加了唯一约束,避免两个房间用同一个门牌号。booking表上的联合索引能加速“按日期区间查预订”的语句。SQLite的索引采用B树,对范围查询友好,但也会稍微拖慢写入,客房量少时完全可以接受。

设计上把房间状态字段留在room表里,但实际房态更多是由booking推导出来的。我们保留它是为了前台一眼看到空闲房,后续可用触发器同步,而不是每次都联表算。

二、防止重复预订的事务处理

最怕的场景是两位前台同时点开同一间空房并确认入住,结果系统写出两条重叠的预订。SQLite的事务隔离级别是可串行化,配合立即加锁能挡住这类冲突。写操作时用BEGIN IMMEDIATE先拿写锁,再查再插,最后提交。

下面Python片段演示一次安全入住:先锁库,查目标日期该房是否已被占,没有才插入预订并把房间标为已住。任何一步异常都回滚,房间状态不会被改脏。

import sqlite3

def check_in(db_path, cust_id, room_id, cin, cout, amount):
    conn = sqlite3.connect(db_path)
    try:
        conn.execute('PRAGMA foreign_keys=ON')
        conn.execute('BEGIN IMMEDIATE')
        cur = conn.execute(
            'SELECT 1 FROM booking WHERE room_id=? AND check_in < ? AND check_out > ?',
            (room_id, cout, cin)
        )
        if cur.fetchone():
            raise Exception('该房间在选定日期已被预订')
        conn.execute(
            'INSERT INTO booking(cust_id,room_id,check_in,check_out,amount) VALUES(?,?,?,?,?)',
            (cust_id, room_id, cin, cout, amount)
        )
        conn.execute('UPDATE room SET status='occupied' WHERE room_id=?', (room_id,))
        conn.commit()
    except Exception as e:
        conn.rollback()
        raise e
    finally:
        conn.close()

这种写法把“查重”和“写入”放在同一个事务里,中间不会被别的连接插入数据。SQLite在单文件下用文件锁实现,多进程前台共用同一db文件时也能工作。缺点是写期间别的写操作会阻塞,但酒店前台并发写入很少,体验无影响。

若将来要上Web多实例,可以把db放网络盘或换PostgreSQL;当前结构改动很小,只需把连接串换掉,SQL几乎不变,这也是起步用标准SQL的好处。

三、用触发器自动释放过期预订

客户预订了没来且过了入住日,房间应自动回到可售池。手动跑脚本容易忘,用SQLite触发器在插入后校验太重,更轻的做法是每天开门前执行一条清理,或写AFTER DELETE触发器配合取消操作。这里给出基于日期查询释放的存储过程式脚本,逻辑清晰且可定时跑。

下面的SQL把“离店日小于今天且房间被标占”的房间重置为空闲。放在晨会前由计划任务调用,避免前台人工改状态。

UPDATE room
SET status='available'
WHERE room_id IN (
    SELECT r.room_id
    FROM room r
    LEFT JOIN booking b ON r.room_id=b.room_id
    WHERE r.status='occupied'
      AND (b.check_out < DATE('now') OR b.book_id IS NULL)
);

触发器适合做强制规则,比如禁止离店早于入住:

CREATE TRIGGER chk_date BEFORE INSERT ON booking
BEGIN
    SELECT CASE
        WHEN NEW.check_out <= NEW.check_in THEN
            RAISE(ABORT, '离店日期必须晚于入住日期')
    END;
END;

这样在应用层忘了校验时,数据库仍会抛错,保护底层数据合理。SQLite的RAISE函数能在触发器里中断语句,是兜底手段。

四、产出某日可售房清单

店长常问“五一当天还剩几间大床房”。用左连接把房间和当天预订比对,没命中就是可售。下面查询返回指定日期未被占的房间及价格。

SELECT r.room_no, r.room_type, r.price
FROM room r
LEFT JOIN booking b
  ON r.room_id=b.room_id
 AND b.check_in <= '2024-05-01'
 AND b.check_out > '2024-05-01'
WHERE b.book_id IS NULL
  AND r.room_type='大床房'
ORDER BY r.room_no;

该语句利用LEFT JOIN保留全部房间,再用b.book_id IS NULL过滤掉有重叠预订的房。日期条件写在ON里而非WHERE,否则会先把预订删掉再连,语义错。索引idx_booking_date让区间判断走树查找,几十到几百行数据毫秒出结果。

如果加上客户联表,还能顺带导出“今日续住名单”,把bookingcheck_in早于今日且check_out晚于今日的记录拉出来即可,结构完全一致。

五、备份与日常维护

SQLite单文件优势在备份:直接拷走hotel.db就全量落盘。建议写个脚本每日落盘到另一块盘,并用VACUUM整理碎片。

cp hotel.db /backup/hotel_$(date +%F).db
sqlite3 hotel.db 'VACUUM;'

长时间删除预订会产生空页,VACUUM重建文件缩小体积,也顺带校验结构。对于客房数不过百的店,整个db常驻内存都无压力,查询延迟忽略不计。

当出现可疑写入失败时,用PRAGMA integrity_check跑一遍,SQLite会报页损坏或记录越界,比肉眼查表可靠。结合前面事务与触发器,小酒店系统能稳跑多年不需重构。

SQLite酒店预订系统数据库设计修改时间:2026-08-11 08:57:40

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。