共享锁,排他锁,乐观锁,悲观锁
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执行完才会开始执行,所以避免了死锁,但是需要串行执行,所以影响并发
乐观锁,悲观锁
乐观锁:
对当前操作的数据保持一个乐观的态度,认为不会有其他事务操作修改当前的数据记录。只有在提交事务更新时,会检测有没有被修改。若有则直接选择retry或定义的操作。例如es的更新
在查询的时候,不加锁,在最后更新时候,使用version版本字段或者timestamp时间戳字段,保证了修改的数据是和它查询出来的数据是一致的,而其他执行程序未进行修改。当然,如果更新失败,可以尝试重试来保证更新成功。为了尽可能避免更新失败,可以合理调整重试次数(阿里巴巴开发手册规定重试次数不低于三次)
如果在查询居多的情况下,推荐使用乐观锁
悲观锁:
认为其他事务会对操作的数据进行修改,所以当查询时将数据上锁。若其他事务需要操作该数据则需要等待。例如数据库中的行锁,表锁
加排他锁,禁止其他事务的查询和更新。所以,会影响并发
如果写入居多,对并发要求不高,可使用悲观锁
乐观锁,悲观锁如何选择
需要结合这两种锁的特点,进行合理的选择
响应速度:选择乐观锁。要么冲突失败要么快速成功。悲观锁则需要等待释放锁才能被执行
冲突频率:频率高的话不应选择乐观锁,需要重试好几次,代价大。而悲观锁保证成功率
重试代价:若重试代价大则选择悲观锁
数据库隔离级别
Read uncommitted 读未提交:没有任何锁,就算没提交事务,事务跟事务之间,数据会互相影响,会出现脏读
Read committed 读提交(Sql Server , Oracle):没提交事务,多个事务之间的数据不会影响,但是其中一个提交后,另外一个事务读取数据会更新为最新的,会造成不可重复读
Repeatable read 重复读(MySQL):多个事务之间,其中一个事务开始读取数据,另外一个事务就不能修改数据,共享锁在事务没结束之前,会影响其他事务,也就是前面说的那个实例,会出现幻读
注意:幻读,主要针对查询为空,加共享锁无效,所以其他事务可以插入数据,但你读取一直都是空Serializable 序列化:最高的事务隔离级别,同时代价也花费最高,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻像读。两个事务之间,其中一个就算只有select语句,都会加排他锁(范围锁),其他事务查询和插入都会失败,就算select为空,也会在空的数据上加锁,其他事务也是插不进去
隔离级别与锁的关系,看这里,可能会更加清晰
在Read Uncommitted级别下,读操作不加S锁;
在Read Committed级别下,读操作需要加S锁,但是在语句执行完以后释放S锁;
在Repeatable Read级别下,读操作需要加S锁,但是在事务提交之前并不释放S锁,也就是必须等待事务执行完毕以后才释放S锁。可以手动加x锁,避免幻读问题
在Serialize级别下,会在Repeatable Read级别的基础上,添加一个范围锁。保证一个事务内的两次查询结果完全一样,而不会出现第一次查询结果是第二次查询结果的子集。
以上是关于共享锁,排他锁,乐观锁,悲观锁的主要内容,如果未能解决你的问题,请参考以下文章