共享锁,排他锁,乐观锁,悲观锁

Posted 互联网编程

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了共享锁,排他锁,乐观锁,悲观锁相关的知识,希望对你有一定的参考价值。

共享锁,排他锁

  • 共享锁S锁:对某一资源加共享锁,自身可以读该资源,其他人也可以读该资源(也可以再继续加共享锁,即 共享锁可多个共存),但无法修改。要想修改就必须等所有共享锁都释放完之后

  • 排他锁X锁:对某一资源加排他锁,自身可以进行增删改查,其他人无法进行任何操作

实例1

T1:select * from table (假设查询会花很长时间,下面的例子也都这么假设)

T2:update table set column1='hello'

过程:

T1运行(并加共享锁)

T2运行

If T1还没执行完

T2等......

else锁被释放

T2执行

endif

分析:T1加了共享锁,共享锁没释放,不允许修改,所以T2需要等T1执行完后,才能执行

实例2

T1:select * from table 

T2:select * from table 

过程:

一起执行

分析:共享锁允许重复读取

实例3

T1:select * from table 

T2:select * from table 

T3:update table set column1='hello'

过程:

T1和T2可以同时执行,T3需要等待T1和T2执行完才能执行

实例4,死锁的发生

T1:begin tran(事务)

     select * from table 

     update table set column1='hello'

T2:begin tran

     select * from table 

     update table set column1='world'

分析:T1和T2同事达到select,T1和T2同时给table加了共享锁,T1执行update的时候,需要等T2的共享锁释放,相同道理,T2也需要等T1的共享锁释放,于是,死锁产生了(msyql才会发生,Repeatable read级别,大多数数据库的默认级别就是Read committed,比如Sql Server , Oracle,下面会介绍这个,多个事务中的锁)

实例5

T1:begin tran

     update table set column1='hello' where id=10

T2:begin tran

     update table set column1='world' where id=20

分析:如果id有索引,mysql的InnoDB是基于索引的锁,所以会用到行锁的排它锁,互不干扰,同时进行;如果id没有索引,两个更新都是表锁的排它锁,T2会等待T1的执行完再执行

实例6,死锁解决

T1:begin tran

     select * from table for update

     update table set column1='hello'

T2:begin tran

     select * from table for update

     update table set column1='world'

分析:T1的select引入排它锁,导致T2整个执行需要等待T1执行完才会开始执行,所以避免了死锁,但是需要串行执行,所以影响并发

乐观锁,悲观锁

  • 乐观锁:

    1. 对当前操作的数据保持一个乐观的态度,认为不会有其他事务操作修改当前的数据记录。只有在提交事务更新时,会检测有没有被修改。若有则直接选择retry或定义的操作。例如es的更新

    2. 在查询的时候,不加锁,在最后更新时候,使用version版本字段或者timestamp时间戳字段,保证了修改的数据是和它查询出来的数据是一致的,而其他执行程序未进行修改。当然,如果更新失败,可以尝试重试来保证更新成功。为了尽可能避免更新失败,可以合理调整重试次数(阿里巴巴开发手册规定重试次数不低于三次)

    3. 如果在查询居多的情况下,推荐使用乐观锁

  • 悲观锁:

    1. 认为其他事务会对操作的数据进行修改,所以当查询时将数据上锁。若其他事务需要操作该数据则需要等待。例如数据库中的行锁,表锁

    2. 加排他锁,禁止其他事务的查询和更新。所以,会影响并发

    3. 如果写入居多,对并发要求不高,可使用悲观锁

乐观锁,悲观锁如何选择

需要结合这两种锁的特点,进行合理的选择

  • 响应速度:选择乐观锁。要么冲突失败要么快速成功。悲观锁则需要等待释放锁才能被执行

  • 冲突频率:频率高的话不应选择乐观锁,需要重试好几次,代价大。而悲观锁保证成功率

  • 重试代价:若重试代价大则选择悲观锁

数据库隔离级别

  1. Read uncommitted 读未提交:没有任何锁,就算没提交事务,事务跟事务之间,数据会互相影响,会出现脏读

  2. Read committed 读提交(Sql Server , Oracle):没提交事务,多个事务之间的数据不会影响,但是其中一个提交后,另外一个事务读取数据会更新为最新的,会造成不可重复读

  3. Repeatable read 重复读(MySQL):多个事务之间,其中一个事务开始读取数据,另外一个事务就不能修改数据,共享锁在事务没结束之前,会影响其他事务,也就是前面说的那个实例,会出现幻读
    注意:幻读,主要针对查询为空,加共享锁无效,所以其他事务可以插入数据,但你读取一直都是空

  4. Serializable 序列化:最高的事务隔离级别,同时代价也花费最高,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻像读。两个事务之间,其中一个就算只有select语句,都会加排他锁(范围锁),其他事务查询和插入都会失败,就算select为空,也会在空的数据上加锁,其他事务也是插不进去

隔离级别与锁的关系,看这里,可能会更加清晰

  1. 在Read Uncommitted级别下,读操作不加S锁;

  2. 在Read Committed级别下,读操作需要加S锁,但是在语句执行完以后释放S锁;

  3. 在Repeatable Read级别下,读操作需要加S锁,但是在事务提交之前并不释放S锁,也就是必须等待事务执行完毕以后才释放S锁。可以手动加x锁,避免幻读问题

  4. 在Serialize级别下,会在Repeatable Read级别的基础上,添加一个范围锁。保证一个事务内的两次查询结果完全一样,而不会出现第一次查询结果是第二次查询结果的子集。


以上是关于共享锁,排他锁,乐观锁,悲观锁的主要内容,如果未能解决你的问题,请参考以下文章

共享锁,排他锁,乐观锁,悲观锁

Mysql共享锁排他锁悲观锁乐观锁及其使用场景

MySQL共享锁排他锁悲观锁乐观锁及其使用场景

乐观锁,悲观锁,表锁,行锁,共享锁,排他锁

一文搞懂mysql中的共享锁排他锁悲观锁乐观锁以及使用场景

MySQL:表级锁行级锁共享锁排他锁乐观锁悲观锁