一、悲观锁
悲观锁(Pessimistic Lock),每次拿到数据时都会担心被别人修改,所以每次在拿数据的时候都会上锁,确保自己使用的过程中不会被被人修改,使用完后再释放锁。悲观锁常见的实现场景:
- a. 数据库:通过select … for update;上锁,直到事务commit或rollback
- b. synchronized:操作前先上同步锁
- c. 分布式锁:操作前先上分布式锁,操作完成后释放锁
以第一种数据库层的悲观锁为例,在关系型数据库中,悲观并发控制(Pessimistic Concurrency Control, 简写为PCC)被称为悲观锁,它是一种数据库层的并发控制方法,如果一个事务执行的操作对一行数据上了锁,那只有当这个事务释放锁,其他事务才执行与该锁冲突的操作。
本文使用MySQL InnoDB做测试,MySQL默认使用autocommit模式,也就是说执行一个更新操作后,MySQL会立即将结果提交,因此,为便于测试需要关闭自动提交模式.
开启MySQL-console1:
mysql> set autocommit=0;
创建的测试数据如下:
mysql> select * from user;
---
+----+---------+-----+
| id | name | age |
+----+---------+-----+
| 1 | hehe | 29 |
| 2 | Tonglin | 25 |
+----+---------+-----+
开启并执行事务:
mysql> begin; # 同begin work/start transaction
mysql> select * from user where id=1 for update;
mysql> update user set age=100 where id=1;
此时,开启MySQL-console2,对同一行记录进行查询或修改:
mysql> select * from user where id=1;
---
+----+------+-----+
| id | name | age |
+----+------+-----+
| 1 | hehe | 29 |
+----+------+-----+
1 row in set (0.00 sec)
mysql> update user set age=99 where id=1;
---
在MySQL-console2上可以对id=1的记录进行查询,但修改操作会被阻塞,直到MySQL-console1上的事务提交或回滚。
MySQL-console1:
mysql> commit; # commit work,或回滚rollback
MySQL-console2:
mysql> update user set age=99 where id=1;
---
Query OK, 1 row affected (0.00 sec)
二、乐观锁
乐观锁(Optimistic Lock),每次拿数据的时候完全不担心被别人修改,所以每次拿数据的时候都不会上锁,但是会在更新数据的时候判断是否被别人修改过,期间数据可以被其他人读取。悲观锁常见的实现场景:
- a. 数据库:版本号机制
- b. CAS操作
仍然以数据库为例,在关系型数据库中,乐观并发控制(Optimistic Concurrency Control, 简写为OCC)被称为乐观锁。在提交数据库更新前会检查其他事务是否已经修改了数据,如果有更新,则当前提交的事务进行回滚。
首先为数据库表增加version字段:
mysql> select * from user;
---
+----+---------+-----+---------+
| id | name | age | version |
+----+---------+-----+---------+
| 1 | hehe | 99 | 0 |
| 2 | Tonglin | 25 | 0 |
+----+---------+-----+---------+
在执行更新前先查询数据库,将版本号version字段一同读出,数据每更新一次将version值加1。模拟并发的场景,假设两个线程读取了相同的记录数据,并尝试对相应主键(id=1)和版本号(version=1)的就进行修改,并将其版本号加1,这样只有第一个执行的线程可以执行成功,第二个线程执行时版本号已经变为2,因此更新失败。
select * from user where id=1;
---
+----+------+-----+---------+
| id | name | age | version |
+----+------+-----+---------+
| 1 | hehe | 1 | 1 |
+----+------+-----+---------+
mysql> update user set age=2, version=version+1 where id=1 and version=1;
---
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> update user set age=3, version=version+1 where id=1 and version=1;
---
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0 Changed: 0 Warnings: 0
三、优缺点对比
两种锁各有优缺点,不能单纯说哪个更好,需要根据具体业务场景决定使用哪一种锁。
- 3.1 乐观锁比较适合数据修改比较少,读取比较频繁的场景,即使出现了少量写冲突,也省去了大量锁的开销,因此提高了系统的吞吐量。
- 3.2 悲观锁比较适合数据修改较多的场景,悲观锁通过锁记录减少冲突,从而减少上层retry,可以提升系统性能。