1. 悲观锁和乐观锁定义是什么
悲观锁和乐观锁定义:
乐观锁:乐观锁在操作数据时非常乐观,认为别人不会同时修改数据。因此乐观锁不会上锁,只是在执行更新的时候判断一下在此期间别人是否修改了数据:如果别人修改了数据则放弃操作,否则执行操作。
悲观锁:悲观锁在操作数据时比较悲观,认为别人会同时修改数据。因此操作数据时直接把数据锁住,直到操作完成后才会释放锁;上锁期间其他人不能修改数据。
悲观锁实现方式
悲观锁的实现,往往依靠数据库提供的锁机制。在数据库中,悲观锁的流程如下:
1.在对记录进行修改之前,先尝试为该记录加上排它锁(exclusive locking)。
2.如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。具体响应方式由开发者根据实际需要决定。
3.如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了。
4.期间如果有其他对该记录做修改或加排它锁的操作,都会等待解锁或直接抛出异常。
2. 什么是事务什么是锁
事务与锁是不同的。事务具有ACID(原子性、一致性、隔离性和持久性),锁是用于解决隔离性的一种机制。事务的隔离级别通过锁的机制来实现。另外锁有不同的粒度,同时事务也是有不同的隔离级别的(一般有四种:读未提交Read uncommitted,
读已提交Read committed,
可重复读Repeatable read,
可串行化Serializable)。
在具体的程序设计中,开启事务其实是要数据库支持才行的,如果数据库本身不支持事务,那么仍然无法确保你在程序中使用的事务是有效的。
锁可以分为乐观锁和悲观锁:
悲观锁:认为在修改数据库数据的这段时间里存在着也想修改此数据的事务;
乐观锁:认为在短暂的时间里不会有事务来修改此数据库的数据;
我们一般意义上讲的锁其实是指悲观锁,在数据处理过程中,将数据置于锁定状态(由数据库实现)
如果开启了事务,在事务没提交之前,别人是无法修改该数据的;如果rollback,你在本次事务中的修改将撤消(不是别人修改的会没有,因为别人此时无法修改)。当然,前提是你使用的数据库支持事务。还有一个要注意的是,部分数据库支持自定义sql锁覆盖事务隔离级别默认的锁机制,如果使用了自定义的锁,那就另当别论。
重点:一般事务使用的是悲观锁(具有排他性)
3. 数据库高并发下乐观锁的原理
在高并发下,经常需要处理SELECT之后,在业务层处理逻辑,再执行UPDATE的情况。
若两个连接并发查询同一条数据,然后在执行一些逻辑判断或业务操作后,执行UPDATE,可能出现与预期不相符的结果。
在不使用悲观锁与复杂SQL的前提下,可以使用乐观锁处理该问题,同时兼顾性能。
场景模拟:
假设一张表两个字段,一个id,一个use_count。
表里存了100个id,每个id对应自己的use_count。
当id每使用一次,use_count要加1。当use_count大于1000时,这个id就不能在被使用了(换句话说 无法从数据库中查出)。
在高并发情况下,会遇到一种问题:假设数据表中有一条记录为:id=123456, use_count=999
A与B两个连接并发查询这个id=123456,都执行下列SQL:
SELECT*FROMtableWHEREid=123456anse_count<1000;
A先执行,得到id=123456的use_count是999,之后在程序里做了一些逻辑判断或业务操作后执行SQL:
UPDATEtableSETuse_count+1WHEREid=123456;
在A做判断且没有update之前,B也执行了查询SQL,发现use_count是999,之后它也会执行SQL:
UPDATEtableSETuse_count+1WHEREid=123456;
但是,事实上B不应该取得这个id,因为A已经是第1000个使用者。
处理步骤如下:
1、添加第3个字段version,int类型,default值为0。version值每次update时作加1处理。
'0'NOTNULLAFTERuse_count;
2、SELECT时同时获取version值(例如为3)。
SELECTuse_count,versionFROMtableWHEREid=123456ANDuse_count<1000;
3、UPDATE时检查version值是否为第2步获取到的值。
UPDATEtableSETversion=4,use_count=use_count+1WHEREid=123456ANDversion=3;
如果UPDATE的记录数为1,则表示成功。
如果UPDATE的记录数为0,则表示已经被其他连接UPDATE过了,需作异常处理。
参考地址:http://latrell.me/post-358.html
4. 乐观锁的介绍
相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库 性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。相对悲观锁而言,乐观锁更倾向于开发运用!
5. 为何Redis用乐观锁,而MySQL数据库却没有
简单来说,Redis使用乐观锁,相对于悲观锁,在实现中更加简单,在某些场景中的性能也更好。Redis作为一个轻量级的、快速的缓存引擎,而不是一个全功能的关系型数据库,既没有使用悲观锁的必要,也难以承受使用悲观锁的成本。
详细来说,要深入到Redis和MySQL的事务处理机制。Redis关于事务的文档见此:
Transactions(事务)
Redis对于事务只提供了非常有限的支持,其实更多地是试图绕过问题。
首先,Redis对于同一事务中的一组操作,而不是立即执行,而是放入一个queue中,当执行到EXEC时,再一起执行。事务执行是全局独占的,也就是同一时间只有一个事务被执行,中途不能被其它事务打断。Redis用这种最简单的、也是性能最差的方式避免了race
condition。
其次,在Redis的事务中,如果有一个或多个操作失败,其它操作仍然会成功,也就是说它根本没有回滚机制。
这种方式会带来很多严重的问题,其中之一是,无法先读取某个数值后再进行依赖这个值的操作,因为放在一个事务里会被在同一个瞬间执行,不放在同一个事务里又会导致race
condition。解决方法是使用WATCH,它会监视一个或多个变量,如果变量的值在调用WATCH以后和事务提交之前被别的事务修改过了,整个事务都会失败。这类似于操作系统中的CAS(Compare
and
Set)。我不知道WATCH具体是怎么实现的,但是我推测它监控了指定变量的版本号。
即使有了WATCH,Redis的事务也是受到严重限制的。第一,它没有实现读数据时的一致性,因为WATCH对于读操作不起作用。第二,它不支持回滚。第三,在对同一变量存在大量并发写操作时,性能会非常差,因为每次提交事务时,WATCH监控的变量都已经被修改了,导致事务将多次提交失败。但是,Redis本来就是一个KV类型的缓存引擎,要处理的是大量读少量写的场景,对一致性也没有要求。
MySQL就完全不一样了,作为一个典型的关系型数据库,它需要完整地实现ACID,所以Redis的方式是解决不了它的问题的。
MySQL中的MVCC机制(Oracle的也是),通过undo
日志来获取某个行记录的历史快照,从而实现了所谓的读一致性。它的目的是读取某个时间点上的历史数据(而不是可能已经被修改了的数据),而不是避免悲观锁的使用。严格地说这不能称之为乐观锁,因为它既不Compare当前版本和历史版本,也不进行Set。事实上,在读取记录的历史快照时,当前记录有可能(由于并发的写操作)已经被加上独占锁。
进一步的问题是:有没有可能使用乐观锁来实现RDBMS中的写一致性?有没有可能使用乐观锁实现完整的ACID特性?
回答是可以。例如,MS
SQL
SERVER的Hekaton引擎通过一套基于时间戳的多版本管理系统,实现了不使用了悲观锁的ACID。
但是,这并不意味着乐观锁必然优于悲观锁。除了维护多版本的开销以外,乐观锁无法避免的一个问题是,当多个写操作试图更新同一个对象时,只有第一个操作可以成功,其它的操作都会在Compare时失败然后回滚,从而造成极大的性能问题。在这种情况下,乐观锁的性能会低于悲观锁。
目前的趋势似乎是,大规模的分布式数据库更倾向于使用乐观锁来达到所谓的external
consistency,因为基于传统悲观锁的分布式锁在集群大到一定程度以后(从几百台扩展到成千上万台时),性能开销就大得无法接受了。Google的Spanner就是基于乐观锁。当然这完全是另外一个问题了。
6. 悲观锁和乐观锁的用处和区别
用处:保证数据安全,处理多用户并发访问。
区别:
悲观锁,从数据开始更改时就将数据锁住,知道更改完成才释放。
乐观锁,直到修改完成准备提交所做的的修改到数据库的时候才会将数据锁住。完成更改后释放。悲观锁会造成访问数据库时间较长,并发性不好,特别是长事务。乐观锁在现实中使用得较多,厂商较多采用。
7. 什么是乐观锁
hibernate中两种锁机制: 悲观锁,乐观锁.
悲观锁:采用数据库本身的锁机制,for update或no wait
优点:处理并发彻底,并发处理比较好
缺点:当一个用户锁定记录的时候,其它用户都不能使用了,容易造成长时间的等待.
乐观锁:是Hibernate自己实现的,采用版本控制的方式实现处理并发.
1.添加version元素
2.声明使用版本控制的方式实现乐观锁optimistic-lock="version"
<hibernate-mapping>
<class name="..." table="..." catalog="..." optimistic-lock="version"></class></hibernate-mapping>
8. mysql默认是乐观锁
乐观锁,悲观锁,这两个概念你需要搞清楚才能更好的理解。
乐观锁:与悲观锁相对应,不是数据库自带的,需要自己去实现。
悲观锁:与乐观锁相对应,是数据库自己实现了的。要用的时候,我们直接调用数据库的相关语句就可以了。
悲观锁又涉及到其他两个锁概念,共享锁和排他锁。只有去理解和实际操作才能更好的理解这些具体的东西。
希望我的回答对你有所帮助
9. 乐观锁和悲观锁是mysql的概念吗
关于mysql中的乐观锁和悲观锁面试的时候被问到的概率还是比较大的。
mysql的悲观锁:
其实理解起来非常简单,当数据被外界修改持保守态度,包括自身系统当前的其他事务,以及来自外部系统的事务处理,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制,但是也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在自身系统中实现了加锁机制,也无法保证外部系统不会修改数据。
来点实际的,当我们使用悲观锁的时候我们首先必须关闭mysql数据库的自动提交属性,因为mysql默认使用autocommit模式,也就是说,当你执行一个更新操作后,mysql会立刻将结果进行提交。
关闭命令为:set
autocommit=0;
悲观锁可以使用select…for
update实现,在执行的时候会锁定数据,虽然会锁定数据,但是不影响其他事务的普通查询使用。此处说普通查询就是平时我们用的:select
*
from
table
语句。在我们使用悲观锁的时候事务中的语句例如:
//开始事务
begin;/begin
work;/start
transaction;
(三选一)
//查询信息
select
*
from
order
where
id=1
for
update;
//修改信息
update
order
set
name='names';
//提交事务
commit;/commit
work;(二选一)
此处的查询语句for
update关键字,在事务中只有select
...
for
update
或lock
in
share
mode
同一条数据时会等待其它事务结束后才执行,一般的select查询则不受影响。
执行事务时关键字select…for
update会锁定数据,防止其他事务更改数据。但是锁定数据也是有规则的。
查询条件与锁定范围:
1、具体的主键值为查询条件
比如查询条件为主键id=1等等,如果此条数据存在,则锁定当前行数据,如果不存在,则不锁定。
2、不具体的主键值为查询条件
比如查询条件为主键id>1等等,此时会锁定整张数据表。
3、查询条件中无主键
会锁定整张数据表。
4、如果查询条件中使用了索引为查询条件
明确指定索引并且查到,则锁定整条数据。如果找不到指定索引数据,则不加锁。
悲观锁的确保了数据的安全性,在数据被操作的时候锁定数据不被访问,但是这样会带来很大的性能问题。因此悲观锁在实际开发中使用是相对比较少的。
mysql的乐观锁:
相对悲观锁而言,乐观锁假设数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会对数据的冲突与否进行检测,如果发现冲突,则让返回用户错误的信息,让用户决定如何去做。
一般来说,实现乐观锁的方法是在数据表中增加一个version字段,每当数据更新的时候这个字段执行加1操作。这样当数据更改的时候,另外一个事务访问此条数据进行更改的话就会操作失败,从而避免了并发操作错误。当然,还可以将version字段改为时间戳,不过原理都是一样的。
例如有表student,字段:
id,name,version
1
a
1
当事务一进行更新操作:update
student
set
name='ygz'
where
id
=
#{id}
and
version
=
#{version};
此时操作完后数据会变为id
=
1,name
=
ygz,version
=
2,当另外一个事务二同样执行更新操作的时候,却发现version
!=
1,此时事务二就会操作失败,从而保证了数据的正确性。
悲观锁和乐观锁都是要根据具体业务来选择使用,本文仅作简单介绍。
10. 乐观锁的优点
从上面的例子可以看出,乐观锁机制避免了长事务中的数据库加锁开销(操作员 A和操作员 B 操作过程中,都没有对数据库数据加锁),大大提升了大并发量下的系统整体性能表现。