在数据库中怎么高效的实现订座功能

作者:无名 - 数据库其它 -

前言

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

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

总结

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

该篇内容就是由IT人知识库(原文链接:http://www.itpeo.net/12848/522321.html)小编为各位整理





SQL注入的2个小Trick及示例总结

前言 最近发现了两个关于sql注入的小trick,分享一下.下面话不多说了,来一起看看详细的介绍吧 between an... ...

怎么利用分析函数改写范围判断自关联查询详解

前言 最近碰到一个单条SQL运行效率不佳导致数据库整体运行负载较高的问题。 分析、定位数据库的主要负载是这条语句引起的过... ...

SQL注入技巧之显注与盲注中过滤逗号绕过详析

前言 sql注入在很早很早以前是很常见的一个漏洞。后来随着安全水平的提高,sql注入已经很少能够看到了。但是就在今天,还... ...

SQL利用Function创建长整形的唯一ID示例代码

前言 在设计表的时候考虑主键的数据类型是长整形还是字符串,最简单的方式当然是newid(),但这也有个问题,就是主键长度... ...

ETL方法与过程讲解

ETL是将业务系统的数据经过抽取、清洗转换之后加载到数据仓库的过程,目的是将企业中的分散、零乱、标准不统一的数据整合到一... ...

rfedfre

使用sqlplus创建DDL和DML操作技巧

1.连接数据库  在window进入命令行模式敲sqlplus就会    然后进行表的DD... ...

Spark SQL操作JSON字段的小技巧

前言 介绍Spark SQL的JSON支持,这是我们在Databricks中开发的一个功能,可以在Spark中更容易查询... ...

neo4j安装配置入门教程

注:网上找了许多教程,发现都不太适合0基础的用户,所以就自己写了一下。 推荐使用1.x版本,经测试2.3.3大量函数被... ...

rfedfre

Window下Neo4j安装图文教程

Neo4j数据库有两个版本:社区版和商业版,社区版是开源并且免费的,社区版与商业版功能上没有什么区别,不同的是,社区版只... ...

rfedfre

隐式转换引起的sql慢查询实战记录

引言 实在很无语呀,遇到一个mysql隐式转换问题,问了周边的dba大拿该问题,他们居然反问我,你连这个也不知道?白白跟... ...

SQL中where子句与having子句的区别小结

前言: Where和Having都是对查询结果的一种筛选,说的书面点就是设定条件的语句。下面这篇文章就来给大家介绍下S... ...

数据库的四种隔离级别

1,读未提交,Read Uncommited。 指的是一个事务读取到另外一个事务还没有提交的内容。这种情况是必须要避免的... ...

分布式和集群的概述讲解

分布式和集群 集群 集群是一种计算机系统, 它通过一组松散集成的计算机软件和/或硬件连接起来高度紧密地协作完成计算工作。... ...

rfedfre

SQL中过滤条件放on和where中的区别详解

前言 今天接到蚂蚁金服的电面,问了sql中过滤条件放在on和where中的区别,当时满脑子是inner join,觉得没... ...

pymysql怎么解决sql注入问题深入讲解

1. SQL 注入 SQL 注入是非常常见的一种网络攻击方式,主要是通过参数来让 mysql 执行 sql 语句时进行... ...