1、问题引出html
1. 假设当当网上用户下单买了本书,这时数据库中有条订单号为001的订单,其中有个status字段是’有效’,表示该订单是有效的;java
2. 后台管理人员查询到这条001的订单,而且看到状态是有效的;数据库
3. 用户发现下单的时候下错了,因而撤销订单,假设运行这样一条SQL: update order_table set status = ‘取消’ where order_id = 001;安全
4. 后台管理人员因为在②这步看到状态有效的,这时,虽然用户在③这步已经撤销了订单,但是管理人员并未刷新界面,看到的订单状态仍是有效的,因而点击”发货”按钮,将该订单发到物流部门,同时运行相似以下SQL,将订单状态改为已发货:update order_table set status = ‘已发货’ where order_id = 001;session
<<< 上面的问题应该如何解决呢??? 请见下文分解 >>>并发
2、悲观锁oracle
所谓的悲观锁:顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,因此每次拿数据的时候都会上锁。这样别人拿数据的时候就要等待直到锁的释放。性能
Oracle的悲观锁须要利用一条现有的Connection,它分红两种方式,从SQL语句的区别来看,就是一种是select for update,一种是select for update nowait的形式。spa
1. 执行select xxx for update操做时,数据会被锁定,只有执行comit或rollover才会释放.net
2. 执行select xxx for update nowait操做时,数据也会被锁定,其余人访问时或返回ORA-00054错误,内容是资源正忙,须要采起相应的业务措施进行处理。
虽然悲观锁应用起来很简单而且十分安全,与此同时却有两大问题:
1. 锁定:应用的使用者选择一个记录进行更新,而后去吃午餐,可是没有结束或者丢弃该事务。这样其余全部须要更新该记录的用户就必须等待正在进行实务操做的用户回来而且完成该事务或者直到DBA杀掉该不愉快的事务而且释放锁。
2. 死锁:用户A和B同时更新数据库。用户A锁定了一条记录而且试图请求用户B持有的锁,同时用户B也在等待获取用户A持有的锁。两个事务同时进入了无限等待状态即进入死锁状态。
3、乐观锁
所谓的乐观锁:就是很乐观,每次去拿数据的时候都认为别人不会修改,因此不会上锁,可是在更新的时候会判断一下在此期间别人有没有去更新这个数据。oracle默认使用乐观锁
在乐观锁中,咱们有3种经常使用的作法来实现:
1. 第一种就是在数据取得的时候把整个数据都copy到应用中,在进行提交的时候比对当前数据库中的数据和开始的时候更新前取得的数据。当发现两个数据如出一辙之后,就表示没有冲突能够提交,不然则是并发冲突,须要去用业务逻辑进行解决。
2. 第二种乐观锁的作法就是采用版本戳,这个在Hibernate中获得了使用。采用版本戳的话,首先须要在你有乐观锁的数据库table上创建一个新的column,好比为number型,当你数据每更新一次的时候,版本数就会往上增长1。好比一样有2个session一样对某条数据进行操做。二者都取到当前的数据的版本号为1,当第一个session进行数据更新后,在提交的时候查看到当前数据的版本还为1,和本身一开始取到的版本相同。就正式提交,而后把版本号增长1,这个时候当前数据的版本为2。当第二个session也更新了数据提交的时候,发现数据库中版本为2,和一开始这个session取到的版本号不一致,就知作别人更新过此条数据,这个时候再进行业务处理,好比整个Transaction都Rollback等等操做。在用版本戳的时候,能够在应用程序侧使用版本戳的验证,也能够在数据库侧采用Trigger(触发器)来进行验证。不过数据库的Trigger的性能开销仍是比较的大,因此能在应用侧进行验证的话仍是推荐不用Trigger。
3. 第三种作法和第二种作法有点相似,就是也新增一个Table的Column,不过此次这个column是采用timestamp型,存储数据最后更新的时间。在Oracle9i之后能够采用新的数据类型,也就是timestamp with time zone类型来作时间戳。这种Timestamp的数据精度在Oracle的时间类型中是最高的,精确到微秒(还没与到纳秒的级别),通常来讲,加上数据库处理时间和人的思考动做时间,微秒级别是很是很是够了,其实只要精确到毫秒甚至秒都应该没有什么问题。和刚才的版本戳相似,也是在更新提交的时候检查当前数据库中数据的时间戳和本身更新前取到的时间戳进行对比,若是一致则OK,不然就是版本冲突。若是不想把代码写在程序中或者因为别的缘由没法把代码写在现有的程序中,也能够把这个时间戳乐观锁逻辑写在Trigger或者存储过程当中。
4、结论
1. 若是系统并发量不大且不容许脏读,可使用悲观锁解决并发问题。
2. 若是系统并发很是大的话,悲观锁会带来很大性能问题,因此通常采用乐观锁。
3. 若是系统读比较多,写比较少,也应该使用乐观锁,能够提升吞吐量。
5、参考资料
[1] oracle的乐观锁和悲观锁 http://www.blogjava.net/cheneyfree/archive/2008/01/25/177773.html
[2] 乐观锁和悲观锁的区别 http://www.cnblogs.com/Bob-FD/p/3352216.html
[3] Oracle并发控制中的乐观锁 http://blog.csdn.net/ivory_lei/article/details/7940117