高并发访问mysql时的问题(一):库存超减
如果在对某行记录的更新时不采取任何防范措施,在多线程访问时,就容易出现库存为负数的错误.
以下用php、mysql,apache ab工具举例说明:
mysql表结构
CREATE TABLE `yxt_test_concurrence` ( `id` ) NOT NULL AUTO_INCREMENT, `value` ) NOT NULL COMMENT '库存', PRIMARY KEY (`id`) ) ENGINE DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='库存表';
CREATE TABLE `yxt_test_pv` ( `id` ) unsigned NOT NULL AUTO_INCREMENT, `val` ) DEFAULT NULL COMMENT '该线程读取到的库存数量', PRIMARY KEY (`id`) ) ENGINE DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='访问记录表,每次访问都增加一条记录,并记录此次访问时的库存数';
在库存表中存入模拟库存500个.
在此,为方便,php采用TP框架:
public function tc(){
$this->tc = M("test_concurrence");//模拟商品的剩余数量
$this->pv = M("test_pv");//模拟访问次数
$res=$this->tc->field('value')->find(1);//查到的剩余数量
$value=$res['value'];
if($value>0){//如果大于0,则进行下面的逻辑
$this->pv->data(array('val'=>$value))->add();//这个是用来记录访问的次数,并记录此次访问时的库存数
M()->execute("UPDATE `yxt_test_concurrence` SET `value`=`value` - 1 WHERE `id` = 1"); //商品数量减1
}
}
使用ab工具模拟并发访问:
C:\Users\chenhui>ab -c 50 -n 500 http://study.com/course/Course/tc/
This is ApacheBench, Version 2.3 <$Revision: 1554214 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking studyyxtcmf.com (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Finished 500 requests
Server Software: Apache/2.4.9
Server Hostname: studyyxtcmf.com
Server Port: 80
Document Path: /course/Course/tc/
Document Length: 25786 bytes
Concurrency Level: 50
Time taken for tests: 60.035 seconds
Complete requests: 500
Failed requests: 450
(Connect: 0, Receive: 0, Length: 450, Exceptions: 0)
Total transferred: 12973630 bytes
HTML transferred: 12785130 bytes
Requests per second: 8.33 [#/sec] (mean)
Time per request: 6003.543 [ms] (mean)
Time per request: 120.071 [ms] (mean, across all concurrent requests)
Transfer rate: 211.03 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 2.1 1 34
Processing: 781 5915 1578.6 5996 12272
Waiting: 765 5901 1581.8 5983 12261
Total: 783 5916 1578.4 5997 12272
Percentage of the requests served within a certain time (ms)
50% 5997
66% 6385
75% 6707
80% 6850
90% 7387
95% 8402
98% 9734
99% 10300
100% 12272 (longest request)
查看数据库记录:
SELECT * from yxt_test_pv; --截取一段记录(左边是第几次访问,右侧是当次访问看到的库存) | | | | | | | | | | | | |
可以发现在341-343次读取的库存数量是一样的,在库存还很多的情况时,并不会出现问题:因为程序中减库存的逻辑,是当前库存量减去1.但是库存不多的时候,就很可能出现问题,比如库存只有一个了,而此时有多个线程查询到此时还有一个库存,因为1>0满足条件,所以库存减1,多个线程都对当前库存减1,最后就多减了库存,出现负数,这是不允许的.
所以一定要采取措施.
我认为,总的原则是:对于某一个时刻的库存,只允许一个会话去修改.要满足此条件.有两种选择:
1.对于某一个时刻的库存,只允许一个会话去读取(锁机制).待锁被释放后,其他会话才可以读取库存.
2.对于某一个时刻的库存,设定版本(即增加一个版本字段,用于比较.我对版本的理解是刻个记号),更新库存时要判断版本是否发生变化,若没发生变化,则更新库存的同时,更新版本号.若更新库存时发现版本发生变化了,那一定是有别的线程早已对库存修改,此情况下就放弃修改.
选择1.使用mysql的锁机制.(悲观锁)
public function tc(){
$this->tc = M("test_concurrence");//模拟商品的剩余数量
$this->pv = M("test_pv");//模拟访问次数
//对表加锁,注意,如果加锁过程中要操作多个表,要对这几个表都加锁,否则会报错
//mysql> lock table yxt_test_concurrence read;--只锁了一张表
//Query OK, 0 rows affected (0.00 sec)
//mysql> SELECT * from yxt_test_pv;--读取没有被锁的表
//ERROR 1100 (HY000): Table 'yxt_test_pv' was not locked with LOCK TABLES--报错,提示查询的表没有被锁住
M()->execute("lock tables yxt_test_concurrence write,yxt_test_pv write;");
$res=$this->tc->field('value')->find(1);//查到的剩余数量
$value=$res['value'];
if($value>0){//如果大于0,则进行下面的逻辑
$this->pv->data(array('val'=>$value))->add();//这个是用来记录访问的次数
M()->execute(
"UPDATE `yxt_test_concurrence` SET `value`=`value` - 1 WHERE `id` = 1");
//商品数量减1
}
//解锁
M()->execute("unlock tables");
}
采用锁机制,可以严格控制库存数量的变化,但是采用锁会增加数据库的开销.
选择2.版本控制(乐观锁)
乐观锁,是假定事务之间是互不干扰的,事务在访问数据的时候,并不会获取锁,但是,在提交前,每个事务都要确保其他事务并没有修改他读取到的数据.如果在更新数据时发现其他事务已经修改了数据,则回滚提交.乐观锁经常用于"低争用数据结构"的场景中.当冲突特别少的时候,事务可以在完成时,不需要管理锁的开销及等待其他事务释放锁,这可以带来更高的吞吐率.但是,如果对于数据的争用特别频繁,重新开启一个新事务的开销会明显影响性能.
通常认为,其他并发控制方法,在此情况下会有更好的表现,然而,基于悲观锁的方法,会导致较差的性能.因为即使死锁可以避免,"锁"仍会极大的影响并发性能.(我想应该是因为会话被阻塞,从而导致只能串行访问数据库)
以上定义摘自wiki:https://en.wikipedia.org/wiki/Optimistic_concurrency_control
这种情况下,如果并发访问,则修改失败的几率会较高,
举例:在热销产品场景下则容易出现购买失败的情况.这对用户的体验是不好的.因为这意味着又要重新尝试一次.
小结:应该采取哪一种锁,应根据实际场景来权衡利弊,如果更新的很频繁,那应该使用悲观锁.此刻需要考虑的问题是:如何解决并发问题.如果很少更新,则使用乐观锁更为方便省事.
高并发访问mysql时的问题(一):库存超减的更多相关文章
- [转]高并发访问下避免对象缓存失效引发Dogpile效应
避免Redis/Memcached缓存失效引发Dogpile效应 Redis/Memcached高并发访问下的缓存失效时可能产生Dogpile效应(Cache Stampede效应). 推荐阅读:高并 ...
- mysql悲观锁处理赠品库存超卖的情况
处理库存超卖的情况前,先了解下什么是乐观锁和悲观锁,下面的几篇博客已经介绍的比较详细了,我就不在赘述其原理了 [MySQL]悲观锁&乐观锁 对mysql乐观锁.悲观锁.共享锁.排它锁.行锁.表 ...
- ql Server 高频,高并发访问中的键查找死锁解析
死锁对于DBA或是数据库开发人员而言并不陌生,它的引发多种多样,一般而言,数据库应用的开发者在设计时都会有一定的考量进而尽量避免死锁的产生.但有时因为一些特殊应用场景如高频查询,高并发查询下由于数据库 ...
- Sql Server 高频,高并发访问中的键查找死锁解析
死锁对于DBA或是数据库开发人员而言并不陌生,它的引发多种多样,一般而言,数据库应用的开发者在设计时都会有一定的考量进而尽量避免死锁的产生.但有时因为一些特殊应用场景如高频查询,高并发查询下由于数据库 ...
- memcached缓存失效时的高并发访问问题解决
memcached一般用于在访问一些性能相对低下的数据接口时(如数据库),为了保证这些数据接口的稳定性,加上memcached以减少访问次数,保证这些数据接口的健壮性.一般memcached的数据都是 ...
- OpenResty + Lua访问Redis,实现高并发访问时的毫秒级响应打回
一.lua中redis的配置依赖: 1.OpenResty的lua访问redis的插件:https://github.com/openresty/lua-resty-redis 二.下载后,导入对应的 ...
- Java集群--大型网站是怎样解决多用户高并发访问的
时间过得真快,再次登录博客园来写博,才发现距离上次的写博时间已经过去了一个月了,虽然是因为自己找了实习,但这也说明自己对时间的掌控能力还是没那么的强,哈哈,看来还需不断的努力啊!(这里得特别说明一下本 ...
- 大数据量高并发访问SQL优化方法
保证在实现功能的基础上,尽量减少对数据库的访问次数:通过搜索参数,尽量减少对表的访问行数,最小化结果集,从而减轻网络负担:能够分开的操作尽量分开处理,提高每次的响应速度:在数据窗口使用SQL时,尽量把 ...
- jemter模拟高并发访问(亲测ok)
https://blog.csdn.net/a574258039/article/details/19549407
随机推荐
- SpringMVC学习系列(2) 之 经典的HelloWorld实现
前一篇简单介绍了Spring MVC的一些知识,下面就要开始学习如何把Spring MVC运用到具体的项目中去. 首先还是从一个简单的Hello World项目说起: 我机器的开发环境为: Ubunt ...
- ios实现屏幕旋转的方法
1.屏蔽AppDelegate下面的屏幕旋转方法 #pragma mark - 屏幕旋转的 //- (UIInterfaceOrientationMask)application:(UIApplica ...
- 关闭BrowserLink-解决异常/arterySignalR/ping未找到
在使用VS2013 MVC5开发时经常在浏览器的调试窗口看到错误信息,并且每隔两分钟就会出现错误提示:"/365e6ccac83b4cceadee2752a93b81ae/arterySig ...
- 升级NC6.3
2014-04-23 江苏建工&用友公司会谈提纲 1,合同规定江苏建工用友NC在实施成功之后三年免服务费(2010年增补了资金管理,如果以2010年作为软件最终实施完成,那么2010-2013 ...
- FC 坦克大战 老巢铁墙
老巢外围铁墙E2A9:AC 80 EFEF80:A5 10 85 45 A5 45 AC D2 E2 用十六进制编辑器打开坦克大战的游戏文件搜索A5 45 F0 25 A5 0B改为AC 80 EF ...
- asp.net 运行时,"未能映射路径"
asp.net 站点出现:未能映射路径,解决方案之一:发现原来是iis 应用程序池中设置了.net framework 版本为4.0了,而且VS中站点的版本为2.0引起的. 解决方案是把VS 中的站点 ...
- httpd的警告
1. httpd: apr_sockaddr_info_get() failed for serv05 这个是因为httpd.conf文件没有定义ServerName,所以会用hostname来代替, ...
- X-Cart 学习笔记(三)X-Cart框架2
目录 X-Cart 学习笔记(一)了解和安装X-Cart X-Cart 学习笔记(二)X-Cart框架1 X-Cart 学习笔记(三)X-Cart框架2 X-Cart 学习笔记(四)常见操作 3.了解 ...
- postgresql数据类型转换
PostgreSQL数据类型转换需要使用语法 alter table tbname alter column fieldname type date_type 遇到需要转换为特殊类型如DATE.BOO ...
- 使用Jmeter录制脚本
相对于LoadRunner跟SilkPerformer来说,Jmeter确实有差距,但毕竟前两者太贵,Jmeter胜在免费开源. 先看下LoadRunner录制的脚本如下,美如画,结构清晰,易于修改编 ...