文档章节

用PHP+MySQL实现12306购票和退票以及余票查询逻辑

eechen
 eechen
发布于 2017/03/17 07:09
字数 1009
阅读 1840
收藏 5

普通商品的库存之间没有关联性,库存量都是确定的. 火车票跟普通商品不同,同一车次不同路段的车票的库存可能会相互影响. 所以数据库中不应存储某个车次某个路段的余票数量,而应存储该车次该路段已售的车票数量. 也就是普通商品存的是剩下的,而火车票存的是已售的.

车票表:
(车次, 路段(区间), 已售, 限售, 已售座位, 出发日)
(1024, [1,2],     0)
(1024, [1,2,3],   0)
(1024, [1,2,3,4], 0)
(1024, [2,3],     0)
(1024, [2,3,4],   0)
(1024, [3,4],     0)

主键是自增字段,"车次"+"路段"这2个字段组成联合索引,添加唯一约束.
优化并发时,可以考虑根据"出发日"或"车次"进行分表分库.
路段[1,2,3]的含义是:从车站1上车,经过车站2(停站),到车站3下车,编号有序.
如果要给不同的路段配置不同的出票限额,可以添加一个"限售"字段.

购票逻辑:

例如用户购买路段[2,3,4]的车票时,
程序找出跟目标路段[2,3,4]有交集(相交元素>=2)的路段,算法如下:

<?php
function section(array $arr, array &$sub) {
	$size = count($arr);
	if ($size == 1) return;
	for ($i = 2; $i <= $size; $i++) {
		// 从位置0开始,取$i个元素,不影响输入的数组.
		$sub[] = array_slice($arr, 0, $i);
	}
	array_shift($arr); // 删除数组开头元素
	section($arr, $sub); // 递归
}

$all = array(1,2,3,4);
$sub = array();
section($all, $sub); // 找到该车次包含的所有路段
$target = array(2,3,4); // 目标路段
$related = array(); // 目标路段的相关路段(相交路段)
foreach ($sub as $v) {
	$intersect = array_intersect($target, $v);
	if (count($intersect) >= 2) {
		$related[] = $v;
	}
}
var_export($related);
// 得到
[1,2,3]
[1,2,3,4]
[2,3]
[2,3,4]
[3,4]

如果相关路段的票合计小于500(这里假设列车满载为500人),
则用户能够购票,即[2,3,4]这个路段已售的票数+1.

SQL购票逻辑如下(以MySQL为例):

SET AUTOCOMMIT=0;
START TRANSACTION; 或  BEGIN; --开启事务
SELECT 已售 FROM 车票表 WHERE 车次=1024 
AND 路段 IN ('1,2,3', '1,2,3,4', '2,3', '2,3,4', '3,4') FOR UPDATE;
UPDATE 车票表 SET 已售=(已售+1) WHERE 车次=1024 AND 路段='2,3,4';
COMMIT; --提交事务
SET AUTOCOMMIT=1;

其中 select for update 的作用是读上锁(对读出的行上写锁),依赖事务.

select 区间 from 车票表 where 车次=1024 and 区间 like '%2,3,4%';
在本例中得到:
[1,2,3,4]
function foo(array $arr, array &$tmp) {
	$size = count($arr);
	if($size == 1) return;
	for($i=2;$i<=$size;$i++) {
		$tmp[] = array_slice($arr, 0, $i);
	}
	array_shift($arr);
	foo($arr, $tmp);
}
$arr = array(2,3,4);
$tmp = array();
foo($arr, $tmp);
var_export($tmp);
在本例中得到:
[2,3]
[2,3,4]
[3,4]

退票逻辑比购票逻辑简单得多,直接给对应车次,对应区间的已售车票-1即可:

update 车票表 set 已售=(已售-1) where 车次=1024 and 区间='2,3,4';

余票查询逻辑:
例如查询车次1024上区间[2,3,4]的余票:

SELECT 已售 FROM 车票表 WHERE 车次=1024 
AND 路段 IN ('1,2,3', '1,2,3,4', '2,3', '2,3,4', '3,4');

用500减去上述区间已售车票的和就是区间[2,3,4]上的余票.
这里假设列车满载为500人.

座位分配逻辑:
一列火车,其座位都是固定的,存储每列火车的座位的数据表没什么可说的.
至于座位分配,可以在车票表里增加一个"已售座位"的字段.
用户成功购票时,从"待售座位"中取一个分配给用户并更新"已售座位"字段.
"待售座位"为"所有座位"去掉相关路段"已售座位"后的座位.

SET AUTOCOMMIT=0;
BEGIN;
SELECT 已售,已售座位 FROM 车票表 
WHERE 车次=1024 AND 路段 IN ('1,2,3', '1,2,3,4', '2,3', '2,3,4', '3,4') FOR UPDATE;
UPDATE 车票表 SET 已售=(已售+1), 已售座位=CONCAT(已售座位,分配座位) 
WHERE 车次=1024 AND 路段='2,3,4';
COMMIT;
SET AUTOCOMMIT=1;

其中CONCAT相比直接赋值,能够减少传递给MySQL的数据量.
车票表中"已售座位"字段存储"座位编号".
座位表: 座位编号, 列车, 车厢, 座位

 

© 著作权归作者所有

eechen

eechen

粉丝 1021
博文 107
码字总数 55962
作品 1
深圳
私信 提问
加载中

评论(13)

合纵连横
合纵连横
牛逼
eechen
eechen 博主

引用来自“彩虹梦”的评论

呵呵 说不过就要屏蔽? 你也是个人才喔 怪不得你名气这么好
喷子最怕的就是屏蔽功能了,被屏蔽了就没法无脑喷了,哈哈,知乎,干得漂亮!
彩虹梦
彩虹梦
呵呵 说不过就要屏蔽? 你也是个人才喔 怪不得你名气这么好
eechen
eechen 博主

引用来自“彩虹梦”的评论

引用来自“彩虹梦”的评论

引用来自“eechen”的评论

@彩虹梦 你真的理解了?请先理解了再来发表你的高谈阔论,我都懒得跟没细看文章的人解释,果然知乎的用户水平还是要比OSC高,至少知乎上的一名网友明确指出了我的设计存在的问题,我也根据他的修改了设计,而你的吐槽对我来说没有任何改进意义.

请你看评论 , 人家不是已经给你建议了?
他已经告诉你 并发问题了ok?

看来有必要 在谷歌浏览器 下载一个插件来 屏蔽你才行,https://m.gitee.com/shentu-jiazhen/Block-eechen
那好不送
喷子滚吧,不送.
为什么OSC就不出一个类似知乎的屏蔽功能呢?真是纳闷.
eechen
eechen 博主
@彩虹梦 你真的理解了?请先理解了再来发表你的高谈阔论,我都懒得跟没细看文章的人解释,果然知乎的用户水平还是要比OSC高,至少知乎上的一名网友明确指出了我的设计存在的问题,我也根据他的修改了设计,而你的吐槽对我来说没有任何改进意义.
eechen
eechen 博主
@乌龟壳 一个用户购票请求中,都是只能购买指定区间的一张或多张票,所以在一个请求内没有你所说的冲突.
至于多个请求并发时,数据库会使用自己的锁机制进行控制.
一列火车,其座位都是固定的,存储每列火车的座位的数据表没什么可说的.
从"待售座位"中取一个分配给用户并更新"已售座位"字段.
其中"待售座位"为"所有座位"去掉相关区间"已售座位"后的座位.
这个逻辑可以直接放到购票时的select for update事务里:
SET AUTOCOMMIT=0;
BEGIN WORK; --开启事务
select 已售,已售座位 from 车票表 where 车次=1024
and 区间 in ('2,3,4', '2,3', '3,4', '1,2,3,4') for update;
update 车票表 set 已售=(已售+1), 已售座位=CONCAT(已售座位,分配座位)
where 车次=1024 and 区间='2,3,4';
COMMIT WORK; --提交事务
SET AUTOCOMMIT=1;
乌龟壳
乌龟壳

引用来自“乌龟壳”的评论

1. 第一眼看去,一个用户订票必须把整张表给锁了,不然在并发下无法正确运行“统计余票”这个逻辑。
2. 这样设计并未提供哪个订单关联哪张座位的功能
3. 每次订票、退票都要把所有和这趟车相关的行遍历一遍,效率比较差。而且你所说的判断2、3在1、2、3、4中间之类的逻辑是比较难优化的
4. 有一种特殊情况是补票,可能会让这趟车超过比如你设定的500元
5. 票不是一次性放出的,可能是分多次,而且互联网一部分,售票窗一部分那些逻辑,这种设计方案支持不了

我能找到的茬就这些,别问我如果是我要怎么做,没时间去想这么复杂的问题,哈。

引用来自“eechen”的评论

1.不会锁表呀,比如你买[2,3,4]区间的票,那[1,2]区间的行不会被锁,因为[1,2]的票卖了多少跟[2,3,4]完全无关.
2.至于座位分配,可以在表里增加一个"已售座位"的字段,从待售座位中取一个分配给用户并更新已售座位字段即可.
3.退票不需要任何遍历,直接已售-1即可.订票就如1所说,只会操作相关区间,无关区间无需处理.
4.补票的基本都是没有座位的,车厢的走道能容纳这些变数,乘务员也会根据实际情况,来判断是否允许补票.
5.可以支持,把最大的500下调到400,那剩下的100就是可以以后放出的票.
比如现在有499,你锁了[3,4,5][2,3,4,5]等,但是没锁[1,2],那么两个同时买3,4,5和1,2的就会出问题。
座位分配就牵扯到一个问题,既然有记录作为分配的的表,能否通过座位表直接去完成你设计的方案呢?(这是讨论,并非一定哪个好)
退票同1的回复
eechen
eechen 博主

引用来自“乌龟壳”的评论

1. 第一眼看去,一个用户订票必须把整张表给锁了,不然在并发下无法正确运行“统计余票”这个逻辑。
2. 这样设计并未提供哪个订单关联哪张座位的功能
3. 每次订票、退票都要把所有和这趟车相关的行遍历一遍,效率比较差。而且你所说的判断2、3在1、2、3、4中间之类的逻辑是比较难优化的
4. 有一种特殊情况是补票,可能会让这趟车超过比如你设定的500元
5. 票不是一次性放出的,可能是分多次,而且互联网一部分,售票窗一部分那些逻辑,这种设计方案支持不了

我能找到的茬就这些,别问我如果是我要怎么做,没时间去想这么复杂的问题,哈。
1.不会锁表呀,比如你买[2,3,4]区间的票,那[1,2]区间的行不会被锁,因为[1,2]的票卖了多少跟[2,3,4]完全无关.
2.至于座位分配,可以在表里增加一个"已售座位"的字段,从待售座位中取一个分配给用户并更新已售座位字段即可.
3.退票不需要任何遍历,直接已售-1即可.订票就如1所说,只会操作相关区间,无关区间无需处理.
4.补票的基本都是没有座位的,车厢的走道能容纳这些变数,乘务员也会根据实际情况,来判断是否允许补票.
5.可以支持,把最大的500下调到400,那剩下的100就是可以以后放出的票.
乌龟壳
乌龟壳
1. 第一眼看去,一个用户订票必须把整张表给锁了,不然在并发下无法正确运行“统计余票”这个逻辑。
2. 这样设计并未提供哪个订单关联哪张座位的功能
3. 每次订票、退票都要把所有和这趟车相关的行遍历一遍,效率比较差。而且你所说的判断2、3在1、2、3、4中间之类的逻辑是比较难优化的
4. 有一种特殊情况是补票,可能会让这趟车超过比如你设定的500元
5. 票不是一次性放出的,可能是分多次,而且互联网一部分,售票窗一部分那些逻辑,这种设计方案支持不了

我能找到的茬就这些,别问我如果是我要怎么做,没时间去想这么复杂的问题,哈。
红薯
红薯
@eechen 加我微信吧 oschina2012
2014年抢票总结

2014年的抢票捡漏工作已经结束,现对这段时间以来的付出和收获进行总结。过程记录:http://www.cnblogs.com/liweis/p/4150354.html 黄牛与普通人对比 黄牛的工作流程:在极好的网络环境和硬件...

gisweis
2015/02/14
0
0
还在为过年买不到票发愁5款小程序为你助力春运

还在为过年买不到票发愁?5款小程序为你助力春运! 济南微信小程序开发|济南微信小程序开发公司|餐饮小程序|商城小程序|小程序价格|酒店小程序|济南微信小程序开发团队|济南微信小程序开发公...

湃点科技
2018/01/17
0
0
12306 大战“网络黄牛”:淘宝也“救”不了

临近春节,12306与各类刷票软件、网络黄牛的“攻防战”引发社会热议。虽然12306采用了各种反制措施,但屡遭破解的事实,被众多网友点评为“道高一尺,魔高一丈”。专业人士建议,与其陷入无休...

oschina
2014/01/13
9.5K
162
12306的西天取经路 - 春节抢票与PostgreSQL数据库设计思考

背景 马上春节了,又到了火车票的销售旺季,一票难求的问题依旧存在吗? 还记得10年前春节前买火车票得在放票前1天搬个小板凳去排队,对于热门路线,排一个晚上都有可能买不到票。 随着互联网...

无寄语
2016/12/26
59
0
抢票插件开发者责铁道部:不改革会出更多漏洞

今年春运前三天,全国铁路日均接受退票46万张,远高于去年。图为2月2日,北京火车站站前广场退票窗口等候退票的旅客排成长队。 新华社 图 原标题: “‘铁老大’不改革,还会冒出更多的漏洞”...

oschina
2013/02/07
3.1K
38

没有更多内容

加载失败,请刷新页面

加载更多

OSChina 周一乱弹 —— 年迈渔夫遭黑帮袭抢

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @tom_tdhzz :#今日歌曲推荐# 分享Elvis Presley的单曲《White Christmas》: 《White Christmas》- Elvis Presley 手机党少年们想听歌,请使劲...

小小编辑
今天
1K
18
CentOS7.6中安装使用fcitx框架

内容目录 一、为什么要使用fcitx?二、安装fcitx框架三、安装搜狗输入法 一、为什么要使用fcitx? Gnome3桌面自带的输入法框架为ibus,而在使用ibus时会时不时出现卡顿无法输入的现象。 搜狗和...

技术训练营
昨天
5
0
《Designing.Data-Intensive.Applications》笔记 四

第九章 一致性与共识 分布式系统最重要的的抽象之一是共识(consensus):让所有的节点对某件事达成一致。 最终一致性(eventual consistency)只提供较弱的保证,需要探索更高的一致性保证(stro...

丰田破产标志
昨天
8
0
docker 使用mysql

1, 进入容器 比如 myslq1 里面进行操作 docker exec -it mysql1 /bin/bash 2. 退出 容器 交互: exit 3. mysql 启动在容器里面,并且 可以本地连接mysql docker run --name mysql1 --env MY...

之渊
昨天
12
0
python数据结构

1、字符串及其方法(案例来自Python-100-Days) def main(): str1 = 'hello, world!' # 通过len函数计算字符串的长度 print(len(str1)) # 13 # 获得字符串首字母大写的...

huijue
昨天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部