PHP使用数据库的并发问题

2017-10-30  本文已影响0人  ggcoder
<?php
/**
 * 模拟并发下单操作
 *
    CREATE TABLE `counter` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `num` int(11) NOT NULL,
      `version` int(10) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
 * 
 */

function dummy_function()
{
    $conn = mysqli_connect('127.0.0.1','root','','test') or dir(mysqli_error());
    $succ = 0;
    $concounter = 0;//并发数量
    for ($i=0; $i < 1000; $i++) {
        /**
         * 情况1:操作是原子的,所以数据不会有差异
         */
        // mysqli_query($conn,"update counter set num=num+1 where id=1");

        /**
         * 情况2:先查询出结果,再更新
         * 此情况并发时会出现数据差异!!!
         * 在提交更新的时候数据已经被修改了,此时的提交会把别人的提交覆盖!!!
         */
        // $rs = mysqli_query($conn,'select num from counter where id=1');
        // $row = mysqli_fetch_array($rs);
        // mysqli_free_result($rs);
        // $num = $row[0];
        // mysqli_query($conn,"update counter set num=".($num+1)." where id=1");

        /**
         * 增加事务处理看能否解决
         * 事务分为四种不同的隔离级别:
         * read uncommited 会出现脏读
         * read commited 不可重复读
         * repeatable read 可重复读,范围查询时会出现幻读,mysql默认级别
         * serializable 最高级别,可防止脏读、幻读、不可重复读,但可能会引起死锁或性能大大下降
         *
         * 发现增加事务也并没有什么用
         */
        // mysqli_query($conn, 'BEGIN');
        // $rs = mysqli_query($conn,'select num from counter where id=1');
        // $row = mysqli_fetch_array($rs);
        // mysqli_free_result($rs);
        // $num = $row[0];
        // $res = mysqli_query($conn,"update counter set num=".($num+1)." where id=1");
        // if($res==false || mysqli_errno($conn)){
        //  mysqli_query($conn,'rollback');
        // }else{
        //  mysqli_query($conn,'commit');
        //  $succ++;
        // }

        /**
         * 悲观锁(必须开启事务)
         * 悲观锁认为,别人访问正在改变的数据的概率是很高的,因此从数据开始更改时就将数据锁住,
         * 直到更改完成才释放。悲观锁通常由数据库实现(使用SELECT...FOR UPDATE语句)。
         *
         * 由于悲观锁在开始读取时即开始锁定,因此在并发访问较大的情况下性能会变差。对MySQL Inodb来说,
         * 通过指定明确主键方式查找数据会单行锁定,而查询范围操作或者非主键操作将会锁表。 
         */
        // mysqli_query($conn, 'BEGIN');
        // $rs = mysqli_query($conn,'select num from counter where id=1 for update');
        // if($rs==false || mysqli_errno($conn)){
        //  // 回滚事务
        //  mysqli_query($conn, 'ROLLBACK');
        //  // 重新执行本次操作
        //  $i--;
        //  $concounter++;
        //  continue;
        // }
        // $row = mysqli_fetch_array($rs);
        // mysqli_free_result($rs);
        // $num = $row[0];
        // $res = mysqli_query($conn,"update counter set num=".($num+1)." where id=1");
        // if($res==false || mysqli_errno($conn)){
        //  mysqli_query($conn,'rollback');
        // }else{
        //  mysqli_query($conn,'commit');
        //  $succ++;
        // }

        /**
         * 乐观锁
         * 乐观锁认为,别人访问正在改变的数据的概率是很低的,
         * 因此直到修改完成准备提交所做的修改到数据库的时候才会将数据锁住,完成更改后释放。
         *
         * 实验证明当并发较高(此实验的10个并发)时效率较低,实际业务中会频繁报错,用户无法操作。
         * 所以如果确定并发较高的时候还是选择悲观锁比较好
         */
        mysqli_query($conn,'begin');
        $rs = mysqli_query($conn,'select num,version from counter where id=1');
        $row = mysqli_fetch_array($rs);
        mysqli_free_result($rs);
        $num = $row['num'];
        $version = $row['version'];

        // $res = mysqli_query($conn,'update counter set num='.$num.'+1,version=version+1 where id=1 and version='.$version);

        //乐观锁的简便方式
        $res = mysqli_query($conn,'update counter set num='.$num.'+1 where id=1 and num='.$num);

        //----------------
        $affectRow = mysqli_affected_rows($conn);//重要!!!!
        //----------------
        
        if($affectRow==0 || mysqli_errno($conn)){
            //回滚
            mysqli_query($conn,'rollback');
            //重新执行一次
            $i--;
            $concounter++;
            continue;
        }else{
            mysqli_query($conn,'commit');
            $succ++;
        }

    }
    echo 'succ:'.$succ.PHP_EOL;
    echo 'concounter:'.$concounter.PHP_EOL;
    mysqli_close($conn);
}

for ($i=0; $i < 10; $i++) {
    echo $i.PHP_EOL;
    $pid = pcntl_fork();
    if($pid==-1){
        die('can not fork!');
    }else if(!$pid){
        dummy_function();
        echo 'quit '.$i.PHP_EOL;
        break;
    }
}

上一篇下一篇

猜你喜欢

热点阅读