zoukankan      html  css  js  c++  java
  • 乐观锁,悲观锁,表锁,行锁,共享锁,排他锁

    乐观锁

        乐观锁是指操作数据库时(更新操作),想法很乐观,认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突了。

     实现方式1:在表中的数据进行操作时(更新),先给数据表加一个版本(version)字段,每操作一次,将那条记录的版本号加1。也就是先查询出那条记录,获取出version字段,如果要对那条记录进行操作(更新),则先判断此刻version的值是否与刚刚查询出来时的version的值相等,如果相等,则说明这段期间,没有其他程序对其进行操作,则可以执行更新,将version字段的值加1;如果更新时发现此刻的version值与刚刚获取出来的version的值不相等,则说明这段期间已经有其他程序对其进行操作了,则不进行更新操作。
      

    举例:假设莫某个商品是有库存才能下单,有库存的状态是1,没有库存状态变为2,用状态status表示。

    下单操作包括3步骤:

    1.查询出商品信息

    select (status,status,version) from t_goods where id=#{id}

    2.根据商品信息生成订单

    3.修改商品status为2

    update t_goods 

    set status=2,version=version+1

    where id=#{id} and version=#{version};

    假设该商品最后只剩下1个库存,这时同时有A,B两个用户同时去下单,如果发生并发的的话,可能会出现超卖情况,如按上上述操作,可避免这种情况。

    除了自己手动实现乐观锁之外,现在网上许多框架已经封装好了乐观锁的实现,如hibernate。

    如图新建一张表,同时打开2个窗口,相当于模拟2个事务

    首先执行如下语句

     这时版本号已经变成2

     

    再次从另一个窗口执行语句,发现受影响的行已经变成了0,相当于第二条语句就没有执行

    如上演示的没有加入事务,假设这时加入事务

    把版本数据恢复到1,1,1 ,这时先执行开启事务,执行update操作,但是不提交,另外在另一个窗口执行update操作,发现

     此时这条数据会被锁住,只有等待执行完之前上一个事务的事务提交之后,这条语句执行才会释放。

    但是如果只是查询操作,并不会影响。但是由于上一个事务没有提交,这时查到的数据还是版本号1。

    从上面的例子中即可引入悲观锁的概念。

    悲观锁

    与乐观锁相对应的就是悲观锁了。悲观锁就是在操作数据时,认为此操作会出现数据冲突,所以在进行每次操作时都要通过获取锁才能进行对相同数据的操作,这点跟java中的synchronized很相似,所以悲观锁需要耗费较多的时间。另外与乐观锁相对应的,悲观锁是由数据库自己实现了的,譬如上面的update操作所在的事务没有提交的时候,这时候就实现了行锁,要用的时候,我们直接调用数据库的相关语句就可以了。

    说到这里,由悲观锁涉及到的另外两个锁概念就出来了,它们就是共享锁与排它锁。共享锁和排它锁是悲观锁的不同的实现,它俩都属于悲观锁的范畴。

    共享锁
    共享锁指的就是对于多个不同的事务,对同一个资源共享同一个锁。相当于对于同一把门,它拥有多个钥匙一样。就像这样,你家有一个大门,大门的钥匙有好几把,你有一把,你女朋友有一把,你们都可能通过这把钥匙进入你们家,进去啪啪啪啥的,一下理解了哈,没错,这个就是所谓的共享锁。
    刚刚说了,对于悲观锁,一般数据库已经实现了,共享锁也属于悲观锁的一种,那么共享锁在mysql中是通过什么命令来调用呢。通过查询资料,了解到通过在执行语句后面加上lock in share mode就代表对某些资源加上共享锁了。

    begin;
    select * from t_good where f_id=1 lock in share mode; 
    COMMIT;

    例如先执行上诉操作,先开启事务,执行select操作,但不提交事务,这时,在另外一个窗口执行update操作,这时会发现执行会处于等待,如果是执行查询操作不会影响。

    只有在上一个事务释放掉锁后才能进行操作,或用共享锁才能对此数据进行操作。

    继续深入

    但我执行

    UPDATE t_good
    SET f_status = 1,
     f_version = f_version + 1
    WHERE
        f_id = 1
     lock IN SHARE MODE;
    [SQL]UPDATE t_good
    SET f_status = 1,
     f_version = f_version + 1
    WHERE
        f_id = 1
     lock IN SHARE MODE;
    [Err] 1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'lock IN SHARE MODE' at line 6

    发现update语句加入了共享锁语句,还是不能执行成功,

    后来查询资料发现对于update,insert,delete语句会自动加排它锁

    然后再执行

    select * from t_good where f_id=1 lock in share mode;

    执行成功,说明select 语句不管加不加共享锁语句,都能执行成功。

    排它锁
    排它锁与共享锁相对应,就是指对于多个不同的事务,对同一个资源只能有一把锁。
    与共享锁类型,在需要执行的语句后面加上for update就可以了

    begin;
    select * from t_good where f_id=1 for update;
    commit;
    update t_good set f_status = 2 where f_id=1;

    同样打开2个窗口,首先执行,其中一个先开启事务,然后执行查询操作,这时不提交事务,然后另一个窗口执行update操作,这时会等待中,直到上面哪个事务提交


    行锁
    行锁,由字面意思理解,就是给某一行加上锁,也就是一条记录加上锁。

    比如之前演示的共享锁语句

    select * from t_good where f_id=1 lock in share mode;

    由于对于t_good表中,if_d字段为主键,就也相当于索引。执行加锁时,会将id这个索引为1的记录加上锁,那么这个锁就是行锁。

    表锁
    表锁,和行锁相对应,给这个表加上锁。

    MySQL表级锁有两种模式:表共享锁(Table Read Lock)和表独占写锁(Table Write Lock)。
    • 对MyISAM的读操作,不会阻塞其他用户对同一表请求,但会阻塞对同一表的写请求;
    • 对MyISAM的写操作,则会阻塞其他用户对同一表的读和写操作;
    • MyISAM表的读操作和写操作之间,以及写操作之间是串行的。
    当一个线程获得对一个表的写锁后,只有持有锁线程可以对表进行更新操作。其他线程的读、写操作都会等待,直到锁被释放为止。
     
    总结:
    • 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
    • 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
    • 页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般
  • 相关阅读:
    人月神话阅读笔记
    12周总结
    IOS成长之路-用NSXMLParser实现XML解析
    沟通能力的表达
    IOS中UIScrollView的contentSize、contentOffset和contentInset属性
    Block 的使用时机
    drawAdapter
    drawpoly()函数的用法
    @synchronized(self)
    Swift2
  • 原文地址:https://www.cnblogs.com/edison20161121/p/10289892.html
Copyright © 2011-2022 走看看