如何处理并发修改同一条数据,乐观锁,悲观锁的实现及应用场景
如何处理并发修改同一条数据,乐观锁,悲观锁的实现及应用场景
为什么要加锁?下面通过一个例子来说明
id | name | amount |
---|---|---|
1 | 小明 | 1000 |
小明的账户上有1000 元 ,现在有两个线程同时往他账户上加钱。
-
A 线程准备往小明的账户上加100, 1, 读取到小明 有 1000 元, 1000 + 100 事务未提交;
-
B 线程准备往小明的账户上加200, 1, 读取到小明 有 1000 元 ,1000 + 200 事务未提交
; -
A 线程提交事务 小明账户余额变成1100, 但这时B 线程还不知道小明账户余额变成1100了;
id name amount 1 小明 1100 -
B 线程提交事务 小明账户余额变成1200
.id name amount 1 小明 1200 A 线程的更新丢失 本来余额应该是1300,结果余额变成1200
加锁的目的在于保障 一个线程修改数据时这个数据没有被其他线程改过。
悲观锁
读取的时候就对该数据加锁(排他锁),比如对id 为 1 的数据加了锁,其余的线程加锁读取该数据时将被阻塞。如下
-- mysql ,关键在于 FOR UPDATE
START TRANSACTION;
SELECT * FROM USER WHERE id = 1 FOR UPDATE ;
其余线程以上面SQL 加锁去查询将被阻塞,直至已加锁线程释放锁。事务提交时 commit;
锁将被释放。注意:不加锁的读取将不被阻塞,就算该条数据已经被其他线程加锁了。如下面这条sql:
-- mysql , 不管id = 1 的数据有没有加锁,该条查询也不会被阻塞。
START TRANSACTION;
SELECT * FROM USER WHERE id = 1;
总结:每次读取时总认为可能数据会被修改,每次查询都加锁。被锁住的数据同时只能有一个线程读取,因此称为悲观锁。
乐观锁
在表中新增一列 该列不参与业务逻辑,仅记录数据版本,如下:
id | name | amount | version |
---|---|---|---|
1 | 小明 | 1100 | 0 |
-
A 线程准备往小明的账户上加100, 1, 读取到小明 有 1000 元, 1000 + 100 事务未提交 ,读取到的版本号(oversion)为0;
-
B线程准备往小明的账户上加100, 1, 读取到小明 有 1000 元, 1000 + 100 事务未提交 ,读取到的版本号(oversion)为0;
; -
A 线程提交事务,对比版本号,如果数据库该条数据的版本号和线程所持有该条数据的版本号一致,说明数据没有修改过。更新余额以及版本号 小明账户余额变成1100 版本号(version)变成+ 1,
id name amount version 1 小明 1100 1 -
B 线程提交事务 对比版本号,发现说持有的数据和数据中的版本不一致。本次事务回滚
.id name amount version 1 小明 1200 1
乐观锁和悲观锁各自适合的场景
-
乐观锁 适合查多改少,经常被并发修改的数据可能老是出现版本不一致导致有的线程操作常常失败。
-
悲观锁 适合短事务(长事务导致其它事务一直被阻塞,影响系统性能),查少该多。
总结
并没有什么乐观锁比 悲观锁好,它们所适应的是不用的场景。你可以根据你的业务选择合适的锁。