如果将悲观锁(Pessimistic Lock)对应到现实生活中来。悲观锁有点像是一位悲观的人,总是会假设最坏的情况,避免出现问题。
什么是悲观锁?
悲观锁是一种在并发控制中使用的策略,它假设最坏的情况,即默认认为数据在处理过程中极有可能被其他事务修改。因此,当一个事务开始读取数据时,悲观锁会立即对数据加锁,阻止其他事务对该数据进行任何修改,直到当前事务完成(提交或回滚)后才释放锁。这种机制可以有效避免数据冲突,但可能会导致较低的并发性能,因为其他事务可能需要等待锁的释放才能继续执行。
Java 中synchronized
和ReentrantLock
等独占锁就是悲观锁思想的实现。
悲观锁适用于写操作较多的场景,或者数据竞争非常激烈的环境。在数据库管理系统中,实现悲观锁通常通过以下几种方式:
- 行级锁:只锁定被访问的数据行。
- 表级锁:锁定整个表,所有试图访问该表的其他事务都必须等待。
- 共享锁/排他锁:共享锁允许多个事务同时读取同一资源,但不允许写入;而排他锁则既不允许其他事务读也不允许写,保证了数据的一致性。
行级锁 (Row-Level Lock)
- 定义:行级锁是指锁定数据库中的单个数据行。当一个事务执行更新或删除操作时,它会锁定受影响的数据行,以防止其他事务同时修改这些行。
- 优点:
- 提高并发性能,因为只有特定的行被锁定,其他未受锁影响的行仍然可以被其他事务访问。
- 减少锁竞争,提高系统的整体吞吐量。
- 缺点:
- 锁管理开销较大,尤其是当大量行被锁定时。
- 可能会导致死锁问题,需要有效的死锁检测和解决机制。
行级锁通常通过SELECT ... FOR UPDATE
或类似语法来实现。这会锁定查询返回的行,直到事务结束。
START TRANSACTION; -- 开始一个新事务
SELECT * FROM orders WHERE order_id = 12345 FOR UPDATE; -- 锁定订单ID为12345的记录
-- 在这里执行更新操作
UPDATE orders SET status = 'processed' WHERE order_id = 12345;
COMMIT; -- 提交事务,释放锁
表级锁 (Table-Level Lock)
- 定义:表级锁是锁定整个表。一旦一个事务对某个表加了锁,其他事务就不能对该表进行任何读写操作(取决于锁的类型),直到该锁被释放。
- 优点:
- 实现简单,管理开销小。
- 适用于小表或者写操作频繁且数据一致性要求非常高的场景。
- 缺点:
- 降低了并发性能,因为所有试图访问该表的事务都必须等待锁的释放。
- 对于大型表来说,可能会导致严重的性能瓶颈。
可以使用LOCK TABLES
命令或者通过SELECT ... FOR UPDATE
加上适当的提示来锁定整个表。
START TRANSACTION;
LOCK TABLES orders WRITE; -- 写锁,阻止其他会话对orders表进行读写
-- 进行更新操作
UPDATE orders SET status = 'processed' WHERE order_id = 12345;
UNLOCK TABLES; -- 解锁表
COMMIT;
共享锁 (Shared Lock, S-Lock)
- 定义:共享锁允许多个事务同时读取同一资源,但不允许其他事务获取排他锁来修改数据。这意味着多个事务可以同时持有共享锁,但不能有事务同时持有共享锁和排他锁。
- 应用场景:主要用于读取操作,确保在读取期间数据不会被修改。
- 特点:
- 读取之间互不干扰。
- 读取与写入相互排斥。
共享锁允许并发读取但阻止写入。
BEGIN;
SELECT * FROM orders WHERE order_id = 12345 FOR UPDATE NOWAIT; -- 立即获取共享锁
-- 可以有多个这样的选择同时发生
COMMIT;
排他锁 (Exclusive Lock, X-Lock)
- 定义:排他锁阻止其他事务对锁定的资源进行任何形式的访问,无论是读还是写。这意味着如果一个事务对某资源加了排他锁,则其他事务既不能读也不能写这个资源,直到当前事务释放锁。
- 应用场景:主要用于写操作,确保数据的一致性和完整性。
- 特点:
- 写操作之间互斥。
- 写操作与读操作互斥。
排他锁阻止其他所有事务对该资源进行读取或写入。
START TRANSACTION;
SELECT * FROM orders WHERE order_id = 12345 FOR UPDATE; -- 获取排他锁
-- 执行更新
UPDATE orders SET status = 'processed' WHERE order_id = 12345;
COMMIT;
注意事项
- 死锁:在使用锁时要注意避免死锁(线程获得锁的顺序不当时)的发生。可以通过设置合理的超时时间、使用更细粒度的锁(如行级锁)来减少死锁的风险。
- 性能影响:长时间持有锁会影响系统的并发处理能力。尽量减少事务的持续时间和锁持有的时间。高并发的场景下,激烈的锁竞争会造成线程阻塞,大量阻塞线程会导致系统的上下文切换,增加系统的性能开销。
- 事务管理:确保每个事务尽可能短,并且只包含必要的操作,以减少锁的竞争和等待时间。