专栏首页腾讯数据库技术如何在数据库中高效实现订座功能?

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

提示:公众号展示代码会自动折行,建议横屏阅读。

第一部分: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

腾讯数据库技术团队对内支持微信红包,彩票、数据银行等集团内部业务,对外为腾讯云提供各种数据库产品,如CDB、CTSDB、CKV、CMongo, 腾讯数据库技术团队专注于增强数据库内核功能,提升数据库性能,保证系统稳定性并解决用户在生产过程中遇到的问题,并对生产环境中遇到的问题及知识进行分享。

本文分享自微信公众号 - 腾讯数据库技术(gh_83eebc796d5d)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-07-24

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 关注这些腾讯公号,助你走上人生巅峰(送价值万元的福利)

    来了?鹅厂小编们等你很久了!咱们闲话少叙,今天,10位小编携手为你奉上10份超级大礼: 书籍、技术教程、鹅厂公仔、腾讯云代金券……每位朋友都可以免!费!参与抽...

    腾讯数据库技术
  • 江湖通缉令

    ? 1. Who we are 腾讯TEG云架构平台部数据库&块存储团队为腾讯自研业务和腾讯云提供业界领先的云原生数据库CynosDB/CDB、块存储CBS...

    腾讯数据库技术
  • 比ls快8倍?百万级文件遍历的奇技淫巧

    腾讯数据库技术
  • easy ui的三级联动,两级联动

    斯文的程序
  • 四大组件以及Application和Context的全面理解

    1.概述 ? Context抽象结构 2.用处 1.Context的实现类有很多,但是ContextImpl(后称CI)是唯一做具体工作的,其他实现都是对CI做...

    何时夕
  • 一日一技:不用get获取字典中不存在的Key

    现在问题来了,每次都用 .get方法,虽然能解决问题,但是这样写起来代码不美观。并且实际上,只要key不存在,直接返回 None即可。

    青南
  • 过渡与动画 - 逐帧动画&steps调速函数

    写在前面 上一篇中我们熟悉五种内置的缓动曲线和(三次)贝塞尔曲线,并且基于此完成了缓动效果. 但是如果我们想要实现逐帧动画,基于贝塞尔曲线的调速函数就显得有些无...

    okaychen
  • 27. 减少方差的技术

    • 添加更多的训练数据:这是最简单也是最可靠的一种方式来处理方差,只要你能访问大量的数据并有足够的计算能力来处理它们。

    YingJoy_
  • 27. 减少方差的技术

    YingJoy_
  • 从零开始学 Web 之 CSS3(七)多列布局,伸缩布局

    CSS3中新出现的多列布局 (multi-column) 是传统 HTML 网页中块状布局模式的有力扩充。

    Daotin

扫码关注云+社区

领取腾讯云代金券