一、为什么需要锁
在多线程应用中,同一时间可能会有多个用户同时更新一条数据,这样会产生冲突,产生并发性的问题,而常见的冲突有一下俩种
1、丢失更新:一个事务的更新覆盖了其他事务更新的结果,如a事务将一条数据由10更改为5,b事务将一条数据由5改为3,那么a事务则丢失了其更新的数据
2、当一个事务读取其它完成一半事务的记录时,就会发生脏读取。例如:用户A,B看到的值都是6,用户B把值改为2,用户A读到的值仍为6。
如在一些业务逻辑的实现过程中,需要保证数据访问的排他性,比如金融系统中,需要在某个时间点进行结算,结算过程中不希望数据在发生任何变动,就需要保证在这个操作中数据不被其他事务所更改,就需要锁。
二、乐观锁和悲观锁
悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。
乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。
1、悲观锁:再一次a事务中,执行查询语句select * from message where to_id=‘1’ for update;因为加了for update所以在本次事务提交之前外界事务无法对其进行修改
案例:
当事务开启后执行了for update 的查询语句,此时外界事务无法对其进行修改
b客户端发送了修改的update 语句想修改记录此时因为此记录上悲观锁所以不能对其进行修改,出现…等待的字样。
2、乐观锁:相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。如一个金融系统,当某个操作员读取用户的数据,并在读出的用户数据的基础上进行修改时(如更改用户帐户余额)如果采用悲观锁机制,也就意味着整个操作过程中(从操作员读出数据、开始修改直至提交修改结果的全过程,甚至还包括操作员中途去煮咖啡的时间),数据库记录始终处于加锁状态,可以想见,如果面对几百上千个并发,这样的情况将导致怎样的后果。
乐观锁机制在一定程度上解决了这个问题,乐观锁大多是基于数据版本(Version)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个“version”字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
即核心为:使用版本标识来确定读到的数据与提交时的数据是否一致。提交后修改版本标识,不一致时可以采取丢弃和再次尝试的策略
案例:首先我们在设计数据库表字段时为其加上一个version字段表示其版本号,我们需要更新一条数据,那就先去查询这条数据得到里面的version,然后进行更新操作,等到我们提交更新的时候,判断查询出来的version是否与之前的version一致,一致则更新, 同时,version+1,否则失败。
1
version为版本号
2)、修改数据的时候首先把这条数据的版本号查出来,update时判断这个版本号是否和数据库里的一致,如果一致则表明这条数据没有被其他用户修改,若不一致则表明这条数据在操作期间被其他客户修改过,此时需要在代码中抛异常或者回滚等。伪代码如下
update tb set name=‘yyy’ and version=version+1 where id=1 and version=version;
3,
- SELECT name AS old_name, version AS old_version FROM tb where …;
- 根据获取的数据进行业务操作,得到new_dname和new_version
- UPDATE SET name = new_name, version = new_version WHERE version = old_version
if (updated row > 0) {
// 乐观锁获取成功,操作完成
} else {
// 乐观锁获取失败,回滚并重试
}