数据库悲观锁和乐观锁

mysql 数据库的悲观锁和乐观锁 悲观锁(Pessimistic Concurrency Control) 当我们要对一个数据库中的一条数据进行修改的时候,为了避免同时被其他人修改,最好的办法就是直接对该数据进行加锁以防止并发。 这种借助数据库锁机制在修改数据之前先锁定,再修改的方式被称之为悲观并发控制(又名“悲观锁”,Pessimistic Concurrency Control,缩写“PCC”)。 之所以叫做悲观锁,是因为这是... 阅读详情

数据库悲观锁和乐观锁

一下是转载的oracle和Mysql两种数据库悲观锁和乐观锁机制及乐观锁实现方式:

一、Oracle

Oracle数据库悲观锁与乐观锁是本文我们主要要介绍的内容。有时候为了得到最大的性能,一般数据库都有并发机制,不过带来的问题就是数据访问的冲突。为了解决这个问题,大多数数据库用的方法就是数据的锁定。

数据的锁定分为两种方法,第一种叫做悲观锁,第二种叫做乐观锁。什么叫悲观锁呢,悲观锁顾名思义,就是对数据的冲突采取一种悲观的态度,也就是说假设数据肯定会冲突,所以在数据开始读取的时候就把数据锁定住。而乐观锁就是认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让用户返回错误的信息,让用户决定如何去做。

先从悲观锁开始说。在SqlServer等其余很多数据库中,数据的锁定通常采用页级锁的方式,也就是说对一张表内的数据是一种串行化的更新插入机制,在任何时间同一张表只会插1条数据,别的想插入的数据要等到这一条数据插完以后才能依次插入。带来的后果就是性能的降低,在多用户并发访问的时候,当对一张表进行频繁操作时,会发现响应效率很低,数据库经常处于一种假死状态。而Oracle用的是行级锁,只是对想锁定的数据才进行锁定,其余的数据不相干,所以在对Oracle表中并发插数据的时候,基本上不会有任何影响。

注:对于悲观锁是针对并发的可能性比较大,而一般在我们的应用中用乐观锁足以。

Oracle的悲观锁需要利用一条现有的连接,分成两种方式,从SQL语句的区别来看,就是一种是for update,一种是for update nowait的形式。比如我们看一个例子。首先建立测试用的数据库表。

CREATE TABLE TEST(ID,NAME,LOCATION,VALUE,CONSTRAINT test_pk PRIMARY KEY(ID))AS SELECT deptno, dname, loc, 1 FROM scott.dept

这里我们利用了Oracle的Sample的scott用户的表,把数据copy到我们的test表中。首先我们看一下for update锁定方式。首先我们执行如下的select for update语句。

select * from test where id = 10 for update

通过这条检索语句锁定以后,再开另外一个sql*plus窗口进行操作,再把上面这条sql语句执行一便,你会发现sqlplus好像死在那里了,好像检索不到数据的样子,但是也不返回任何结果,就属于卡在那里的感觉。这个时候是什么原因呢,就是一开始的第一个Session中的select for update语句把数据锁定住了。由于这里锁定的机制是wait的状态(只要不表示nowait那就是wait),所以第二个Session(也就是卡住的那个sql*plus)中当前这个检索就处于等待状态。当第一个session最后commit或者rollback之后,第二个session中的检索结果就是自动跳出来,并且也把数据锁定住。不过如果你第二个session中你的检索语句如下所示。

select * from test where id = 10

也就是没有for update这种锁定数据的语句的话,就不会造成阻塞了。另外一种情况,就是当数据库数据被锁定的时候,也就是执行刚才for update那条sql以后,我们在另外一个session中执行for update nowait后又是什么样呢。比如如下的sql语句。 由于这条语句中是制定采用nowait方式来进行检索,所以当发现数据被别的session锁定中的时候,就会迅速返回ORA-00054错误,内容是资源正忙, 但指定以 NOWAIT 方式获取资源。所以在程序中我们可以采用nowait方式迅速判断当前数据是否被锁定中,如果锁定中的话,就要采取相应的业务措施进行处理。

select * from test where id = 10 for update nowait

那这里另外一个问题,就是当我们锁定住数据的时候,我们对数据进行更新和删除的话会是什么样呢。比如同样,我们让第一个Session锁定住id=10的那条数据,我们在第二个session中执行如下语句。

update test set value=2 where id = 10

这个时候我们发现update语句就好像select for update语句一样也停住卡在这里,当你第一个session放开锁定以后update才能正常运行。当你update运行后,数据又被你update语句锁定住了,这个时候只要你update后还没有commit,别的session照样不能对数据进行锁定更新等等。

总之,Oracle中的悲观锁就是利用Oracle的Connection对数据进行锁定。在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或者存储过程中

二、Mysql(转自:http://chenzhou123520.iteye.com/blog/1860954)

悲观锁介绍(百科):

悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。

 

使用场景举例:以MySQL InnoDB为例

商品goods表中有一个字段status,status为1代表商品未被下单,status为2代表商品已经被下单,那么我们对某个商品下单时必须确保该商品status为1。假设商品的id为1。

 

1如果不采用锁,那么操作方法如下:

//1.查询出商品信息

select status from t_goods where id=1;

//2.根据商品信息生成订单

insert into t_orders (id,goods_id) values (null,1);

//3.修改商品status为2

update t_goods set status=2;

 

上面这种场景在高并发访问的情况下很可能会出现问题。

前面已经提到,只有当goods status为1时才能对该商品下单,上面第一步操作中,查询出来的商品status为1。但是当我们执行第三步Update操作的时候,有可能出现其他人先一步对商品下单把goods status修改为2了,但是我们并不知道数据已经被修改了,这样就可能造成同一个商品被下单2次,使得数据不一致。所以说这种方式是不安全的。

 

2使用悲观锁来实现:

在上面的场景中,商品信息从查询出来到修改,中间有一个处理订单的过程,使用悲观锁的原理就是,当我们在查询出goods信息后就把当前的数据锁定,直到我们修改完毕后再解锁。那么在这个过程中,因为goods被锁定了,就不会出现有第三者来对其进行修改了。

 

注:要使用悲观锁,我们必须关闭mysql数据库的自动提交属性,因为MySQL默认使用autocommit模式,也就是说,当你执行一个更新操作后,MySQL会立刻将结果进行提交。

 

我们可以使用命令设置MySQL为非autocommit模式:

set autocommit=0;

 

设置完autocommit后,我们就可以执行我们的正常业务了。具体如下:

//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;

 

注:上面的begin/commit为事务的开始和结束,因为在前一步我们关闭了mysql的autocommit,所以需要手动控制事务的提交,在这里就不细表了。

 

上面的第一步我们执行了一次查询操作:select status from t_goods where id=1 for update;

与普通查询不一样的是,我们使用了select…for update的方式,这样就通过数据库实现了悲观锁。此时在t_goods表中,id为1的 那条数据就被我们锁定了,其它的事务必须等本次事务提交之后才能执行。这样我们可以保证当前的数据不会被其它事务修改。

 

注:需要注意的是,在事务中,只有SELECT ... FOR UPDATE 或LOCK IN SHARE MODE 同一笔数据时会等待其它事务结束后才执行,一般SELECT ... 则不受此影响。拿上面的实例来说,当我执行select status from t_goods where id=1 for update;后。我在另外的事务中如果再次执行select status from t_goods where id=1 for update;则第二个事务会一直等待第一个事务的提交,此时第二个查询处于阻塞的状态,但是如果我是在第二个事务中执行select status from t_goods where id=1;则能正常查询出数据,不会受第一个事务的影响。

 

补充:MySQL select…for update的Row Lock与Table Lock

上面我们提到,使用select…for update会把数据给锁住,不过我们需要注意一些锁的级别,MySQL InnoDB默认Row-Level Lock,所以只有「明确」地指定主键,MySQL 才会执行Row lock (只锁住被选取的数据) ,否则MySQL 将会执行Table Lock (将整个数据表单给锁住)。

 

举例说明:

数据库表t_goods,包括id,status,name三个字段,id为主键,数据库中记录如下;

Sql代码 复制代码

  1. mysql> select * from t_goods;   
  2. +----+--------+------+   
  3. | id | status | name |   
  4. +----+--------+------+   
  5. |  1 |      1 | 道具 |   
  6. |  2 |      1 | 装备 |   
  7. +----+--------+------+   
  8. 2 rows in set  
  9.   
  10. mysql>  
mysql> select * from t_goods;+----+--------+------+| id | status | name |+----+--------+------+|  1 |      1 | 道具 ||  2 |      1 | 装备 |+----+--------+------+2 rows in setmysql>

注:为了测试数据库锁,我使用两个console来模拟不同的事务操作,分别用console1、console2来表示。 

 

例1: (明确指定主键,并且有此数据,row lock)

console1:查询出结果,但是把该条数据锁定了

Sql代码 复制代码

  1. mysql> select * from t_goods where id=1 for update;   
  2. +----+--------+------+   
  3. | id | status | name |   
  4. +----+--------+------+   
  5. |  1 |      1 | 道具 |   
  6. +----+--------+------+   
  7. 1 row in set  
  8.   
  9. mysql>  
mysql> select * from t_goods where id=1 for update;+----+--------+------+| id | status | name |+----+--------+------+|  1 |      1 | 道具 |+----+--------+------+1 row in setmysql>

console2:查询被阻塞

Sql代码 复制代码

  1. mysql> select * from t_goods where id=1 for update;  
mysql> select * from t_goods where id=1 for update;

console2:如果console1长时间未提交,则会报错

Sql代码 复制代码

  1. mysql> select * from t_goods where id=1 for update;   
  2. ERROR 1205 : Lock wait timeout exceeded; try restarting transaction  
mysql> select * from t_goods where id=1 for update;ERROR 1205 : Lock wait timeout exceeded; try restarting transaction

 

例2: (明确指定主键,若查无此数据,无lock)

console1:查询结果为空

Sql代码 复制代码

  1. mysql> select * from t_goods where id=3 for update;   
  2. Empty set  
mysql> select * from t_goods where id=3 for update;Empty set

console2:查询结果为空,查询无阻塞,说明console1没有对数据执行锁定

Sql代码 复制代码

  1. mysql> select * from t_goods where id=3 for update;   
  2. Empty set  
mysql> select * from t_goods where id=3 for update;Empty set

 

例3: (无主键,table lock)

console1:查询name=道具 的数据,查询正常

Sql代码 复制代码

  1. mysql> select * from t_goods where name='道具' for update;   
  2. +----+--------+------+   
  3. | id | status | name |   
  4. +----+--------+------+   
  5. |  1 |      1 | 道具 |   
  6. +----+--------+------+   
  7. 1 row in set  
  8.   
  9. mysql>  
mysql> select * from t_goods where name='道具' for update;+----+--------+------+| id | status | name |+----+--------+------+|  1 |      1 | 道具 |+----+--------+------+1 row in setmysql>

console2:查询name=装备 的数据,查询阻塞,说明console1把表给锁住了

Sql代码 复制代码

  1. mysql> select * from t_goods where name='装备' for update;  
mysql> select * from t_goods where name='装备' for update;

console2:若console1长时间未提交,则查询返回为空

Sql代码 复制代码

  1. mysql> select * from t_goods where name='装备' for update;   
  2. Query OK, -1 rows affected  
mysql> select * from t_goods where name='装备' for update;Query OK, -1 rows affected

 

例4: (主键不明确,table lock)

console1:查询正常

Sql代码 复制代码

  1. mysql> begin;   
  2. Query OK, 0 rows affected   
  3.   
  4. mysql> select * from t_goods where id>0 for update;   
  5. +----+--------+------+   
  6. | id | status | name |   
  7. +----+--------+------+   
  8. |  1 |      1 | 道具 |   
  9. |  2 |      1 | 装备 |   
  10. +----+--------+------+   
  11. 2 rows in set  
  12.   
  13. mysql>  
mysql> begin;Query OK, 0 rows affectedmysql> select * from t_goods where id>0 for update;+----+--------+------+| id | status | name |+----+--------+------+|  1 |      1 | 道具 ||  2 |      1 | 装备 |+----+--------+------+2 rows in setmysql>

console2:查询被阻塞,说明console1把表给锁住了

Sql代码 复制代码

  1. mysql> select * from t_goods where id>1 for update;  
mysql> select * from t_goods where id>1 for update;

 

例5: (主键不明确,table lock)

console1:

Sql代码 复制代码

  1. mysql> begin;   
  2. Query OK, 0 rows affected   
  3.   
  4. mysql> select * from t_goods where id<>1 for update;   
  5. +----+--------+------+   
  6. | id | status | name |   
  7. +----+--------+------+   
  8. |  2 |      1 | 装备 |   
  9. +----+--------+------+   
  10. 1 row in set  
  11.   
  12. mysql>  
mysql> begin;Query OK, 0 rows affectedmysql> select * from t_goods where id<>1 for update;+----+--------+------+| id | status | name |+----+--------+------+|  2 |      1 | 装备 |+----+--------+------+1 row in setmysql>

console2:查询被阻塞,说明console1把表给锁住了

Sql代码 复制代码

  1. mysql> select * from t_goods where id<>2 for update;  
mysql> select * from t_goods where id<>2 for update;

console1:提交事务

Sql代码 复制代码

  1. mysql> commit;   
  2. Query OK, 0 rows affected  
mysql> commit;Query OK, 0 rows affected

console2:console1事务提交后,console2查询结果正常

Sql代码 复制代码

  1. mysql> select * from t_goods where id<>2 for update;   
  2. +----+--------+------+   
  3. | id | status | name |   
  4. +----+--------+------+   
  5. |  1 |      1 | 道具 |   
  6. +----+--------+------+   
  7. 1 row in set  
  8.   
  9. mysql>  
mysql> select * from t_goods where id<>2 for update;+----+--------+------+| id | status | name |+----+--------+------+|  1 |      1 | 道具 |+----+--------+------+1 row in setmysql>

 

以上就是关于数据库主键对MySQL锁级别的影响实例,需要注意的是,除了主键外,使用索引也会影响数据库的锁定级别

 

举例:

我们修改t_goods表,给status字段创建一个索引

修改id为2的数据的status为2,此时表中数据为:

Sql代码 复制代码

  1. mysql> select * from t_goods;   
  2. +----+--------+------+   
  3. | id | status | name |   
  4. +----+--------+------+   
  5. |  1 |      1 | 道具 |   
  6. |  2 |      2 | 装备 |   
  7. +----+--------+------+   
  8. 2 rows in set  
  9.   
  10. mysql>  
mysql> select * from t_goods;+----+--------+------+| id | status | name |+----+--------+------+|  1 |      1 | 道具 ||  2 |      2 | 装备 |+----+--------+------+2 rows in setmysql>

 

例6: (明确指定索引,并且有此数据,row lock)

console1:

Sql代码 复制代码

  1. mysql> select * from t_goods where status=1 for update;   
  2. +----+--------+------+   
  3. | id | status | name |   
  4. +----+--------+------+   
  5. |  1 |      1 | 道具 |   
  6. +----+--------+------+   
  7. 1 row in set  
  8.   
  9. mysql>  
mysql> select * from t_goods where status=1 for update;+----+--------+------+| id | status | name |+----+--------+------+|  1 |      1 | 道具 |+----+--------+------+1 row in setmysql>

console2:查询status=1的数据时阻塞,超时后返回为空,说明数据被console1锁定了

Sql代码 复制代码

  1. mysql> select * from t_goods where status=1 for update;   
  2. Query OK, -1 rows affected  
mysql> select * from t_goods where status=1 for update;Query OK, -1 rows affected

console2:查询status=2的数据,能正常查询,说明console1只锁住了行,未锁表

Sql代码 复制代码

  1. mysql> select * from t_goods where status=2 for update;   
  2. +----+--------+------+   
  3. | id | status | name |   
  4. +----+--------+------+   
  5. |  2 |      2 | 装备 |   
  6. +----+--------+------+   
  7. 1 row in set  
  8.   
  9. mysql>  
mysql> select * from t_goods where status=2 for update;+----+--------+------+| id | status | name |+----+--------+------+|  2 |      2 | 装备 |+----+--------+------+1 row in setmysql>

 

例7: (明确指定索引,若查无此数据,无lock)

console1:查询status=3的数据,返回空数据

Sql代码 复制代码

  1. mysql> select * from t_goods where status=3 for update;   
  2. Empty set  
mysql> select * from t_goods where status=3 for update;Empty set

console2:查询status=3的数据,返回空数据

Sql代码 复制代码

  1. mysql> select * from t_goods where status=3 for update;   
  2. Empty set  
mysql> select * from t_goods where status=3 for update;Empty set

 

 

以上就是关于我对数据库悲观锁的理解和总结,有不对的地方欢迎拍砖,下一次会带来数据库乐观锁的总结和实践

 

参考资料:

MySQL事务与锁定命令:http://www.docin.com/p-16805970.html

悲观锁:http://www.cnblogs.com/chenwenbiao/archive/2012/06/06/2537508.html 

乐观锁介绍:

乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。那么我们如何实现乐观锁呢,一般来说有以下2种方式:

1.使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。用下面的一张图来说明:

如上图所示,如果更新操作顺序执行,则数据的版本(version)依次递增,不会产生冲突。但是如果发生有不同的业务操作对同一版本的数据进行修改,那么,先提交的操作(图中B)会把数据version更新为2,当A在B之后提交更新时发现数据的version已经被修改了,那么A的更新操作会失败。

 

2.乐观锁定的第二种实现方式和第一种差不多,同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。

 

使用举例:以MySQL InnoDB为例

还是拿之前的实例来举:商品goods表中有一个字段status,status为1代表商品未被下单,status为2代表商品已经被下单,那么我们对某个商品下单时必须确保该商品status为1。假设商品的id为1。

 

下单操作包括3步骤:

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};

 

那么为了使用乐观锁,我们首先修改t_goods表,增加一个version字段,数据默认version值为1。

t_goods表初始数据如下:

Sql代码 复制代码

  1. mysql> select * from t_goods;   
  2. +----+--------+------+---------+   
  3. | id | status | name | version |   
  4. +----+--------+------+---------+   
  5. |  1 |      1 | 道具 |       1 |   
  6. |  2 |      2 | 装备 |       2 |   
  7. +----+--------+------+---------+   
  8. 2 rows in set  
  9.   
  10. mysql>  

[sql] view plain copy

 

  1. mysql> select * from t_goods;  
  2. +----+--------+------+---------+  
  3. | id | status | name | version |  
  4. +----+--------+------+---------+  
  5. |  1 |      1 | 道具 |       1 |  
  6. |  2 |      2 | 装备 |       2 |  
  7. +----+--------+------+---------+  
  8. 2 rows in set  
  9.   
  10. mysql>  

对于乐观锁的实现,我使用MyBatis来进行实践,具体如下:

Goods实体类:

Java代码 复制代码

  1. /**  
  2.  * ClassName: Goods <br/>  
  3.  * Function: 商品实体. <br/>  
  4.  * date: 2013-5-8 上午09:16:19 <br/>  
  5.  * @author chenzhou1025@126.com  
  6.  */  
  7. public class Goods implements Serializable {   
  8.   
  9.     /**  
  10.      * serialVersionUID:序列化ID.  
  11.      */  
  12.     private static final long serialVersionUID = 6803791908148880587L;   
  13.        
  14.     /**  
  15.      * id:主键id.  
  16.      */  
  17.     private int id;   
  18.        
  19.     /**  
  20.      * status:商品状态:1未下单、2已下单.  
  21.      */  
  22.     private int status;   
  23.        
  24.     /**  
  25.      * name:商品名称.  
  26.      */  
  27.     private String name;   
  28.        
  29.     /**  
  30.      * version:商品数据版本号.  
  31.      */  
  32.     private int version;   
  33.        
  34.     @Override  
  35.     public String toString(){   
  36.         return "good id:"+id+",goods status:"+status+",goods name:"+name+",goods version:"+version;   
  37.     }   
  38.   
  39.     //setter and getter   
  40.   
  41. }  

[java] view plain copy

 

  1. /** 
  2.  * ClassName: Goods <br/> 
  3.  * Function: 商品实体. <br/> 
  4.  * date: 2013-5-8 上午09:16:19 <br/> 
  5.  * @author chenzhou1025@126.com 
  6.  */  
  7. public class Goods implements Serializable {  
  8.   
  9.     /** 
  10.      * serialVersionUID:序列化ID. 
  11.      */  
  12.     private static final long serialVersionUID = 6803791908148880587L;  
  13.       
  14.     /** 
  15.      * id:主键id. 
  16.      */  
  17.     private int id;  
  18.       
  19.     /** 
  20.      * status:商品状态:1未下单、2已下单. 
  21.      */  
  22.     private int status;  
  23.       
  24.     /** 
  25.      * name:商品名称. 
  26.      */  
  27.     private String name;  
  28.       
  29.     /** 
  30.      * version:商品数据版本号. 
  31.      */  
  32.     private int version;  
  33.       
  34.     @Override  
  35.     public String toString(){  
  36.         return "good id:"+id+",goods status:"+status+",goods name:"+name+",goods version:"+version;  
  37.     }  
  38.   
  39.     //setter and getter  
  40.   
  41. }  

GoodsDao

Java代码 复制代码

  1. /**  
  2.  * updateGoodsUseCAS:使用CAS(Compare and set)更新商品信息. <br/>  
  3.  *  
  4.  * @author chenzhou1025@126.com  
  5.  * @param goods 商品对象  
  6.  * @return 影响的行数  
  7.  */  
  8. int updateGoodsUseCAS(Goods goods);  

[java] view plain copy

 

  1. /** 
  2.  * updateGoodsUseCAS:使用CAS(Compare and set)更新商品信息. <br/> 
  3.  * 
  4.  * @author chenzhou1025@126.com 
  5.  * @param goods 商品对象 
  6.  * @return 影响的行数 
  7.  */  
  8. int updateGoodsUseCAS(Goods goods);  

mapper.xml

Xml代码 复制代码

  1. <update id="updateGoodsUseCAS" parameterType="Goods">  
  2.     <![CDATA[  
  3.         update t_goods  
  4.         set status=#{status},name=#{name},version=version+1  
  5.         where id=#{id} and version=#{version}  
  6.     ]]>  
  7. </update>  

[xml] view plain copy

 

  1. <update id="updateGoodsUseCAS" parameterType="Goods">  
  2.     <![CDATA[ 
  3.         update t_goods 
  4.         set status=#{status},name=#{name},version=version+1 
  5.         where id=#{id} and version=#{version} 
  6.     ]]>  
  7. </update>  

GoodsDaoTest测试类

Java代码 复制代码

  1. @Test  
  2. public void goodsDaoTest(){   
  3.     int goodsId = 1;   
  4.     //根据相同的id查询出商品信息,赋给2个对象   
  5.     Goods goods1 = this.goodsDao.getGoodsById(goodsId);   
  6.     Goods goods2 = this.goodsDao.getGoodsById(goodsId);   
  7.        
  8.     //打印当前商品信息   
  9.     System.out.println(goods1);   
  10.     System.out.println(goods2);   
  11.        
  12.     //更新商品信息1   
  13.     goods1.setStatus(2);//修改status为2   
  14.     int updateResult1 = this.goodsDao.updateGoodsUseCAS(goods1);   
  15.     System.out.println("修改商品信息1"+(updateResult1==1?"成功":"失败"));   
  16.        
  17.     //更新商品信息2   
  18.     goods1.setStatus(2);//修改status为2   
  19.     int updateResult2 = this.goodsDao.updateGoodsUseCAS(goods1);   
  20.     System.out.println("修改商品信息2"+(updateResult2==1?"成功":"失败"));   
  21. }  

[java] view plain copy

 

  1. @Test  
  2. public void goodsDaoTest(){  
  3.     int goodsId = 1;  
  4.     //根据相同的id查询出商品信息,赋给2个对象  
  5.     Goods goods1 = this.goodsDao.getGoodsById(goodsId);  
  6.     Goods goods2 = this.goodsDao.getGoodsById(goodsId);  
  7.       
  8.     //打印当前商品信息  
  9.     System.out.println(goods1);  
  10.     System.out.println(goods2);  
  11.       
  12.     //更新商品信息1  
  13.     goods1.setStatus(2);//修改status为2  
  14.     int updateResult1 = this.goodsDao.updateGoodsUseCAS(goods1);  
  15.     System.out.println("修改商品信息1"+(updateResult1==1?"成功":"失败"));  
  16.       
  17.     //更新商品信息2  
  18.     goods1.setStatus(2);//修改status为2  
  19.     int updateResult2 = this.goodsDao.updateGoodsUseCAS(goods1);  
  20.     System.out.println("修改商品信息2"+(updateResult2==1?"成功":"失败"));  
  21. }  

输出结果:

Shell代码 复制代码

  1. good id:1,goods status:1,goods name:道具,goods version:1  
  2. good id:1,goods status:1,goods name:道具,goods version:1  
  3. 修改商品信息1成功   
  4. 修改商品信息2失败  

说明:

在GoodsDaoTest测试方法中,我们同时查出同一个版本的数据,赋给不同的goods对象,然后先修改good1对象然后执行更新操作,执行成功。然后我们修改goods2,执行更新操作时提示操作失败。此时t_goods表中数据如下:

Sql代码 复制代码

  1. mysql> select * from t_goods;   
  2. +----+--------+------+---------+   
  3. | id | status | name | version |   
  4. +----+--------+------+---------+   
  5. |  1 |      2 | 道具 |       2 |   
  6. |  2 |      2 | 装备 |       2 |   
  7. +----+--------+------+---------+   
  8. 2 rows in set  
  9.   
  10. mysql>   

[sql] view plain copy

 

  1. mysql> select * from t_goods;  
  2. +----+--------+------+---------+  
  3. | id | status | name | version |  
  4. +----+--------+------+---------+  
  5. |  1 |      2 | 道具 |       2 |  
  6. |  2 |      2 | 装备 |       2 |  
  7. +----+--------+------+---------+  
  8. 2 rows in set  
  9.   
  10. mysql>   

我们可以看到 id为1的数据version已经在第一次更新时修改为2了。所以我们更新good2时update where条件已经不匹配了,所以更新不会成功,具体sql如下:

Sql代码 复制代码

  1. update t_goods    
  2. set status=2,version=version+1   
  3. where id=#{id} and version=#{version};  

[sql] view plain copy

 

  1. update t_goods   
  2. set status=2,version=version+1  
  3. where id=#{id} and version=#{version};  

这样我们就实现了乐观锁

OpenIddict实战:5步搞定.NET Core应用的OIDC单点登录集成 本文详细介绍了如何使用OpenIddict在.NET Core应用中快速实现OIDC单点登录集成。通过五个关键步骤,包括环境准备、数据库集成、客户端注册、令牌配置和多应用SSO集成,帮助开发者高效构建安全的企业级单点登录解决方案。 阅读详情

相关推荐

CODESYS Softmotion(一)功能介绍

一、CODESYS SoftMotion RTE SL产品 1.1 CODESYS Runtime SL 产品家族介绍 目前支持的主流厂商的硬件产品,让部署在上面的成为实时设备。 对于Windows系统,根据需求选择对应的Runtime: RTE极大的提高了实时性,适合进行运动控制,同时RTE还支持了多个主从站协议: 还可以选择可选的功能(Single License): RTE对硬件和系统有要求,如果你需要硬件功能,那么网卡会有最低要求。 1.2 RTE的下载和安装 在官网http://codes

Fishfishfishfishcat 2万+

乐观锁和悲观锁

为什么需要锁(并发控制) 在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对同一数据进行操作)。并发虽然是必须的,但可能会导致以下的问题。 丢失更新:一个事务的更新覆盖了其它事务的更新结果,就是所谓的更新丢失。例如:用户A把值从6改为2,用户B把值从2改为6,则用户A丢失了他的更新。 脏读:当一个事务读取其它完成一半事务的记录时,就会发生脏读取。例如:用...

qq_43059674的博客 484

Power Bi数据分析案例:在线零售平台销售数据分析

该数据集为,涵盖2023年1月至2024年3月,包含用户群体特征、购买行为、产品偏好以及参与度指标。用户维度:Customer_ID,Age,Gender,City产品维度:Product_Category,Unit_Price事实表:Order_ID,Quantity,Total_Amount,Discount_Amount,Rating,Delivery_Time在Power Query当中处理销售日期列,并新增年龄段、浏览页面分段列。

2301_79586168的博客 1278

数据库中的悲观锁和乐观锁

为了得到最大的性能,一般数据库都有并发机制,不过带来的问题就是数据访问的冲突。为了解决这个问题,大多数数据库用的方法就是数据的锁定。 1、悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部...

cuihuang1302的博客 212

Python自动化生成HyperMesh Tcl命令流:从几何建模到有限元分析

本文详细介绍了如何利用Python自动化生成HyperMesh的Tcl命令流,实现从几何建模到有限元分析的全流程二次开发。通过封装常用操作函数,工程师可以高效完成参数化建模、自动网格划分及求解器文件输出,显著提升CAE前处理效率与标准化水平。

weixin_29041767的博客 424

数字孪生模型构建理论及应用

源自:计算机集成制造系统作者:陶飞 张贺 戚庆林 徐 俊 孙铮 胡天亮 刘晓军 刘庭煜 关俊涛 陈畅宇 孟凡伟 张辰源 李志远 魏永利 朱铭浩 肖斌数字孪生作为实现数字化转型和促进智能化升级的重要使能途径,一直备受各行各业关注,已从理论研究走向了实际应用阶段。数字孪生是以多维虚拟模型和融合数据双驱动,通过虚实闭环交互,来实现监控、仿真、预测、优化等实际功能服务和应用需求,其中数字孪生模型构建是实现数字孪生落地应用的前提。

renhongxia1的博客 1万+

mybatis悲观锁

作用:让线程1获取的数据库记录上锁,其他线程获取 不到线程1这条数据记录。只有等线程1执行完 放锁之后,其他线程才能对线程1操作的记录进 行操作。上图两个测试方法是对数据库的表数据进行测试,根据之前写的两个方法来对标数据操作。7.问题来了,线程1对数据库的修改过后的数据,线程2确并没有拿线程1修改的数据去更改,这就导致线程1修改的数据被线程2覆盖了。在对线程2进行测试,获取数据库的数据。这是数据库原本的数据。

weixin_62975608的博客 706

从PCEP到PCAP:一个数据分析师的Python认证升级之路与学习规划

本文详细介绍了数据分析师从PCEP(初级)到PCAP(中级)的Python认证升级路径,包括考试内容、备考策略和时间规划。通过系统化的学习,读者可以掌握Python在数据分析中的核心应用,提升职业竞争力。文章还提供了实用的代码示例和认证考试对比,帮助读者高效备考。

weixin_27427619的博客 440

数据库中的乐观锁与悲观锁

当我们提交更新的时候,判断数据库表对应记录的当前库存数与第一次取出来的库存数进行比对,如果数据库表当前库存数与第一次取出来的库存数相等,则予以更新,否则认为是过期数据。乐观锁( Optimistic Locking ) 是相对悲观锁而言的,乐观锁假设数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。以上,在对id = 1的记录修改前,先通过for update的方式进行加锁,然后再进行修改。...

很多时候犯错都是在不知情的情况下发生的 6975

【存储原理】IO超时处理 块层block-scsi层之间

【存储原理】IO超时处理 块层block-scsi层之间

sinat_37817094的博客 1024

你知道几种数字地与模拟地连接方式及优缺点?

频率选择性:磁珠的阻抗特性随频率变化,只对某个频点的噪声有显著抑制作用,对于频率不确定或无法预知的情况,可能不是最佳选择。在实际应用中,选择合适的连接方式需要根据具体的电路频率、信号类型、设计要求等因素综合考虑,以达到最佳的电磁兼容效果.高频信号干扰:在高频电路中,单点接地可能导致信号回流路径过长,增加地线电感,从而引入高频噪声。限制噪声电流:0欧姆电阻相当于一个非常窄的电流通路,能够有效地限制环路电流,使噪声得到抑制。抑制高频信号:电感对高频信号有较大的阻碍作用,可以有效抑制高频噪声。

linkking27的博客 801

【DB锁】数据库中-乐观锁与悲观锁

乐观锁和悲观锁的区别(最全面的分析)        悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系...

Life is tragedy,Need you to struggle,to fighting.. 1998

7.6 GPU页表更新机制

本文分析了AMDGPU驱动中BO到VA映射的页表更新实现机制。核心流程通过amdgpu_vm_bo_update()函数触发,支持CPU直接写入和SDMA异步命令两种更新模式。系统根据VRAM可见性自动选择最优更新策略,处理三种主要场景:命令提交、BO迁移和BO销毁。关键实现包括DMA地址数组处理、权限标志合并和BO迁移状态转换,最终通过amdgpu_vm_update_range()完成硬件页表更新。该机制有效平衡了更新延迟与吞吐量,为GPU虚拟内存管理提供基础支撑。

图形/计算GPU的驱动与虚拟化技术分享园地 2470

数据库乐观锁,悲观锁的区别,怎么实现

【面试干货】数据库乐观锁,悲观锁的区别,怎么实现

✨ 欢迎来到【Seal ^_^ 的优快云博客】!✨ 6890

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

数据库的乐观锁和悲观锁是什么?

两种锁各有优缺点,不可认为一种好于另一 种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。

南_茗的博客 1935

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

数据库的悲观锁和乐观锁

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录前言一、悲观锁和乐观锁的定义二、悲观锁的实现1.引入库2.读入数据总结 前言 提示:这里可以添加本文要记录的大概内容: 例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。 提示:以下是本篇文章正文内容,下面案例可供参考 一、悲观锁和乐观锁的定义 悲观锁:对数据的冲突采取一个悲观的态度,认定数据会冲突,为了避免冲突,在对数据操作时就加上锁。这种在对数据修改之前

shyany的博客 1813

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

分布式锁4 :数据库DB实现分布式锁的悲观锁和乐观锁,unique实现方式

在读取数据时锁住那几行,其他对这几行的更新需要等到悲观锁结束时才能继续。,交出CPU资源,可以把CPU给其他线程使用,提高了CPU的利用率。

健康平安的活着的专栏 1534

数据库之乐观锁和悲观锁篇

冲突在多用户环境中,在同一时间可能会有多个用户更新相同的记录,这会产生冲突。这就是著名的并发性问题。典型的冲突有: 丢失更新:一个事务的更新覆盖了其它事务的更新结果,就是所谓的更新丢失。例如:用户A把值从6改为2,用户B把值从2改为6,则用户A丢失了他的更新。 脏读:当一个事务读取其它完成一半事务的记录时,就会发生脏读取。例如:用户A,B看到的值都是6,用户B把值改为2,用户A读到的值

#include<?> 8104

数据库以及java线程中的乐观锁与悲观锁

适合写操作频繁、冲突概率高的场景,优点是能有效防止冲突,缺点是并发性能较差。

m0_69966537的博客 1223

数据库 并发更新之乐观锁和悲观锁

文章目录1. 问题引出2. 数据库悲观锁解决并发更新3. 数据库乐观锁解决并发更新4. 乐观锁 CAS 的 ABA 问题5. 拓展思考5.1. 悲观锁和排他锁、乐观锁和 CAS 分别有什么区别5.2. 悲观锁和乐观锁适用场景5.3. 乐观锁是否必须加版本号或时间戳字段 1. 问题引出 假设现在有一张 item 商品表,quantity 字段表示该商品的数量。 这时候有一个用户下了订单,购买一件商品。那么我们可以用以下 SQL 来实现这个逻辑 UPDATE item SET quantity = quanti

2106

Oracle数据库悲观锁与乐观锁详解

详见: http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp88 Oracle数据库悲观锁与乐观锁详解 Oracle数据库悲观锁与乐观锁是本文我们主要要介绍的内容。有时候为了得到最大的性能,一般数据库都有并发机制,不过带来的问题就是数据访问的冲突。为了解决这个问题,大多数数据库用的方法就是数据的锁定。 数据的锁定分

yemou_blog的博客 1万+

数据库的乐观锁和悲观锁

悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作,假设数据肯定会冲突,所以在数据开始读取的时候就把数据锁定住。【数据锁定:数据将暂时不会得到修改】 乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让用户返回错误的信息。让用户决定如何去做。 1.乐观锁是一种思想,实际实现中,表中有一个版本字段,首先要先获取到要更新的行的版本字段,当需要对某行或某几行更新时,查看该字段与第一次获取的是否相等,一.

HWQlet的博客 475

理解数据库之悲观锁和乐观锁

数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。而乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段。 概念 乐观锁和悲观锁可以说是一种思想,一种人们为了解决并发控制带来的问题而定义好的策略。这里需要注意的是千万不能将乐观锁和悲观锁的概念狭义地理解为数据库中提供的锁机制,例如行锁、表锁、...

keYuK0的博客 387

数据库中悲观锁和乐观锁

悲观锁:悲观的觉得别人一定会修改我的数据。悲观锁有两种,读锁和写锁。 1.读锁:也叫共享锁。共享数据对象上锁权,大家都可以上锁,我上了一把读锁,你也可以上,但是只能上共享锁。可以使自己和别人不能修改数据,只能读取。 我开启事务 select * from student    LOCK IN  SHARE MODE //把select出来的数据对象锁定 update studen

heronos的博客 6764
上一篇: 基础篇(一)幂等性
下一篇: VMware虚拟机下CentOS 6.5配置IP地址的三种方法
javaee1234
博客等级 码龄10年 49粉丝 24原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值