理解乐观锁、悲观锁、共享锁、排他锁、表锁、行锁

  • 2019-10-11
  • 浏览 (24)

数据库的乐观锁、悲观锁、共享锁、排他锁、表锁、行锁说明:

1. 乐观锁:乐观锁的实现完全是逻辑的,不需要数据库提供特殊的支持,一般的做法是在需要锁的数据上增加一个版本号

    public function lock()
    {
        //获取id=1该数据行的版本
        $version = Db::table('test')->where('id',1)->value('version');

        //TO DO...(执行其他业务逻辑)
        //如果最后取锁失败,可能需要回撤这些业务逻辑,所以乐观锁适合数据不那么频繁被更新的场景
        
        //更新数据
        $data['user_id'] = 100;
        $data['user_name'] = 'guangdong';
        $data['version'] = $version + 1;
        //更新条件:id=1 version=$version
        //如果没有被其他业务更新,版本号是不会变化的,更新操作可以完成;否则更新失败
        $affectRows = Db::table('test')->where(['id'=>1,'version'=>$version])->update($data);
        if($affectRows !== false){
            $result['code'] = 1;
            $result['msg'] = '乐观锁取锁成功';
        }else{
            $result['code'] = 2;
            $result['msg'] = '乐观锁取锁失败';
            //TO DO...(回撤其他业务逻辑)
        }
        return json($result);
    }

 

2. 悲观锁:悲观锁的实现需要数据库提供支持,开启事务并调用数据库的相关语句 lock in share mode 或者 for update,即共享锁和排他锁两种表示形式
2.1 共享锁:如select ... lock in share mode,此时给数据加了一把锁,对其只能进行读操作,其他事务可以对该数据再加共享锁,但不能再加排他锁,需要等待当前事务共享锁的释放
2.2 排他锁:如select ... for update,给数据加了一把锁,只有当前事务可以对其进行读和写操作,其他事务不能读写也不能加锁,需要等待当前事务排他锁的释放
2.3 注意 :   
  a.加锁时所有扫描过的行都会被锁上,因此使用悲观锁务必确定走了索引,而不是全表扫描,全表扫描相当于锁表操作
  b.update、delete、insert操作会自动给语句加排他锁,所以在这三种语句后面不能加lock in share mode或是for update,否则语法会报错
  c.事务提交、进程结束、进程中断都会自动释放锁
  d.无论加共享锁还是排他锁都不影响普通select查询,因为普通查询时不会给语句加锁
2.4 测试:打开两个浏览器窗口,窗口1执行add_lock方法,窗口2执行test_lock方法

//当前事务:对id=1的数据加锁
public function add_lock()
{
Db::startTrans();
$result = Db::query('SELECT * FROM `test` WHERE id=1 LOCK IN SHARE MODE'); //对主键为1的数据加共享锁
//$result = Db::query('SELECT * FROM `test` WHERE id=1 FOR UPDATE'); //对主键为1的数据加排他锁
sleep(150);//阻断锁的释放
}
//其他进程:对id=1的数据增删改查
public function test_lock()
{
//查询
$result = Db::query('SELECT * FROM `test` WHERE id=1'); //普通查询
//$result = Db::query('SELECT * FROM `test` WHERE id=1 LOCK IN SHARE MODE'); //共享锁查询
//$result = Db::query('SELECT * FROM `test` WHERE id=1 FOR UPDATE'); //排他锁查询

//更新
$result = Db::query('UPDATE `test` SET version=2 WHERE id=1'); //普通更新
//$result = Db::query('UPDATE `test` SET version=2 WHERE id=1 LOCK IN SHARE MODE'); //共享锁更新
//$result = Db::query('UPDATE `test` SET version=2 WHERE id=1 FOR UPDATE'); //排他锁更新

//删除
$result = Db::query('DELETE FROM `test` WHERE id=1'); //普通删除
//$result = Db::query('DELETE FROM `test` WHERE id=1 LOCK IN SHARE MODE'); //共享锁删除
//$result = Db::query('DELETE FROM `test` WHERE id=1 FOR UPDATE'); //排他锁删除

//插入(第一个1是主键)
$result = Db::query('INSERT INTO `test` VALUES (1,6,6,6,6,6,6)'); //普通插入
//$result = Db::query('INSERT INTO `test` VALUES (1,6,6,6,6,6,6) LOCK IN SHARE MODE');//共享锁插入
//$result = Db::query('INSERT INTO `test` VALUES (1,6,6,6,6,6,6) FOR UPDATE'); //排他锁插入

dump($result);
}
    测试结果:ok表示可以进行操作;no表示需要等待锁的释放,否则无法进行操作;error表示语法报错

    加共享锁后:  普通查询ok       共享锁查询ok         排他锁查询no
普通更新no 共享锁更新error 排他锁更新error
普通删除no 共享锁删除error 排他锁删除error
普通插入error 共享锁插入error 排他锁插入error

加排他锁后: 普通查询ok 共享锁查询no 排他锁查询no
普通更新no 共享锁更新error 排他锁更新error
普通删除no 共享锁删除error 排他锁删除error
普通插入error 共享锁插入error 排他锁插入error

3. 表锁:LOCK TABLE为当前线程锁定表。UNLOCK TABLES释放被当前线程持有的任何锁。如果一个线程获得在一个表上的一个READ锁,该线程和所有其他线程只能从表中读。如果一个线程获得一个表上的一个WRITE锁,那么只有持锁的线程可以从表中读写,其他线程被阻止。

    LOCK TABLE [table] [READ|WRITE];
        ...
        ...
        ...
    UNLOCK TABLES;


4. 行锁:即共享锁和排他锁

 

正文到此结束