在数据库中如何高效的实现订座功能

前言

本文主要给大家介绍了关于在数据库中如何高效的实现订座功能的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧

第一部分: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)的原作者,我也期待着大家来分享该功能更多的使用场景。

参考链接:

1.MySQL 8.0.1: Using SKIP LOCKED and NOWAIT to handle hot rows

2.WL#3597: Implement NOWAIT and SKIP LOCKED

3.WL#8919: InnoDB: Implement NOWAIT and SKIP LOCKED

4.WL#6657: PERFORMANCE_SCHEMA, DATA LOCKS

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • 基于jquery实现在线选座订座之影院篇

    先给大家展示效果图(支持源码下载哦): 查看演示          源码下载 我们在线购票时(如电影票.车票等)可以自己选座.开发者会在页面上列出座次席位,用户可以一目了然的看到可以选择的座位及支付.本文以电影院购票为例,为您展示如何选座.处理选座数据等. 在这里,我给大家介绍一款基于jQuery的在线选座插件:jQuery Seat Charts,它支持自定义座位类型和价格,支持自定义样式,支持设置不可选的座位,也支持键盘控制座位. HTML 我们假设进入电影<星际穿越>的选座页面,页面布局

  • 在数据库中如何高效的实现订座功能

    前言 本文主要给大家介绍了关于在数据库中如何高效的实现订座功能的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧 第一部分:SKIP LOCKED/NOWAIT订座功能实现 订座在现实生活中是一种很常见的场景,比较常见的有火车票席位选择,电影院席位选择等等.那么如何实现订座功能呢?应用程序可能有很多种不同的实现方式,当然,肯定离不开数据库.这里将介绍一种纯数据库的实现方式. 设想我们有一张座位表如下: CREATE TABLE seats ( seat_no INT PR

  • Java语言实现对MySql数据库中数据的增删改查操作的代码

    简单说操作的步骤: 1.连接数据库 2.将SQL语句发送到数据库 3.执行SQL语句 这里举个例子: 在一个数据库中有个students表,表中有学号(Id),姓名(Name),性别(Sex),地址(Address),电话(Phone),专业(Dept). 这里把这个表写成一个学生信息类(Info_student) (请先确保看了例子说明,不然代码有的地方可能看不明白) 要实现操纵我们首先得连接数据库,因为每个操作都要进行连接操作,所以我们直接把连接的操作封装在一个类中,需要连接的时候直接调用可

  • SQL Server数据库中批量导入数据的四种方法总结

    在软件项目实施的时候,数据导入一直是项目人员比较头疼的问题.其实,在SQL Server中集成了很多成批导入数据的方法.有些项目实施顾问头疼的问题,在我们数据库管理员眼中,是小菜一碟.现在的重点就是,如何让用户了解这些方法,让数据导入变得轻松一些. 第一:使用Select Into语句 若企业数据库都采用的是SQL Server数据库的话,则可以利用Select Into语句来实现数据的导入.Select Into语句,他的作用就是把数据从另外一个数据库中查询出来,然后加入到某个用户指定的表中.

  • Oracle数据库中SQL开窗函数的使用

    开窗函数:在开窗函数出现之前存在着很多用 SQL 语句很难解决的问题,很多都要通过复杂的相关子查询或者存储过程来完成.为了解决这些问题,在 2003 年 ISO SQL 标准加入了开窗函数,开窗函数的使用使得这些经典的难题可以被轻松的解决.目前在 MSSQLServer.Oracle.DB2 等主流数据库中都提供了对开窗函数的支持,不过非常遗憾的是 MYSQL 暂时还未对开窗函数给予支持. 开窗函数简介:与聚合函数一样,开窗函数也是对行集组进行聚合计算,但是它不像普通聚合函数那样每组只返回一个值

  • 详解如何在ASP.NET Core中编写高效的控制器

    通过遵循最佳实践,可以编写更好的控制器.所谓的"瘦"控制器(指代码更少.职责更少的控制器)更容易阅读和维护.而且,一旦你的控制器很瘦,可能就不需要对它们进行太多测试了.相反,你可以专注于测试业务逻辑和数据访问代码.瘦控制器的另一个优点是,它更容易维护控制器的多个版本. 这篇文章讨论了使控制器变胖的坏习惯,然后探索了使控制器变瘦和易于管理的方法.我列出编写控制器的最佳实践可能并不全面,但我已经讨论了最重要的一些,并在适当的情况下提供了相关的源代码.在接下来的几节中,我们将研究什么是胖控制

  • Python爬虫获取数据保存到数据库中的超详细教程(一看就会)

    目录 1.简介介绍 2.Xpath获取页面信息 3.通过Xpath爬虫实操 3-1.获取xpath 完整代码展示: 总结 1.简介介绍 -网络爬虫(又称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本.另外一些不常使用的名字还有蚂蚁.自动索引.模拟程序或者蠕虫.-一般在浏览器上可以获取到的,通过爬虫也可以获取到,常见的爬虫语言有PHP,JAVA,C#,C++,Python,为啥我们经常听到说的都是Python爬虫,这是

  • Django中Migrate和Makemigrations实操详解

    目录 一.前言 二.migrate和makemigrations详解和实操 1. makemigrations 2. 在协同开发的情况下,有冲突的迁移文件时如何解决? 3. migrate 4. 迁移报错怎么办? 三.迁移生成的外键约束有必要吗 四.反向迁移-inspectdb 一.前言 当我们在django中添加或修改了数据库model后,一般需要执行makemigrations.migrate把我们的model类生成相应的数据库表,或修改对应的表结构.这是非常方便的. 但我们在实际使用中执行

  • Oracle数据库中ora-12899错误的解决方法

    在使用ORACLE的过程中,会出现各种各样的问题,各种各样的错误,其中ORA-12899就是前段时间我在将数据导入到我本地机器上的时候一直出现的问题.不过还好已经解决了这个问题,现在分享一下,解决方案; 出现ORA-12899,是字符集引起的,中文在UTF-8中占3个字节,ZHS16GBK中占2个字节,而源dmp文件字符集是ZHS16GBK库里倒出来的数据,现在要导入到目标字符集为UTF-8的库里,所以会出现ORA-12899 其实只要修改一下ORACLE 的字符集就可以很好的解决这个问题; 但

  • asp.net mvc 从数据库中读取图片的实现代码

    首先是创建一个类,继承于ActionResult,记住要引用System.Web.Mvc命名空间,如下: 复制代码 代码如下: public class ImageResult : ActionResult { public ImageFormat ContentType { get; set; } public Image image { get; set; } public string SourceName { get; set; } public ImageResult(string _

  • mysql数据库中的information_schema和mysql可以删除吗?

    新装的mysql里面有两个数据库:information_schema 和 mysql  .他们是干么用的?可以删除么?当然是不可以删除的. 1.information schema 是mysql系统用的所有字典信息,包括数据库系统有什么库,有什么表,有什么字典,有什么存储过程等所有对象信息和进程访问.状态信息.一旦删除该数据库系统将无法使用. 2.mysql数据库是保存系统有关的权限,对象和状态信息.同样是不能删除的.并且这两个数据库都很小,不占用空间,你为什么要删除呢.? mysql数据库中

随机推荐