PHP 使用MySQL锁和文件锁解决高并发(含ab压力测试方法)

事务锁

不用事务的时候并发的结果

# 库存表
CREATE TABLE `storage` (
 `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
 `number` int(11) DEFAULT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1
# 订单表
CREATE TABLE `order` (
 `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
 `number` int(11) DEFAULT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1

测试:

$pdo = new PDO('mysql:host=127.0.0.1;port=3306; dbname=test','root','test');
# 未使用事务
$sql = "select `number` from storage where id=1 limit 1";
$res = $pdo->query($sql)->fetch();
$number = $res['number'];
if ($number > 0) {
    $sql = "insert into `order` VALUES (null,$number)";
    $order_id = $pdo->query($sql);
    if ($order_id) {
        $sql = "update storage set `number`=`number`-1 WHERE id=1";
        $pdo->query($sql);
    }
}

预设库存是10,执行ab测试查看结果

Apache自带的ab测试工具的使用

Apache安装目录下的bin目录下 运行 命令窗口

输入 ab -V 回车

会列出ApacheBench工具的版本,注意V大写。看到如下界面

说明ab工具可以正常使用

使用ab对网站进行测试

ab -n 12 -c 10 xxx.xxx.com/xxx/xx/x.html

-n 请求的次数        -c 并发数

查看结果

mysql> select * from storage
  -> ;
+----+--------+
| id | number |
+----+--------+
| 1 |   -2 |
+----+--------+
1 row in set (0.00 sec)

mysql> select * from `order`;
+----+--------+
| id | number |
+----+--------+
| 22 |   10 |
| 23 |   10 |
| 24 |   8 |
| 25 |   8 |
| 26 |   7 |
| 27 |   6 |
| 28 |   4 |
| 29 |   3 |
| 30 |   2 |
| 31 |   2 |
| 32 |   2 |
| 33 |   1 |
+----+--------+
12 rows in set (0.00 sec)

得到订单12个,库存减到了-2,显然不符合实际

使用数据库行锁来解决问题

代码修改如下:

$pdo = new PDO('mysql:host=127.0.0.1;port=3306; dbname=test','root','test');
# 使用事务
$pdo->beginTransaction(); // 开启事务
$sql = "select `number` from storage where id=1 for UPDATE "; // 利用for update 开启行锁
$res = $pdo->query($sql)->fetch();
$number = $res['number'];
if ($number > 0) {
    $sql = "insert into `order` VALUES (null,$number)";

    $order_id = $pdo->query($sql);
    if ($order_id) {

        $sql = "update storage set `number`=`number`-1 WHERE id=1";
        if ($pdo->query($sql)) {
            $pdo->commit(); // 提交事务
        } else {
            $pdo->rollBack(); // 回滚
        }
    } else {
        $pdo->rollBack(); // 回滚
    }
}

查看结果

mysql> select * from storage;
+----+--------+
| id | number |
+----+--------+
| 1 |   0 |
+----+------
--+
1 row in set (0.00 sec)

mysql> select * from `order`;
+----+--------+
| id | number |
+----+--------+
| 1 |   10 |
| 2 |   9 |
| 3 |   8 |
| 4 |   7 |
| 5 |   6 |
| 6 |   5 |
| 7 |   4 |
| 8 |   3 |
| 9 |   2 |
| 10 |   1 |
+----+--------+
10 rows in set (0.00 sec)

文件锁

新建一个空的.txt文件

一. 阻塞(等待)模式:(只要有其他进程已经加锁文件,当前进程会一直等其他进程解锁文件)

<?php
// 连接数据库
$con = mysqli_connect("127.0.0.1", "test", "root", "test");
// 查询商品数量是否大于0,大于0才能下单,并减少库存
$fp = fopen("lock.txt", "r");
// 加锁
if (flock($fp, LOCK_EX)) {
    $res = mysqli_fetch_assoc(mysqli_query($con, 'SELECT total FROM shop WHERE id=1 LIMIT 1'));
    if ($res['total'] > 0) {
        mysqli_query($con, 'UPDATE shop SET total=total-1 WHERE id=1');
    }
    // 执行完成解锁
    flock($fp, LOCK_UN);
}
// 关闭文件
fclose($fp);
unset($res);
mysqli_close($con);
?>

二. 非阻塞(等待)模式:(只要有其他进程已经加锁文件,当前进程不会等其他进程解锁文件直接返回)

// 连接数据库
$con = mysqli_connect("127.0.0.1", "test", "root", "test");
// 查询商品数量是否大于0,大于0才能下单,并减少库存
$fp = fopen("lock.txt", "r");
// 加锁
if (flock($fp, LOCK_EX | LOCK_NB)) {
    $res = mysqli_fetch_assoc(mysqli_query($con, 'SELECT total FROM shop WHERE id=1 LIMIT 1'));
    if ($res['total'] > 0) {
        mysqli_query($con, 'UPDATE shop SET total=total-1 WHERE id=1');
    }
    // 执行完成解锁
    flock($fp, LOCK_UN);
}
// 关闭文件
fclose($fp);
unset($res);
mysqli_close($con);

如果连接数据库耗时的话,下面有个简单的小demo,直观的理解下。

demo.php

<?php
$fp = fopen("file_lock.txt", "r");
// 加锁
if (flock($fp, LOCK_EX)) {
    sleep(10);
    echo 1;
    //执行完成解锁
    flock($fp, LOCK_UN);
} else {
    echo 2;
}
// 关闭文件
fclose($fp);

demo2.php

<?php
$fp = fopen("file_lock.txt", "r");
// 加锁(如果改成flock($fp, LOCK_EX | LOCK_NB),demo2.php会直接返回2,否则会等待demo.php执行完返回1)
if (flock($fp, LOCK_EX)) {
    echo 1;
} else {
    echo 2;
}
// 关闭文件
fclose($fp);

同时运行两个文件,然后修改demo2中加锁机制,就可以看出来阻塞(等待)模式和非阻塞(等待)模式的区别了。

但是这样会导致队列堵塞,假如10个人同一秒写入数据库,那就堵塞了,第10个人会等待前9个都执行完才会执行!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

withoutfear

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值