zoukankan      html  css  js  c++  java
  • PHP_MySQL高并发加锁事务处理

    1、背景:

    现在有这样的需求,插入数据时,判断test表有无username为‘mraz’的数据,无则插入,有则提示“已插入”,目的就是想只插入一条username为‘mraz’的记录。

    2、一般程序逻辑如下:

        $conn = mysqli_connect('127.0.0.1', 'root', '111111') or die(mysqli_error());  
          
        mysqli_select_db($conn, 'mraz');  
          
        $rs = mysqli_query($conn, 'SELECT count(*) as total FROM test WHERE username = "mraz" ');  
        $row = mysqli_fetch_array($rs);  
        if($row['total']>0){  
            exit('exist');  
        }  
          
        mysqli_query($conn, "insert into test(username) values ('mraz')");  
        var_dump('error:'.mysqli_errno($conn));  
        $insert_id = mysqli_insert_id($conn);  
          
        echo 'insert_id:'.$insert_id.'<br>';  
          
        mysqli_free_result($rs);  
        mysqli_close($conn);  


    3、一般少量请求的时候,程序逻辑不会有问题。但是一旦高并发请求执行的话,程序并没有按预期执行,会插入多条username为‘mraz’的记录。

    4、解决方案:利用MySQL的FOR UPDATE 语句和事务的隔离性。注意的是FOR UPDATE仅适用于InnoDB,且必须在事务(BEGIN/COMMIT)中才能生效。

    调整代码后如下:

        $conn = mysqli_connect('127.0.0.1', 'root', '111111') or die(mysqli_error());  
          
        mysqli_select_db($conn, 'mraz');  
        mysqli_query($conn, 'BEGIN');  
        $rs = mysqli_query($conn, 'SELECT count(*) as total FROM test WHERE username = "mraz" FOR UPDATE');  
        $row = mysqli_fetch_array($rs);  
        if($row['total']>0){  
            exit('exist');  
        }  
          
        mysqli_query($conn, "insert into test(username) values ('mraz')");  
        var_dump('error:'.mysqli_errno($conn));  
        $insert_id = mysqli_insert_id($conn);  
          
        mysqli_query($conn, 'COMMIT');  
        echo 'insert_id:'.$insert_id.'<br>';  
          
        mysqli_free_result($rs);  
        mysqli_close($conn);  
    

    5、再利用php的curl模拟高并发请求该php脚本,查看数据库会只有一条username为‘mraz’的记录。达到程序执行的预期结果~

    +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    http://www.phpthinking.com/archives/1307

    MySQL  使用 SELECT … FOR UPDATE 做事务写入前的确认

    以MySQL 的InnoDB 为例,预设的 Tansaction isolation level 为 REPEATABLE READ,在 SELECT 的读取锁定主要分为两种方式:

    SELECT … LOCK IN SHARE MODE
    SELECT … FOR UPDATE

    这两种方式在事务(Transaction) 进行当中SELECT 到同一个数据表时,都必须等待其它事务数据被提交(Commit)后才会执行。而主要的不同在于LOCK IN SHARE MODE 在有一方事务要Update 同一个表单时很容易造成死锁 。

    简单的说,如果SELECT 后面若要UPDATE 同一个表单,最好使用 SELECT … UPDATE。

    举个例子:假设商品表单products 内有一个存放商品数量的quantity ,在订单成立之前必须先确定quantity 商品数量是否足够(quantity>0) ,然后才把数量更新为1。

    不安全的做法:

    1 SELECT quantity FROM products WHERE id=3;
    1 UPDATE products SET quantity = 1 WHERE id=3;

    为什么不安全呢?

    少量的状况下或许不会有问题,但是大量的数据存取「铁定」会出问题。

    如果我们需要在 quantity>0 的情况下才能扣库存,假设程序在第一行 SELECT 读到的 quantity 是 2 ,看起来数字没有错,但是当MySQL 正准备要UPDATE 的时候,可能已经有人把库存扣成 0 了,但是程序却浑然不知,将错就错的 UPDATE 下去了。

    因此必须透过的事务机制来确保读取及提交的数据都是正确的。

    于是我们在MySQL 就可以这样测试:(注1)

    1 SET AUTOCOMMIT=0;
    2 BEGIN WORK;
    3 SELECT quantity FROM products WHERE id=3 FOR UPDATE;

    此时 products 数据中 id=3 的数据被锁住(注3),其它事务必须等待此次事务提交后才能执行 SELECT * FROM products WHERE id=3 FOR UPDATE (注2)如此可以确保 quantity 在别的事务读到的数字是正确的。

    1 UPDATE products SET quantity = '1' WHERE id=3 ;
    2 COMMIT WORK;

    提交(Commit)写入数据库,products 解锁。

    注1:BEGIN/COMMIT 为事务的起始及结束点,可使用二个以上的MySQL Command 视窗来交互观察锁定的状况。

    注2:在事务进行当中,只有SELECT … FOR UPDATE 或LOCK IN SHARE MODE 同一笔数据时会等待其它事务结束后才执行,一般SELECT … 则不受此影响。

    注3:由于InnoDB 预设为Row-level Lock,数据列的锁定可参考这篇。

    注4:InnoDB 表单尽量不要使用LOCK TABLES 指令,若情非得已要使用,请先看官方对于InnoDB 使用LOCK TABLES 的说明,以免造成系统经常发生死锁。

    MySQL SELECT … FOR UPDATE 的 Row Lock 与 Table Lock

    上面介绍过SELECT … FOR UPDATE 的用法,不过锁定(Lock)的数据是判别就得要注意一下了。由于InnoDB 预设是Row-Level Lock,所以只有「明确」地指定主键,MySQL 才会执行 Row lock (只锁住被选取的数据) ,否则MySQL 将会执行 Table Lock (将整个数据表单给锁住)。

    举个例子:

    假设有个表单products ,里面有id 跟name 二个栏位,id 是主键。

    例1: (明确指定主键,并且有此数据,row lock)

      SELECT FROM products WHERE id='3' FOR UPDATE;

    例2: (明确指定主键,若查无此数据,无lock)

      SELECT FROM products WHERE id='-1' FOR UPDATE;

    例2: (无主键,table lock)

      SELECT FROM products WHERE name='Mouse' FOR UPDATE;

    例3: (主键不明确,table lock)

      SELECT FROM products WHERE id<>'3' FOR UPDATE;

    例4: (主键不明确,table lock)

      SELECT FROM products WHERE id LIKE '3' FOR UPDATE;

    注1: FOR UPDATE 仅适用于InnoDB,且必须在事务区块(BEGIN/COMMIT)中才能生效。

    注2: 要测试锁定的状况,可以利用MySQL 的Command Mode ,开二个视窗来做测试。

  • 相关阅读:
    保持URL不变和数字验证
    centOS ftp key?
    本地环境测试二级域名
    linux 解决You don't have permission to access 问题
    php smarty section loop
    php header Cannot modify header information headers already sent by ... 解决办法
    linux部分命令
    Linux 里面的文件操作权限说明
    用IT网络和安全专业人士视角来裁剪云的定义
    SQL Server 2008 R2炫酷报表"智"作有方
  • 原文地址:https://www.cnblogs.com/taozi32/p/9226376.html
Copyright © 2011-2022 走看看