DBMS中并发控制的任务是确保多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性
乐观锁和悲观锁是并发控制主要采用的技术手段。
无论是乐观锁还是悲观锁,都是人们定义出来的概念,可以认为是一种思想。其实不仅仅是关系型数据库中有乐观锁和背锅所的盖面,其他很多地方都有类似的概念。
针对不同的业务场景,应该选用不同的并发控制方式,所以,不要把乐观锁和悲观锁下一的理解为DBMS中的概念,更不要把他们和数据库中提供的锁机制混为一谈。其实,在DBMS中,悲观锁正式利用数据库本身的锁机制来实现的。
悲观锁
在关系数据库中,悲观锁是一种并发控制的方法,它可以组织一个事务一影响其他用户的方式来修改数据。如果一个事务执行的操作嗾使某行数据应用了锁,那只有当这个事务把锁释放,其他事务才能够执行与该锁冲突的操作。
悲观锁主要用于数据征用激烈的环境,以及发生并发冲突时使用锁保护数据的成本要低于回滚事务的成本的环境中。
悲观锁指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往一句数据库提供的锁机制(也只有数据库层提供的锁机制擦能真正保证数据访问的排他性,否则,及时在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)
在数据库中,悲观锁的流程如下
1、在对任意记录进行修改前,先尝试为该记录加上排他锁
2、如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者跑出异常。具体相应方式有开发者根据实际需要决定
3、如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了
4、期间如果有其他对其记录做修改或加排他锁的操作,都会等待解锁或直接跑出异常
mysql innodb中使用悲观锁
要使用悲观锁,我们必须关闭mysql数据库的自动提交属性,因为mysql默认使用autocommit模式,也就是说,当你执行一个更新操作后,mysql会立刻将结果进行提交
//0.开始事务 begin;/begin work;/start transaction; (三者选一就可以) //1.查询出商品信息 select status from t_goods where id=1 for update; //2.根据商品信息生成订单 insert into t_orders (id,goods_id) values (null,1); //3.修改商品status为2 update t_goods set status=2; //4.提交事务 commit;/commit work;
上面的查询语句中,我们使用了select...for update的方式,这样就通过开启排他锁的试试实现了悲观锁。此时在t_good表中,id=1的那条数据就被我们锁定了,其他的事务必须等本次事务提交之后才能执行,这样我们可以保证当前的数据不会被其他事务修改
上面提到,使用select... for update会把数据锁住。不过需要注意一些锁的级别,mysql innodb默认行级锁。行级锁都是基于索引的,如果一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住,这点需要注意。
优点与不足
悲观锁实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会,另外,在只读型事务处理中由于不会产生冲突,也没必要使用锁。这样做只能增加系统负载,还会降低并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理哪行数据
乐观锁
在关系数据库中,乐观锁是一种并发控制的方法。它假设多用户并发的事务在处理时不会彼此户型影响,各事务能够在不产生锁的情况下处理各自印象的那部分数据。在提交数据更新之前,每个事务会先检查在该事物读取数据后,有没有其他事务又修改了该数据,如果其他事务又更新的话,正在提交的事务会进行回滚。
乐观锁相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。
相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制,一般的实现乐观锁的方式就是记录数据版本。
数据版本:为数据增加一个版本表示,当读取数据时,将版本标识的值一同读出,数据每更新一次,同时对版本表示进行更新,当我们提交更新的时候,判断数据库对应记录的当前版本信息与第一次取出来的版本表示进行比对,如果数据库表当前版本号与第一次取出来的版本表示值相等,则予以更新,否则认为是过期数据。
实现数据版本有两种方式,第一种是使用版本号,第二种是使用时间戳。
使用版本号实现乐观锁
使用版本号时,可以在数据初始化时指定一个版本号,每次对数据的更新操作都对版本号执行+1操作,并判断当前版本号是不是该数据的最新的版本号。
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};
优点与不足
乐观锁相信事务之间的数据竞争的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁,但如果直接简单这么做,还是有可能会遇到不可预期的结果,例如两个事务都读取了数据库的某一行,经过修改以后写回数据库,这时就遇到了问题。