数据库悲观锁和乐观锁介绍一下?

参考回答

悲观锁乐观锁是两种常用的并发控制机制,主要用于解决多事务并发访问同一数据时的冲突问题。

  • 悲观锁:通过加锁机制防止并发事务对数据的修改,适合冲突概率较高的场景。
  • 乐观锁:不加锁,而是通过版本号或时间戳来检测冲突,适合冲突概率较低的场景。

详细讲解与拓展

1. 悲观锁

(1)定义

悲观锁假定冲突会频繁发生,在操作数据前先加锁,保证当前事务对数据的独占访问,其他事务需等待锁释放后才能访问。

(2)实现方式

在数据库中,悲观锁通常通过 数据库原生的锁机制 实现,例如行级锁、表级锁等。
共享锁(S 锁):允许多个事务同时读,但不允许写。
排他锁(X 锁):只允许一个事务读或写,其他事务被阻塞。

(3)悲观锁的典型场景

适合高冲突场景,例如:
银行转账:需要确保账户余额的正确性。
库存扣减:多个用户同时购买同一件商品时,需要确保库存不会超卖。

(4)实现示例

假设有一个商品表 Products

CREATE TABLE Products (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    stock INT
);

悲观锁示例

-- 开启事务
START TRANSACTION;

-- 查询并加排他锁
SELECT stock FROM Products WHERE id = 1 FOR UPDATE;

-- 更新库存
UPDATE Products SET stock = stock - 1 WHERE id = 1;

-- 提交事务
COMMIT;

FOR UPDATE 的查询中,行锁会阻止其他事务同时修改这条记录。


2. 乐观锁

(1)定义

乐观锁假定冲突较少,允许多个事务同时操作数据,只有在提交时才检查冲突,如果检测到冲突则回滚并重试。

(2)实现方式

乐观锁通常通过版本号(Version)时间戳(Timestamp)字段实现:
– 每次更新时检查版本号是否匹配,不匹配则说明发生了并发冲突。

(3)乐观锁的典型场景

适合低冲突场景,例如:
表单更新:用户同时编辑表单,提交时校验版本号。
统计系统:数据更新频率较低时,使用乐观锁避免频繁加锁。

(4)实现示例

假设同样的商品表 Products 添加了版本号字段:

ALTER TABLE Products ADD version INT DEFAULT 0;

乐观锁示例

-- 查询当前库存和版本号
SELECT stock, version FROM Products WHERE id = 1;

-- 尝试更新(乐观锁通过版本号控制)
UPDATE Products
SET stock = stock - 1, version = version + 1
WHERE id = 1 AND version = 0;

-- 检查是否更新成功
IF ROW_COUNT() = 0 THEN
    -- 说明版本号不匹配,冲突发生
    -- 回滚或重试操作
END IF;

在此示例中,如果 version 不匹配,更新失败,表示发生了并发修改。


3. 悲观锁与乐观锁的对比

特性 悲观锁 乐观锁
适用场景 高冲突场景,如银行转账、库存扣减 低冲突场景,如统计分析、表单编辑
实现方式 数据库加锁(行锁、表锁) 版本号或时间戳
性能开销 开销高(锁等待、锁定资源) 开销低(无锁操作,但需检测冲突)
并发性能 并发性能低 并发性能高
事务冲突处理 通过阻塞等待解决 通过检测冲突并回滚重试
典型问题 锁等待、死锁 多次冲突可能导致频繁重试

4. 选择悲观锁还是乐观锁?

1)选择悲观锁
  • 数据竞争严重,冲突发生概率高。
  • 数据对一致性要求高,不能容忍失败。
  • 示例场景:
    • 银行账户转账。
    • 多用户实时操作库存。
2)选择乐观锁
  • 数据竞争较低,冲突发生概率小。
  • 对事务性能要求高,希望减少锁的开销。
  • 示例场景:
    • 用户更新个人资料。
    • 数据分析中的统计更新。

总结

悲观锁通过加锁来确保数据的一致性,适合冲突频繁且对数据要求严格的场景;而乐观锁则通过版本控制来避免锁开销,适合低冲突、高性能需求的场景。选择哪种锁机制,需根据业务特性和并发冲突概率权衡使用。

发表评论

后才能评论