mysql 高并发 死锁_高并发、死锁、幂等性问题

一.介绍

在短时间之内对数据表(库)的集中访问,就称为“高并发”。

高并发 在使用的时候容易出现问题,

在短时间之内对数据表有大量的集中修改操作,如果不做控制,数据表的修改容易出现重复。

比如:

多个人操作获得的剩余量(95)是一致的(操作的时间点是同一个)

操作完毕对剩余量做减少操作,多人减少的数额(94)也是一样的

这样数据库剩余的数据量就不准确

新建数据表:

create table gbf_hot_goods(

id MEDIUMINT UNSIGNED notnull auto_increment comment '主键id',

number TINYINT UNSIGNED notnull default 0 comment '库存',

name varchar(32) not null default '' comment '名称',

pricedecimal(10,2) not null default 0 comment '价格',

primary key (id)

)engine=Innodb charset=utf8;

insert into gbf_hot_goods values(null,100,'HUAWEI手机',5450);

正常整理库存:

$pdo= new PDO('mysql:host=127.0.0.1;dbname=demo','root','');

$pdo->query('set names utf8');//设置字符集//模拟购买商品,购买前先判断库存,购买后库存做减少操作

$sql = 'select number from gbf_hot_goods';

$qry= $pdo->query($sql);

$rst= $qry->fetch();//取出值

$num = $rst['number'];

$num= $num - 3;//每次购买3个

$sql2= 'update gbf_hot_goods set number='.$num;

$pdo->exec($sql2);?>

结果:

35fde098b6aa9e14da9cd4e0e73a2e6a.png

二.模拟高并发处理

使用apache自带的小工具ab.exe进行模拟:

windows的cmd下apache/bin下> ab.exe -c 人数  -n 总请求数目 地址

ab.exe -c 10 -n 10 http://local-demo.cn/note/php/demo/high/demo.php

执行

This is ApacheBench, Version 2.3 Copyright1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/

Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking local-demo.cn (be patient).....done

Server Software: Apache/2.4.23Server Hostname: local-demo.cn

Server Port:80Document Path:/note/php/demo/high/demo.php

Document Length:0bytes

Concurrency Level:10Time takenfor tests: 2.030seconds

Complete requests:10Failed requests:0Total transferred:2220bytes

HTML transferred:0bytes

Requests per second:4.93 [#/sec] (mean)

Time per request:2030.309[ms] (mean)

Time per request:203.031[ms] (mean, across all concurrent requests)

Transfer rate:1.07 [Kbytes/sec] received

Connection Times (ms)

min mean[+/-sd] median max

Connect:1 1 0.5 1 2Processing:15 524 698.3 44 1989Waiting:13 523 698.2 43 1988Total:16 526 698.2 45 1990Percentage of the requests served within a certain time (ms)50% 45

66% 1025

75% 1031

80% 1034

90% 1990

95% 1990

98% 1990

99% 1990

100% 1990 (longest request)

b9c14a747322d0908c06164d2edc2892.png

请求10次后,number=73.正确应该等于70,所以已经出现了并发性问题。

注意,若等于70的话,可以多试几次,本地环境有可能受性能影响造成数据不准确。

高并发容易造成多个客户端同时处理程序,这样获得的数据结果有重复(错误)

解决:虽然是高并发,但是程序文件必须一个一个执行。

设置锁即可。

锁有两种类型,php的锁、mysql的锁,

定义描述:

共享锁:又叫做读锁,所有人可以读同一个资源,但只有获取锁的人可以对其进行写操作。

排它锁:只有获得锁的对象可以操作资源,在其释放锁之前其他人不能进行任何操作,读都不可以。

策略描述:

悲观锁:在读取数据时锁住那几行,其他对这几行的更新需要等到悲观锁结束时才能继续 。

乐观锁:读取数据时不锁,更新时检查是否数据已经被更新过,如果是则取消当前更新,一般在悲观锁的等待时间过长而不能接受时我们才会选择乐观锁。

mysql表锁:

$pdo= new PDO('mysql:host=127.0.0.1;dbname=demo','root','');

$pdo->query('set names utf8');//设置字符集//查询前先上锁:

$pdo->query('LOCK TABLES `gbf_hot_goods` WRITE');//也可以锁多个表,以,间隔,如:a READ,b WRITE,c READ,d WRITE//模拟购买商品,购买前先判断库存,购买后库存做减少操作

$sql = 'select number from gbf_hot_goods';

$qry= $pdo->query($sql);

$rst= $qry->fetch();//取出值

$num = $rst['number'];

$num= $num - 3;//每次购买3个

$sql2= 'update gbf_hot_goods set number='.$num;

$pdo->exec($sql2);//执行完后释放锁,即解锁

$pdo->query('UNLOCK TABLES');?>

表锁的缺点是:因为同一时间只能有一人对表进行操作,所以会出现阻塞,如果同时锁多张表的话,还会影响整个网站相关表的加载。

mysql行级锁:

共享锁:SELECT `id` FROM  table WHERE id in(1,2)  LOCK IN SHARE MODE。//读锁,不让他人写,一般之后有update操作时用写锁较好

排它锁:SELECT `id` FROM mk_user WHERE id=1 FOR UPDATE。//写锁,不让他人读,须开启事务才有效,且要是innodb类型引擎,明确主键id,并有数据

$pdo= new PDO('mysql:host=127.0.0.1;dbname=demo','root','');

$pdo->query('set names utf8');//设置字符集//mysql行级锁,必须开启事务才有效

$pdo->beginTransaction();//开启事务

$sql = 'select number from gbf_hot_goods id = 1 FOR UPDATE';

$qry= $pdo->query($sql);

$rst= $qry->fetch();//取出值

$num = $rst['number'];if($num == 997){

$pdo->rollback();//回滚的同时也会释放锁

}else{

$num= $num - 3;//每次购买3个

$sql2= 'update gbf_hot_goods set number='.$num.'where id = 1';

$pdo->exec($sql2);

$pdo->commit();//提交的同时也会释放锁

}?>

php锁:

特点:当调用flock锁一个文件时,如果没有获取锁,直接返回FALSE,不会出现阻塞。

故,flock时判断是否等于false

排它锁:flock($fp,LOCK_EX);

共享锁:flock($fp,LOCK_SH);

释放锁:flock($fp,LOCK_UN);

$pdo= new PDO('mysql:host=127.0.0.1;dbname=demo','root','');

$pdo->query('set names utf8');//设置字符集//查询前先上锁://$pdo->query('LOCK TABLES `gbf_hot_goods` WRITE');//php的锁

$fp = fopen('01.php','r');//打开一个实际存在的物理文件,文件可以是空的

flock($fp,LOCK_EX);//上锁//模拟购买商品,购买前先判断库存,购买后库存做减少操作

$sql = 'select number from gbf_hot_goods';

$qry= $pdo->query($sql);

$rst= $qry->fetch();//取出值

$num = $rst['number'];

$num= $num - 3;//每次购买3个

$sql2= 'update gbf_hot_goods set number='.$num;

$pdo->exec($sql2);//执行完后释放锁,即解锁//$pdo->query('UNLOCK TABLES');//释放php锁

flock($fp,LOCK_UN);//解锁

fclose($fp);//释放锁的资源

?>

高并发进行上锁,可以确保所有人都是排队依次对程序进行访问。

避免负数:

$pdo->query('UPDATE warehouse SET `number` = `number` -1 WHERE `number` > 0'); //可以避免库存为负数

三.死锁

`id`  主键索引

`name` index 索引

`age`  普通字段

死锁产生的根本原因:

是两个以上的进程都要求对方释放资源,以至于进程都一直等待。在代码上是因为两个或者以上的事务都要求另一个释放资源。

死锁产生的四个必要条件:

互斥条件、环路条件、请求保持、不可剥夺,缺一不可,相对应的只要破坏其中一种条件死锁就不会产生。

例如下面两条语句:

第一条语句会优先使用`name`索引,因为name不是主键索引,还会用到主键索引。

第二条语句是首先使用主键索引,再使用name索引。

如果两条语句同时执行,第一条语句执行了name索引等待第二条释放主键索引,第二条执行了主键索引等待第一条的name索引,这样就造成了死锁。

解决方法:改造第一条语句 使其根据主键值进行更新

#①

update userset name ='1' where `name`='tom';

#②

update userset name='12' where id=12;//改造后

update user set name='1' where id=(select id from user where name='tom' );

四.幂等

在日常开发中很容易碰到这类问题,如用户重复点击按钮请求、重复请求接口等。这样的后果,就是会生成多条数据或者造成脏数据。

我在日常开发中习惯使用的方法就是使用token加锁、或者判断下是否存在再添加。当然判断是否存在再添加再核心高并发下不建议。所以,使用数据去主键索引或者唯一索引就很必要了。

如下,就是利用第三方工具redis来进行加锁排队判断:

publicfunction execute()

{

$this->time =\time();

declare(ticks=1);

pcntl_signal(SIGTERM, [$this, 'termHandle']);

pcntl_signal(SIGINT, [$this, 'termHandle']);

$status= true;while($status) {if ($this->termFlag === true) {

self::showMsg('进程重启退出');

die;//进程重启退出

}

$lock = self::lock('lock:mnw:task');if ($lock) {

$message= '';try{

$this->start();

}catch(\Throwable $e) {

$message= 'Error line' . $e->getLine().'in' . $e->getFile()

.': ' . $e->getMessage();

}

$status= false;

self::unlock('lock:mnw:task');

self::showMsg(empty($message)? '进程正常退出' : ('进程异常退出:'. $message));

die;

}

self::showMsg('已有其他进程运行等待30秒后再次尝试运行');

sleep(30);

}

}

//加锁

public static function lock($key, $expire = 1800){

$isLock= CashRedis::setnx($key,time()+$expire);if(!$isLock){

$lockTime= CashRedis::get($key);//锁已过期,重置

if($lockTime

self::unlock($key);

$isLock= CashRedis::setnx($key,time()+$expire);

}

}if($isLock) {

CashRedis::expire($key, $expire);return true;

}return false;

}//释放锁

private staticfunction unlock($key){

CashRedis::delete($key);

}

publicfunction termHandle()

{

$this->termFlag = true;

}

上面就是利用redis的setnx方法,有就不操作来进行进程的幂等性处理。

推荐一篇博文,对于幂等性问题的讲解就很详细了

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值