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表的读操作和写操作之间,以及写操作之间是串行的。
    当一个线程获得对一个表的写锁后,只有持有锁线程可以对表进行更新操作。其他线程的读、写操作都会等待,直到锁被释放为止。
     
    总结:
    • 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
    • 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
    • 页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般
  • 相关阅读:
    在SQLite中使用索引优化查询速度
    SQLite支持的SQL数据操作
    left (outer) join , right (outer) join, full (outer) join, (inner) join, cross join 区别
    深入理解Android内存管理原理(六)
    Merge Sorted Array
    Sort Colors
    Construct Binary Tree from Preorder and Inorder Traversal
    Binary Tree Postorder Traversal
    Symmetric Tree
    Rotate Image
  • 原文地址:https://www.cnblogs.com/edison20161121/p/10289892.html
Copyright © 2011-2022 走看看