乐观锁认为一个用户读数据的时候,别人不会去写自己所读的数据;
悲观锁就刚好相反,觉得自己读数据库的时候,别人可能刚好在写自己刚读的数据,其实就是持一种比较保守的态度;时间戳就是不加锁,通过时间戳来控制并发出现的问题。

共享锁,又称为读锁,可以查看但无法修改和删除的一种数据锁。
共享锁(S锁)又称为读锁,若事务T对数据对象A加上S锁(共享锁),则事务T只能读A;其他事务只能再对A加S锁(共享锁),而不能加X锁(排他锁),直到T释放A上的S锁(共享锁)。这就保证了其他事务可以读A,但在T释放A上的S锁(共享锁)之前不能对A做任何修改。

时间戳
从数据库行记录里取出一个字段timestamp为$timestamp,执行业务逻辑,存储数据时,如果$newTimestamp=$timestamp+1与数据库字段timestamp值对比,如果$newTimestamp大于数据库字段timestamp值的话,则执行写操作;等于或者小于的话,不执行写操作。

    public function lockTest(){
        $balance = Db::name('balance')->where('id',1)->find();
        $money = $balance['money'] + 3;
        $timestamp = $balance['timestamp'];
        $newTimestamp = $timestamp + 1;
        sleep(20);
        $res = Db::name('balance')->where(array('id'=>1,'timestamp'=>array('<',$newTimestamp)))->update(array('timestamp'=>$newTimestamp,'money'=>$money));
        if($res){
            echo '更新成功';
        }else{
            echo '更新失败';
        }
    }

文件锁

<?php
 
namespace app\common\controller;
use think\Controller;
 
 
/**
 * Block锁类
 */
class Block extends Controller{
 
    private $fps = [];
 
    /**
     * 加锁
     */
    public function file_lock($filename){
        $fp_key = sha1($filename);
        //echo '锁文件';
        $filename = './lock/'.$filename.'.txt';
        $this->fps[$fp_key] = fopen($filename, 'w+');
        if($this->fps[$fp_key]){
            return flock($this->fps[$fp_key], LOCK_EX);
        }
        return false;
    }
 
    /**
     * 解锁
     */
    public function file_unlock($filename){
        $fp_key = sha1($filename);
        //$filename = './lock/'.$filename.'.txt';
        if($this->fps[$fp_key] ){
            flock($this->fps[$fp_key] , LOCK_UN);
            fclose($this->fps[$fp_key] );
        }
    }
}

当执行事务时,相当于执行了锁,来保持数据的一致性,但是锁分多种,有行锁,表锁。行锁就是只锁定那一行,那一条记录,别的连接下的操作还可以操作这张表。表锁就是锁定整张表,只有当前连接执行完事务,才可以解锁。

就效率而然,当然是行锁好,适用与多线程和高并发的情况,不过行锁对数据库会带来额外的开销。表锁高并发就差一点了,但单个的话快一点。

以mysql为例,有索引并且使用了该索引当条件的时候就是行锁,没有索引的时候就是表锁。innodb 的行锁是在有索引的情况下,没有索引的表是锁定全表的.

锁是对于别的连接来说,不是对于当前连接,即当前连可以一直不加rollback,commit,一路更新,但是别的连接就不行,必须等加了锁的连接释放(rollback,commit)后才能更新,插入

建议:对于插入操作,一般加表锁,但是对于修改和删除操作,最好加行锁,这样在高并发时,不用等太久

加锁方式:

select * from testlock where id=1 for update;——查询加锁,查询时不允许更改,该语句在自动提交为off或事务中生效,相当于更改操作,模拟加锁

update testlock name=name;——列=同一个列

更新操作,插入,删除操作,在事务中均属于加锁

以上转载自 https://blog.csdn.net/zyu67/article/details/46669889

下边为tp5事务中加锁验证

① 更新操作,在事务中均属于加锁

   public function test2()
    {
        Db::startTrans();
        try{
            Db::name('test2')->where('id',1)->update(array('test'=>100));
            sleep(10);
            Db::commit();
            echo date('H:i:s');
            echo '执行完毕';
        }catch(\Exception $e){
            Db::rollback();
        }
    }
 
    public function test3(){
        Db::name('test2')->where('id',1)->update(array('test'=>20));
        sleep(10);
        echo date('H:i:s');
        echo '执行完毕';
    }
 
    public function test4(){
        Db::name('test2')->where('id',2)->update(array('test'=>20));
        echo date('H:i:s');
        echo '执行完毕';
    }

test2 16:31:38执行完毕
test3 16:31:48执行完毕 更新未提交需要等待
test4 16:31:16执行完毕 因为采用行锁,所以对其他主键没有影响

② 查询加行锁 防止脏数据

    public function test2()
    {
        Db::startTrans();
        try{
            Db::name('test2')->lock(true)->where('id',1)->find();
            sleep(10);
            Db::commit();
            echo date('H:i:s');
            echo '执行完毕';
        }catch(\Exception $e){
            Db::rollback();
        }
    }
 
    public function test3(){
        Db::name('test2')->where('id',1)->find();
        echo date('H:i:s');
        echo '执行完毕';
    }
 
    public function test4(){
        Db::name('test2')->where('id',1)->update(array('test'=>20));
        echo date('H:i:s');
        echo '执行完毕';
    }

16:43:21执行完毕
16:43:21执行完毕 可以执行查询操作,如果test1事务中执行更新操作,会造成test2数据脏读
16:43:21执行完毕 需等待锁资源释放才能执行更新操作

③ 查询加行锁 需等待锁资源释放,下一个请求才能继续加锁

    public function test2()
    {
        Db::startTrans();
        try{
            $res = Db::name('test2')->lock(true)->where('id',1)->find();
            Db::name('test2')->where('id',1)->update(array('test'=>117));
            sleep(10);
            Db::commit();
            echo date('H:i:s');
            echo '执行完毕';
        }catch(\Exception $e){
            Db::rollback();
        }
    }
 
    public function test4(){
        $res = Db::name('test2')->lock(true)->where('id',1)->find();
        dump($res);
        echo date('H:i:s');
        echo '执行完毕';
    }

test2 22:51:00执行完毕
test3
array(2) {
[“id”] => int(1)
[“test”] => string(3) “117”
}
22:51:00执行完毕

④ 事务中更新数据,不会阻塞其他数据读

    public function test2()
    {
        Db::startTrans();
        try{
            Db::name('test2')->where('id',1)->update(array('test'=>3));
            sleep(10);
            Db::commit();
            echo date('H:i:s');
            echo '执行完毕';
        }catch(\Exception $e){
            Db::rollback();
        }
    }
 
 
    public function test4(){
        $res = Db::name('test2')->where('id',1)->find();
        dump($res);
        echo date('H:i:s');
        echo '执行完毕';
    }

test2 23:37:41执行完毕
test4 array(2) {
[“id”] => int(1)
[“test”] => string(1) “1”
}
23:37:32执行完毕


5.27 比较好的一个加锁方法

 Db::startTrans();
            try {
                /***①减少用户百合***/
                Db::name('user')->where('id', $userId)->setDec('baihe', $orderInfo['count']);
                /***②减少用户百合写入日志***/
                baiheLog($userId, $userInfo['baihe'], -$orderInfo['count'], $userInfo['baihe'] - $orderInfo['count'], 2, '用户在交易大厅出售,减少百合数量', 1);
                /***③更新百合订单交易状态***/
                $res = Db::name('lolli_order')  //事务中执行写操作,会加上锁
                    ->where('id', $orderId)
                    ->where('status',0)
                    ->update($orderInfo);
                if($res){
                    Db::commit();
                }else{
                    throw new \Exception('交易失败,请重新下单');
                }
            } catch (\Exception $e) {
                // 捕获异常回滚事务
                Db::rollback();
                $this->error('交易失败,请重新下单');
            }

一个简单的方法,防止库存超发

创建测试表

CREATE TABLE `test` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `number` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '数量',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='测试表';

注意:number字段设置为 unsigned,如果number存在超发的情况,事物就会回滚

    public function test(){
        Db::startTrans();
        try {
            $res = Db::table('test')->where('id', 3)->setDec('number');  //抛出异常回滚
            Db::table('test')->insert(array('number'=>7));
            Db::commit();
        } catch (\Exception $e) {
            Db::rollback();
        }
    }

一个简单的方法,防止并发添加,同样的记录 多个字段唯一索引

CREATE TABLE `test` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `number` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '数量',
  `goods_id` int(11) NOT NULL DEFAULT '0' COMMENT '商品ID',
  PRIMARY KEY (`id`),
  UNIQUE KEY `number` (`number`,`goods_id`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8 COMMENT='测试表';
    public function test2(){
        Db::startTrans();
        try {
            Db::table('test')->insert(array('number'=>3,'goods_id'=>3));
            Db::commit();
        } catch (\Exception $e) {
            Db::rollback();
            $this->error('发生了一个错误');
        }
    }