数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。mysql
乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段。sql
不管是悲观锁仍是乐观锁,都是人们定义出来的概念,能够认为是一种思想。其实不只仅是数据库系统中有乐观锁和悲观锁的概念,像memcache、hibernate、tair等都有相似的概念。数据库
针对于不一样的业务场景,应该选用不一样的并发控制方式。因此,不要把乐观并发控制和悲观并发控制狭义的理解为DBMS中的概念,更不要把他们和数据中提供的锁机制(行锁、表锁、排他锁、共享锁)混为一谈。其实,在DBMS中,悲观锁正是利用数据库自己提供的锁机制来实现的。json
下面来分别学习一下悲观锁和乐观锁。安全
悲观锁:并发
在关系数据库管理系统里,悲观并发控制(又名“悲观锁”,Pessimistic Concurrency Control,缩写“PCC”)是一种并发控制的方法。它能够阻止一个事务以影响其余用户的方式来修改数据。若是一个事务执行的操做都某行数据应用了锁,那只有当这个事务把锁释放,其余事务才可以执行与该锁冲突的操做。悲观并发控制主要用于数据争用激烈的环境,以及发生并发冲突时使用锁保护数据的成本要低于回滚事务的成本的环境中。app
悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其余事务,以及来自外部系统的事务处理)修改持保守态度(悲观),所以,在整个数据处理过程当中,将数据处于锁定状态。 悲观锁的实现,每每依靠数据库提供的锁机制 (也只有数据库层提供的锁机制才能真正保证数据访问的排他性,不然,即便在本系统中实现了加锁机制,也没法保证外部系统不会修改数据)学习
在数据库中,悲观锁的流程以下:spa
在对任意记录进行修改前,先尝试为该记录加上排他锁(exclusive locking)。hibernate
若是加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。 具体响应方式由开发者根据实际须要决定。
若是成功加锁,那么就能够对记录作修改,事务完成后就会解锁了。
要使用悲观锁,咱们必须关闭mysql数据库的自动提交属性,由于MySQL默认使用autocommit模式,也就是说,当你执行一个更新操做后,MySQL会马上将结果进行提交。 set autocommit=0;
//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_goods表中,id为1的 那条数据就被咱们锁定了,其它的事务必须等本次事务提交以后才能执行。这样咱们能够保证当前的数据不会被其它事务修改。
上面咱们提到,使用 select…for update 会把数据给锁住,不过咱们须要注意一些锁的级别,MySQL InnoDB默认行级锁。行级锁都是基于索引的,若是一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住,这点须要注意。
优势与不足
悲观并发控制其实是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。可是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增长产生死锁的机会;另外,在只读型事务处理中因为不会产生冲突,也不必使用锁,这样作只能增长系统负载;还有会下降了并行性,一个事务若是锁定了某行数据,其余事务就必须等待该事务处理完才能够处理那行数
乐观锁
在关系数据库管理系统里,乐观并发控制(又名“乐观锁”,Optimistic Concurrency Control,缩写“OCC”)是一种并发控制的方法。它假设多用户并发的事务在处理时不会彼此互相影响,各事务可以在不产生锁的状况下处理各自影响的那部分数据。在提交数据更新以前,每一个事务会先检查在该事务读取数据后,有没有其余事务又修改了该数据。若是其余事务有更新的话,正在提交的事务会进行回滚。乐观事务控制最先是由孔祥重(H.T.Kung)教授提出。
乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据通常状况下不会形成冲突,因此在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,若是发现冲突了,则让返回用户错误的信息,让用户决定如何去作。
相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。通常的实现乐观锁的方式就是记录数据版本。
数据版本,为数据增长的一个版本标识。当读取数据时,将版本标识的值一同读出,数据每更新一次,同时对版本标识进行更新。当咱们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的版本标识进行比对,若是数据库表当前版本号与第一次取出来的版本标识值相等,则予以更新,不然认为是过时数据。
实现数据版本有两种方式,第一种是使用版本号,第二种是使用时间戳
//3.线索池线索 $retry = 0; while ($retry < 3) { $query = SaleClueQueue::onWriteConnection()->select('id', 'updated_at', 'phone', 'sc_business_type_id', 'sc_ids', 'sc_main_id', 'created_at') ->whereIn('operator', [0, $userId]) ->where('next_call_time', '<=', date('Y-m-d H:i:s', time())) ->where('is_double_appoint', $type); if ($type == self::DOUBLE) { $query->whereIn('city_id', SaleClueUtils::getMyCities()); } $commonclue = $query->orderBy('weight', 'asc')->orderBy('created_at', 'desc')->first(); if (!empty($commonclue)) { $updateData = [ 'appoint_status' => AppointCallVars::APPOINT_STATUS_CALLING, 'operator' => Utils::getInstance()->getUserId(), 'updated_at' => date('Y-m-d H:i:s'), ]; $flag = SaleClueQueue::where('id', $commonclue->id)->where('updated_at', $commonclue->updated_at)->update($updateData); if ($flag) { $log = self::$logPress . '申请到公共线索 : 结果 : ' . json_encode($commonclue); LogUtils::logInfo($log); break; } else { $retry++; } } else { $retry++; } }
数据库的字段
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立时间',
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
其间若是有其余对该记录作修改或加排他锁的操做,都会等待咱们解锁或直接抛出异常。