ci mysql高并发_高并发访问mysql时的问题(一):库存超减

标签:

如果在对某行记录的更新时不采取任何防范措施,在多线程访问时,就容易出现库存为负数的错误.

以下用php、mysql,apache ab工具举例说明:

mysql表结构

CREATE TABLE`yxt_test_concurrence` (

`id`int(11) NOT NULLAUTO_INCREMENT,

`value`int(11) NOT NULL COMMENT ‘库存‘,PRIMARY KEY(`id`)

) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT=‘库存表‘;

CREATE TABLE`yxt_test_pv` (

`id`int(10) unsigned NOT NULLAUTO_INCREMENT,

`val`int(255) DEFAULT NULL COMMENT ‘该线程读取到的库存数量‘,PRIMARY KEY(`id`)

) ENGINE=MyISAM AUTO_INCREMENT=351 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT=‘访问记录表,每次访问都增加一条记录,并记录此次访问时的库存数‘;

在库存表中存入模拟库存500个.

在此,为方便,php采用TP框架:

public functiontc(){$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.3Copyright 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 * fromyxt_test_pv;--截取一段记录(左边是第几次访问,右侧是当次访问看到的库存)

| 338 | 164 |

| 339 | 164 |

| 340 | 163 |

| 341 | 162 |

| 342 | 162 |

| 343 | 162 |

| 344 | 162 |

| 345 | 157 |

| 346 | 156 |

| 347 | 156 |

| 348 | 153 |

| 349 | 155 |

| 350 | 151 |

可以发现在341-343次读取的库存数量是一样的,在库存还很多的情况时,并不会出现问题:因为程序中减库存的逻辑,是当前库存量减去1.但是库存不多的时候,就很可能出现问题,比如库存只有一个了,而此时有多个线程查询到此时还有一个库存,因为1>0满足条件,所以库存减1,多个线程都对当前库存减1,最后就多减了库存,出现负数,这是不允许的.

所以一定要采取措施.

我认为,总的原则是:对于某一个时刻的库存,只允许一个会话去修改.要满足此条件.有两种选择:

1.对于某一个时刻的库存,只允许一个会话去读取(锁机制).待锁被释放后,其他会话才可以读取库存.

2.对于某一个时刻的库存,设定版本(即增加一个版本字段,用于比较.我对版本的理解是刻个记号),更新库存时要判断版本是否发生变化,若没发生变化,则更新库存的同时,更新版本号.若更新库存时发现版本发生变化了,那一定是有别的线程早已对库存修改,此情况下就放弃修改.

选择1.使用mysql的锁机制.(悲观锁)

public functiontc(){$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

这种情况下,如果并发访问,则修改失败的几率会较高,

举例:在热销产品场景下则容易出现购买失败的情况.这对用户的体验是不好的.因为这意味着又要重新尝试一次.

小结:应该采取哪一种锁,应根据实际场景来权衡利弊,如果更新的很频繁,那应该使用悲观锁.此刻需要考虑的问题是:如何解决并发问题.如果很少更新,则使用乐观锁更为方便省事.

标签:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值