先提出讨论一个问题: 数据库修改 库存 应该怎么写?
sql大概这么写: update goods set num = newNum where id = #{id }; 如果两个线程都在 写 那么很容易 被冲掉修改。
很多时候我们会考虑数据的并发,会考虑加锁。
悲观乐观的角度
数据库有悲观锁 和乐观锁。
悲观锁写法:
select * from goods where id = #{id} for update;
update goods set num = newNum where id = #{id };
分析: 在 for update 的时候,已经个这个行 数据加了 一个悲观的 写锁。这时候别的 关于这行的修改操作需要获取这个行的锁,明显获取不到,也就是 只有打事务提交以后 别的关于写这行的事务才有机会获取锁,才能执行。 不会出并发问题。
我们在看,锁的时间 forupdate 查询 到事务提交完成都占着。
乐观锁的写法: 乐观锁不是 读锁,也不是写锁,可以说不是锁,是一种 很乐观的标记,乐观的认为这个99% 可以成功,并且如果真的出了并发问题,我也能知道,并且重试。 这种思想叫做 CAS ,检查并且更新。
select * from goods where id = #{id} ;
update goods set num = #{ newNum },version = version+1 where id = #{id } and version = #{ oldVersion};
分析: 查询的时候其实做了一个 标记 version ,但是没有在这行上面加 独占锁 。 update的时候如果version 没有改变,那么我们认定 这行记录没有被修改过。 否则 说明 这个 操作冲突了,这时候应该 从试。
然后我们仔细思考 数据库的 写入其实自己会加锁的。写锁也就是独占锁。所以在A 事务写入数据的时候回获取到这吧写锁,知道A事务提交才释放,这时候 B事务拿不到这个锁,对于这行数据的写入会排队。死哦如操作是串行 的了。
既然这样我们大可以这样写: update goods set num = #{ newNum } where id = #{id } ; 2 个事务如果同时执行,桶一瞬间只有一个在执行者句话 ,也就不会出并发问题。
但是真的不会出问题吗?
写法1 update goods set num = #{ newNum } where id = #{id } ; 备注 newNum = 101
写法2 update goods set num = num +1 where id = #{id } ;
一样吗? 那种会出问题?
明显写法2 不会出问题,前面已经说了 同一个锁的数据写入 是排序的( 这里一般是行级锁 或者间隙锁 )
写法1 为啥会出问题了? 因为 set num = 101 ,看起来是对的,但是 其实 并发的时候就错了。问题出在哪里?出在我们查询到100 的时候没有加锁( 读写锁规定,读的时候加读锁,读锁一加,写锁就加不上,但是mysql 不是这样的,mysql 的读是非锁定读 ),
因为没有加读锁,所以事务B可以获取写锁,吧 100 改成 101。 但是我们程序还是用的 100.问题就出了。
怎么解决?
1 在 查询的时候就加锁 这样 后面 就不会被别人修改了。 上面 for update 的写法。 效率最低,但是非常有效。而且简单易懂。
2 我认为不会有人来改,但是如果有人来改我会知道并且重试 乐观锁写法。 100% 的 可以用,需要设置 version字段,然后 并发操作以后,需要重试 。 如果并发太多反复 重试,不如直接 悲观锁。比如90% 重试,不如直接排队读了在写。
3 我 如果我修改的时候 不是 前面查询的值 而是去的 当前的最新数据。 也不会出问题。( 简单的 加减可以,但是复杂的计算,比通过 当前值去别的系统 的得到一个值 ,这种情况就不行了 ) 100% 不冲突,但是不是100% 的情况可以用。
读锁会阻止写锁,所以mysql 为了效率是不会 加 读锁的,在读的时候不能写,那效率的多低 ?
读懂以后,什么时候怎么用 很明显了吧。