共计 3948 个字符,预计需要花费 10 分钟才能阅读完成。
自动写代码机器人,免费开通
这篇文章给大家介绍使用数据库怎么实现一个订座功能,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。
第一部分:SKIP LOCKED/NOWAIT 订座功能实现
订座在现实生活中是一种很常见的场景,比较常见的有火车票席位选择,电影院席位选择等等。那么如何实现订座功能呢?应用程序可能有很多种不同的实现方式,当然,肯定离不开数据库。这里将介绍一种纯数据库的实现方式。
设想我们有一张座位表如下:
CREATE TABLE seats (
seat_no INT PRIMARY KEY,
booked ENUM(YES , NO) DEFAULT NO ) ENGINE=InnoDB;
表中有 100 个席位,从 0 到 99。例如我们要预定席位 2,3,我们可以先开启事务,锁定席位:
START TRANSACTION;SELECT * FROM seats WHERE seat_no IN (2,3) AND booked = NO FOR UPDATE;
SELECT… FOR UPDATE 语句返回结果有如下三种情况:
1. 返回成功,并且结果集包含 2 和 3,那么说明锁定成功。我们可以之行下一步操作,等待支付完成,并更新席位状态并提交事务,订座完成。UPDATE seats SET booked = YES WHERE seat_no IN (2,3) COMMIT;
2. 返回成功,但结果集为空,或者只包含 2 或者 3,那么说明锁定失败。
3. 很长时间不返回直到返回超时。比如席位 2 或者 3 已经被另一事务锁定,并且在等待支付完成或者发生其他情况,导致该事务一直未提交 (commit) 或者回滚(rollback)。返回超时默认需要等待 50 秒,我们可以通过修改 innodb_lock_wait_timeout 参数来配置合理的等待时间。超时之后返回的错误如下:ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
情况 3 对用户来说,意味着卡死,完全不能接受。为什么会发生等待?在 InnoDB 的锁系统 (lock system) 中,席位 2 如果被一个事务上了 X(写锁)锁或者 IX 锁 (意向更新锁),那么下一个事务要对席位 2 上 X 锁或者 IX 锁的事务,就要等待。这是由事务本身的特性(ACID) 决定的。
那么是否有一种方法避免等待以及后续可能发生的超时呢?MySQL 8.0 提供的新功能 SKIP LOCKED/NOWAIT 就可以。SKIP LOCKED 的意思是跳过那些已经被其他事务锁定了的席位。使用如下 SKIP LOCKED 语句进行席位锁定,那么返回的结果集可能为空,2 或 3,2 和 3。当结果集不为空时,返回的席位即被锁定成功。
SELECT * FROM seats WHERE seat_no IN (2,3) AND booked = NO FOR UPDATE SKIP LOCKED;
NOWAIT 的意思是如果碰到被其他事务锁定的席位,不等待并直接返回错误。使用如下 NOWAIT 语句进行席位锁定,那么返回结果集 2 和 3,要么返回错误。
SELECT * FROM seats WHERE seat_no IN (2,3) AND booked = NO FOR UPDATE NOWAIT;
如果返回错误,如下:
ERROR 3572 (HY000): Do not wait for lock.
如果成功锁定两个席位,通过如下语句查询锁系统的状态:
SELECT thread_id, object_name, lock_type, lock_mode, lock_data, lock_status FROM performance_schema.data_locks;+-----------+-------------+-----------+-----------+-----------+-------------+| thread_id | object_name | lock_type | lock_mode | lock_data | lock_status |
+-----------+-------------+-----------+-----------+-----------+-------------+| 43 | seats | TABLE | IX | NULL | GRANTED |
| 43 | seats | RECORD | X | 2 | WAITING |
| 42 | seats | TABLE | IX | NULL | GRANTED |
| 42 | seats | RECORD | X | 2 | GRANTED |
| 42 | seats | RECORD | X | 3 | GRANTED |
+-----------+-------------+-----------+-----------+-----------+-------------+
SKIP LOCKED 还可以很方便的用来进行随机分配席位。例如我们只需要锁定两个空的席位就可以通过如下语句实现。
SELECT * FROM seats WHERE booked = NO LIMIT 2 FOR UPDATE SKIP LOCKED;
SKIP LOCKED/NOWAIT 功能只针对行锁(record lock),不包括表锁(table lock),元数据锁(metadata lock/MDL)。因此,带有 SKIP LOCKED/NOWAIT 的查询语句依然可能会因为表锁或元数据库锁而阻塞。元数据锁是 MySQL Server 层用来保护数据库对象的并发访问的一致性而创建的,数据库对象不仅包括表,同时包括库,函数,存储过程,触发器,事件等等。表和行锁是 InnoDB 存储引擎内部为了保证事务的一致性而创建的不同粒度的锁。
另外,SKIP LOCKED/NOWAIT 还可以配合 FOR SHARE 使用,并且可以与单表绑定。例如:
SELECT seat_noFROM seats JOIN seat_rows USING ( row_no )WHERE seat_no IN (2,3) AND seat_rows.row_no IN (12)AND booked = NO FOR UPDATE OF seats SKIP LOCKEDFOR SHARE OF seat_rows NOWAIT;
第二部分:SKIP LOCKED/NOWAIT 在 InnoDB 中的代码实现
在 InnoDB 中,实现 SKIP LOCKED/NOWAIT 具体实现如下:
1. 增加新的查询模式 enum select_mode {SELECT_ORDINARY = 0, /* default behaviour / SELECT_SKIP_LOCKED, / skip the row if row is locked / SELECT_NO_WAIT / return immediately if row is locked */};
2. 在查询开始前,设置查询模式 ha_innobase::store_lock(): /* Set select mode for SKIP LOCKED / NO_WAIT */ switch (lock_type) {case TL_READ_SHARED_SKIP_LOCKED: case TL_WRITE_SKIP_LOCKED: m_prebuilt- select_mode = SELECT_SKIP_LOCKED; break; case TL_READ_SHARED_NO_WAIT: case TL_WRITE_NO_WAIT: m_prebuilt- select_mode = SELECT_NO_WAIT; break; default: m_prebuilt- select_mode = SELECT_ORDINARY; break;}
3. 上锁函数中,如果记录已被锁定,针对对不同查询模式进行相应处理:lock_rec_lock_slow(): if (wait_for != NULL) {switch (sel_mode) {case SELECT_SKIP_LOCKED: err = DB_SKIP_LOCKED; break; case SELECT_NO_WAIT: err = DB_LOCK_NOWAIT; break;
4. 查询中对上锁结果进行处理:row_search_mvcc(): case DB_SKIP_LOCKED: goto next_rec; 对 DB_LOCK_NOWAIT 的处理则是回滚当前语句(statement),见函数 row_mysql_handle_errors()。
5. 二级索引 (secondary index) 的处理 在 InnoDB 中,对表中记录的锁定分两种情况。第一种是查询使用是聚集索引(cluster index),那么直接对聚集索引的记录上锁;第二中是查询使用的是二级索引,那么首先对二级索引的记录上锁,然后根据二级索引的记录,找到对应的聚集索引记录进行上锁。所以,对于第一部分订座的席位表中,如果存在二级索引,对于锁定表中一条记录而言,最终锁定成功与否,还是以锁定聚集索引记录为准。
SKIP LOCKED/NOWAIT 可以非常高效地实现订座这个场景,作为 InnoDB 部分(WL#8919: InnoDB: Implement NOWAIT and SKIP LOCKED)的原作者,我也期待着大家来分享该功能更多的使用场景。
关于使用数据库怎么实现一个订座功能就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。
向 AI 问一下细节