乐观锁:假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的 那部分数据。在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,正在提交的事务会进行回 滚。
乐观锁,不会使用数据库提供的锁机制,大多是基于数据版本(Version)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加1。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
实现乐观锁的两种机制:1.使用版本号机制:一个是数据版本机制,一个是时间戳机制。2.使用条件限制
悲观锁(Pessimistic Lock):假定会发生并发冲突,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。
通常来讲在数据库上的悲观锁需要数据库本身提供支持,即通过常用的select … for update操作来实现悲观锁。当数据库执行select for update时会获取被select中的数据行的行锁,因此其他并发执行的select for update如果试图选中同一行则会发生排斥(需要等待行锁被释放),因此达到锁的效果。select for update获取的行锁会在当前事务结束时自动释放,因此必须在事务中使用。
这里需要注意的一点是不同的数据库对select for update的实现和支持都是有所区别的,例如oracle支持select for update no wait,表示如果拿不到锁立刻报错,而不是等待,mysql就没有no wait这个选项。另外mysql还有个问题是select for update语句执行中所有扫描过的行都会被锁上,这一点很容易造成问题。因此如果在mysql中用悲观锁务必要确定走了索引,而不是全表扫描。
Java synchronized 就属于悲观锁的一种实现,每次线程要修改数据时都先获得锁,保证同一时刻只有一个线程能操作数据,其他线程则会被block。
悲观锁详解中有一段话:在事务中,只有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;则能正常查询出数据,不会受第一个事务的影响(Read committed与Repeatable read 隔离级别下,普通select不会对返回的记录加锁)
悲观锁(共享锁)与排他锁的区别:上了排它锁的数据,既不允许别的事务去上共享锁,也不允许其他事务去改(上排它锁);但悲观锁与排他锁不完全相同,悲观锁要依赖数据库的锁机制,即排他锁来实现功能。
补充:MySQL select…for update的Row Lock与Table Lock 上面我们提到,使用select…for update会把数据给锁住,不过我们需要注意一些锁的级别,MySQL InnoDB默认Row-Level Lock,所以只有「明确」地指定主键/索引,且数据存在时,MySQL 才会执行Row lock (只锁住被选取的数据) ,若无数据,则不上锁;否则,当主键不明确(id>3)时,MySQL 将会执行Table Lock (将整个数据表单给锁住)。
乐观锁与悲观锁之间的区别:1.概念 2.是否使用数据库的锁机制 3. 实现